漫談Linux內核哈希表(2)

    對照前面介紹過的內核通知鏈、鏈表,本章咱們將要介紹的哈希表的初始化和定義也是一模一樣的:

點擊(此處)摺疊或打開 css

  1. 定義並初始化一個名爲name的哈希鏈表表頭
  2. #define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }

  3. 初始化一個已經定義好的哈希鏈表,其中ptr指向哈希表頭的地址
  4. #define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
   
   其中,HLIST_HEAD_INIT
通常這麼用:

點擊(此處)摺疊或打開 node

  1. struct hlist_head myhlist;
  2. HLIST_HEAD_INIT(&myhlist);

    對於哈希表中的每個hlist_node節點,一般狀況下都要調用初始化函數INIT_HLIST_NODE()來初始化:

點擊(此處)摺疊或打開 linux

  1. static inline void INIT_HLIST_NODE(struct hlist_node *h)
  2. {
  3.     h->next = NULL;
  4.     h->pprev = NULL;
  5. }

    一個給定的哈希節點,判斷它是否已經被插入到某條哈希鏈表裏hlist_unhashed()

點擊(此處)摺疊或打開 數據庫

  1. static inline int hlist_unhashed(const struct hlist_node *h)
  2. {
  3.     return !h->pprev;
  4. }
    這裏咱們能夠看到, hlist_node 裏的 pprev 完成了這個功能,即若是一個 hlist_node pprev NULL ,則說明該節點目前並未加入任何哈希鏈表。

   
下面這個接口就沒啥好說的,用於判斷是一個給定哈希表是否爲空(即不包含任何哈希節點)。注意,該接口入參爲 hlist_head 類型而非 hlist_node 類型:

點擊(此處)摺疊或打開 安全

  1. static inline int hlist_empty(const struct hlist_head *h)
  2. {
  3.     return !h->first;
  4. }
    
   剩下的其餘接口,也都很是簡單,這裏再也不一一贅述。下面咱們看幾個宏定義:

點擊(此處)摺疊或打開 數據結構

  1. #define hlist_entry(ptr, type, member) container_of(ptr,type,member)
  2. 該宏和前面介紹過的list_entry()的實現、做用徹底同樣
  3. #define list_entry(ptr, type, member)  container_of(ptr,type,member)
    
   對照list的學習過程,可想而知,下面這幾組結構,其做用也就不言而喻了:
哈希表 鏈表
hlist_for_each(pos, head)
list_for_each(pos, head)
hlist_for_each_safe(pos, n, head)
list_for_each_safe(pos, n, head)
hlist_for_each_entry(tpos, pos, head, member)
list_for_each_entry(pos, head, member)
hlist_for_each_entry_safe(tpos, pos, n, head, member)
list_for_each_entry_safe(pos, n, head, member)

    區別在於最後兩個宏的入參上有些小區別。因爲哈希鏈表,表頭和表節點是不一樣的數據結構,因此纔會有這個差別。仍是對照着list_for_each_*的學習過程:

點擊(此處)摺疊或打開 ide

  1. hlist_for_each_entry(tpos, pos, head, member)
    其中tpos,是hlist_node所屬宿主結構體類型的指針,pos是hlist_node類型的指針,tpos和pos都充當的遊標的做用。例如:

點擊(此處)摺疊或打開 函數

  1. typedef struct student
  2. {
  3.     char m_name[MAX_STRING_LEN];
  4.     char m_sex;
  5.     int m_age;
  6.     struct list_head m_list; /*把咱們的學生對象組織成雙向鏈表,就靠該節點了*/
  7.     struct hlist_node m_hlist; /*把咱們的學生對象組織成哈希鏈表,就靠該節點了*/
  8. }Student;

  9. HLIST_HEAD(myhlist);
  10. Student *st;
  11. struct hlist_node *i;
  12. hlist_for_each_entry(st, i&myhlist, m_hlist)
  13. {
  14.     //To do something here…
  15.     //一般狀況,開發者在這裏僅須要關注、使用st變量就能夠,不須要關心i
  16. }
    
   一樣地,在使用hlist_for_each_entry_safe(tpos, pos, n, head, member)時,tpos也是宿主結構體類型的一個指針變量,當遊標使用,n是一個hlist_node類型的另外一個指針,這個指針指向pos所在元素的下一個元素,它由hlist_for_each_entry_safe()自己進行維護,開發者不用修改它

點擊(此處)摺疊或打開 學習

  1. HLIST_HEAD(myhlist);
  2. Student *st;
  3. struct hlist_node *i,*j;
  4. hlist_for_each_entry_safe(st, i, j, &myhlist, m_hlist)
  5. {
  6.     //To do something here…
  7.     //i和j都不須要開發者關注,僅使用st就能夠了
  8. }
    
   另外,還有一組宏:

點擊(此處)摺疊或打開 測試

  1. hlist_for_each_entry_continue(tpos, pos, member)    
  2. hlist_for_each_entry_from(tpos, pos, member)
    其參數 tpos pos 意義和類型與前面介紹過的一致,這兩個宏的做用分別是:
   
hlist_for_each_entry_continue() :從 pos 節點開始 ( 不包含 pos) ,日後依次遍歷全部節點;
   
hlist_for_each_entry_from()       pos 節點開始 ( 包含 pos) ,依次日後遍歷全部節點;
   
這一組宏是「不安全」的,意思是,在它們裏面你只能執行查找遍歷的任務、不能插入或者刪除節點,由於它們腦門上沒有那個「 safe 」的關鍵字。

   
最後,仍是老生常談,實際操練一把。把鏈表章節咱們介紹過的學歷管理系統拿來,添加一個需求:「按照男、女的分類原則,將全部學生進行分類」。很明顯,這裏咱們就能夠用到哈希鏈表了。怎麼實現呢?其實很是簡單,前面咱們已經見過對 Student 結構體的改造了。最終的完整代碼以下所示:

   頭文件修改:

點擊(此處)摺疊或打開

  1. /*student.h*/

  2. #ifndef __STUDENT_H_
  3. #define __STUDENT_H_

  4. #include <linux/list.h>
  5. #define MAX_STRING_LEN 32
  6. #define MAX_HLIST_COUNT 2 //只有「男」、「女」兩條哈希鏈表
  7. typedef struct student
  8. {
  9.         char m_name[MAX_STRING_LEN];
  10.         char m_sex;
  11.         int m_age;
  12.         struct list_head m_list; /*把咱們的學生對象組織成雙向鏈表,就靠該節點了*/
  13.         struct hlist_node m_hlist; /*把咱們的學生對象組織成哈希鏈表,就靠該節點了*/
  14. }Student;
  15. #endif
   
   源文件修改:

點擊(此處)摺疊或打開

  1. #include <linux/module.h>

  2. #include <linux/kernel.h>
  3. #include <linux/init.h>

  4. #include "student.h"

  5. MODULE_LICENSE("Dual BSD/GPL");
  6. MODULE_AUTHOR("Koorey Wung");

  7. static int dbg_flg = 0;

  8. LIST_HEAD(g_student_list);
  9. // 其中g_stu_hlist[0]表明男生;g_stu_hlist[1]表明女生
  10. struct hlist_head g_stu_hlist[MAX_HLIST_COUNT];

  11. //初始化男、女學生的哈希鏈表
  12. static void init_hlists(void)
  13. {
  14.     int i = 0;
  15.     for(i=0;i< MAX_HLIST_COUNT;i++){
  16.         INIT_HLIST_HEAD(&g_stu_hlist[i]);
  17.     }
  18. }

  19. static int add_stu(char* name,char sex,int age)
  20. {
  21.     Student *stu,*cur_stu;

  22.     list_for_each_entry(cur_stu,&g_student_list,m_list){ //僅遍歷是否有同名學生,因此用該接口
  23.         if(0 == strcmp(cur_stu->m_name,name))
  24.         {
  25.             printk("Error:the name confict!\n");
  26.             return -1;
  27.         }
  28.     }

  29.     stu = kmalloc(sizeof(Student), GFP_KERNEL);
  30.     if(!stu)
  31.     {
  32.         printk("kmalloc mem error!\n");
  33.         return -1;
  34.     }

  35.     memset(stu,0,sizeof(Student));
  36.     strncpy(stu->m_name,name,strlen(name));
  37.     stu->m_sex = sex;
  38.     stu->m_age = age;
  39.     INIT_LIST_HEAD(&stu->m_list);    //初始化宿主結構裏的雙向鏈表節點m_list
  40.     INIT_HLIST_NODE(&stu->m_hlist);  //初始化宿主結構裏的哈希節點m_hlist

  41.     if(dbg_flg)
  42.         printk("(Add)name:[%s],\tsex:[%c],\tage:[%d]\n",stu->m_name,stu->m_sex,stu->m_age);


  43.     list_add_tail(&stu->m_list,&g_student_list); //將新學生插入到鏈表尾部,很簡單吧

  44.     return 0;
  45. }
  46. EXPORT_SYMBOL(add_stu); //導出該函數,後面咱們要在其餘模塊裏調用,爲了便於測試,下面其餘幾個接口相似

  47. static int del_stu(char *name)
  48. {
  49.         Student *cur,*next;
  50.         int ret = -1;
  51.         list_for_each_entry_safe(cur,next,&g_student_list,m_list){ //由於要刪除鏈表的節點,因此必須有帶有「safe」的宏接口
  52.                 if(0 == strcmp(name,cur->m_name))
  53.                 {
  54.                         list_del(&cur->m_list);
  55.                         printk("(Del)name:[%s],\tsex:[%c],\tage:[%d]\n",cur->m_name,\
  56.                                         cur->m_sex,cur->m_age);
  57.                         kfree(cur);
  58.                         cur = NULL;
  59.                         ret = 0;
  60.                         break;
  61.                 }
  62.         }
  63.         return ret;
  64. }
  65. EXPORT_SYMBOL(del_stu);

  66. static void dump_students(void)
  67. {
  68.         Student *stu;
  69.         int i = 1;
  70.         printk("===================Student List================\n");
  71.         list_for_each_entry(stu,&g_student_list,m_list){ //一樣,也僅遍歷鏈表而已
  72.                 printk("(%d)name:[%s],\tsex:[%c],\tage:[%d]\n",i++,stu->m_name,\
  73.                         stu->m_sex,stu->m_age);
  74.         }
  75.         printk("===============================================\n");
  76. }
  77. EXPORT_SYMBOL(dump_students);

  78. static void dump_hlist(int id)
  79. {
  80.         Student *stu;
  81.         struct hlist_node *i;
  82.         struct hlist_head *head;
  83.         int count = 1;

  84.         if(!(id>=0 && id< MAX_HLIST_COUNT)){
  85.                 printk("Invalid id[%d] !\n",id);
  86.                 return;
  87.         }
  88.         head = &g_stu_hlist[id];

  89.         printk("===================%s List===================\n",((id == 0)?"Boy":"Girl"));
  90.         //由於該接口只遍歷哈希表,並不會插入、刪除節點,因此用hlist_for_each_entry(),注意四個入參的類型、做用和意義
  91.         hlist_for_each_entry(stu, i, head,m_hlist){
  92.                 printk("(%d)name:[%s],\tsex:[%c],\tage:[%d]\n",count++,stu->m_name,\
  93.                         stu->m_sex,stu->m_age);
  94.         }
  95.         printk("==============================================\n");
  96. }
  97. EXPORT_SYMBOL(dump_hlist);

  98. //分別打印男女學生,各自哈希鏈表上的狀況
  99. static void dump_hlists(void)
  100. {
  101.         dump_hlist(0);
  102.         dump_hlist(1);
  103. }
  104. EXPORT_SYMBOL(dump_hlists);


  105. //按照性別對學生進行分類
  106. static void classify_stu(void)
  107. {
  108.         Student *cur,*next;
  109.         int id = 0;

  110.         list_for_each_entry_safe(cur,next,&g_student_list,m_list){
  111.                 //將從cur從g_student_list鏈表上移下來,但並不會釋放cur學生的內存空間,同時對其m_list成員從新初始化
  112.                 list_del_init(&cur->m_list);
  113.                 if('m' == cur->m_sex){
  114.                         id = 0;
  115.                 }
  116.                 else if('f' == cur->m_sex){
  117.                         id = 1;
  118.                 }
  119.                 else{
  120.                         printk("Get error!\n");
  121.                         return;
  122.                 }
  123.                 //根據id,以m_hlist將學生按性別組織成哈希表
  124.                 hlist_add_head(&(cur->m_hlist),&(g_stu_hlist[id]));
  125.         }
  126.         printk("Finished!\n");
  127. }
  128. EXPORT_SYMBOL(classify_stu);


  129. static void init_system(void)
  130. {
  131.     //初始化男、女學生哈希鏈表頭
  132.     init_hlists();

  133.     /*系統啓動初始化時,向鏈表g_student_list裏添加6個學生*/
  134.     add_stu("Tom",'m',18);
  135.     add_stu("Jerry",'f',17);
  136.     add_stu("Alex",'m',18);
  137.     add_stu("Conory",'f',18);
  138.     add_stu("Frank",'m',17);
  139.     add_stu("Marry",'f',17);
  140. }

  141. /*釋放全部哈希鏈表上的內存空間*/
  142. static void clean_up_hlist(void)
  143. {
  144.     int i;
  145.     Student *stu;
  146.     struct hlist_node *cur,*next;

  147.     for(i=0;i< MAX_HLIST_COUNT;i++){
  148.         printk("===================%s List================\n",((i == 0)?"Boy":"Girl"));
  149.         hlist_for_each_entry_safe(stu, cur, next, &(g_stu_hlist[i]), m_hlist){
  150.             hlist_del(&(stu->m_hlist));
  151.             printk("Destroy [%s]\n",stu->m_name);
  152.             kfree(stu);
  153.         }
  154.         printk("===========================================\n");
  155.     }
  156. }

  157. /*釋放雙向表上的內存空間*/
  158. static void clean_up_list(void)
  159. {
  160.         Student *stu,*next;
  161.         printk("===========Unclassified Student List===========\n");
  162.         list_for_each_entry_safe(stu,next,&g_student_list,m_list){
  163.                 list_del(&stu->m_list);
  164.                 printk("Destroy [%s]\n",stu->m_name);
  165.                 kfree(stu);
  166.         }
  167.         printk("===============================================\n");
  168. }

  169. /*由於沒有數據庫,因此當咱們的模塊退出時,須要釋放內存。*/
  170. static void clean_up(void)
  171. {
  172.         clean_up_list();
  173.         clean_up_hlist();
  174. }

  175. /*模塊初始化接口*/
  176. static int student_mgt_init(void)
  177. {
  178.         printk("Student Managment System,Initializing...\n");

  179.         init_system();
  180.         dbg_flg = 1; //今後之後,再調用add_stu()時,都會有有內核打印信息,詳見實例訓練
  181.         dump_students();

  182.         return 0;
  183. }

  184. static void student_mgt_exit(void)
  185. {
  186.         clean_up();
  187.         printk("System Terminated!\n");
  188. }

  189. module_init(student_mgt_init);
  190. module_exit(student_mgt_exit);

    驗證結果以下:
   咱們每調用此classify_stu()就會將目前自由雙向鏈表g_student_list裏的學生按照性別進行分類,男生存儲到哈希鏈表g_stu_hlist[0]裏,女生存儲到哈希鏈表g_stu_hlist[1]裏。而調用add_stu()則是向g_student_list鏈表裏添加學生,以便爲後面調用classify_stu()作準備:
    其實能夠看到,哈希鏈表的用法也是蠻簡單的。其實內核裏諸如通知鏈、鏈表、哈希表等等這些基礎數據結構,掌握了原理後使用起來都不難。    未完,待續....
相關文章
相關標籤/搜索