對照前面介紹過的內核通知鏈、鏈表,本章咱們將要介紹的哈希表的初始化和定義也是一模一樣的:
- 定義並初始化一個名爲name的哈希鏈表表頭
- #define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
-
- 初始化一個已經定義好的哈希鏈表,其中ptr指向哈希表頭的地址
- #define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
其中,HLIST_HEAD_INIT
通常這麼用:
- struct hlist_head myhlist;
- HLIST_HEAD_INIT(&myhlist);
對於哈希表中的每個hlist_node節點,一般狀況下都要調用初始化函數INIT_HLIST_NODE()來初始化:
- static inline void INIT_HLIST_NODE(struct hlist_node *h)
- {
- h->next = NULL;
- h->pprev = NULL;
- }
一個給定的哈希節點,判斷它是否已經被插入到某條哈希鏈表裏hlist_unhashed():
- static inline int hlist_unhashed(const struct hlist_node *h)
- {
- return !h->pprev;
- }
這裏咱們能夠看到,
hlist_node
裏的
pprev
完成了這個功能,即若是一個
hlist_node
的
pprev
爲
NULL
,則說明該節點目前並未加入任何哈希鏈表。
下面這個接口就沒啥好說的,用於判斷是一個給定哈希表是否爲空(即不包含任何哈希節點)。注意,該接口入參爲
hlist_head
類型而非
hlist_node
類型:
- static inline int hlist_empty(const struct hlist_head *h)
- {
- return !h->first;
- }
剩下的其餘接口,也都很是簡單,這裏再也不一一贅述。下面咱們看幾個宏定義:
- #define hlist_entry(ptr, type, member) container_of(ptr,type,member)
- 該宏和前面介紹過的list_entry()的實現、做用徹底同樣
- #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_*的學習過程:
- hlist_for_each_entry(tpos, pos, head, member)
其中tpos,是hlist_node所屬宿主結構體類型的指針,pos是hlist_node類型的指針,tpos和pos都充當的遊標的做用。例如:
- typedef struct student
- {
- char m_name[MAX_STRING_LEN];
- char m_sex;
- int m_age;
- struct list_head m_list; /*把咱們的學生對象組織成雙向鏈表,就靠該節點了*/
- struct hlist_node m_hlist; /*把咱們的學生對象組織成哈希鏈表,就靠該節點了*/
- }Student;
-
- HLIST_HEAD(myhlist);
- Student *st;
- struct hlist_node *i;
- hlist_for_each_entry(st, i, &myhlist, m_hlist)
- {
- //To do something here…
- //一般狀況,開發者在這裏僅須要關注、使用st變量就能夠,不須要關心i
- }
一樣地,在使用hlist_for_each_entry_safe(tpos, pos, n, head, member)時,tpos也是宿主結構體類型的一個指針變量,當遊標使用,n是一個hlist_node類型的另外一個指針,這個指針指向pos所在元素的下一個元素,它由hlist_for_each_entry_safe()自己進行維護,開發者不用修改它:
- HLIST_HEAD(myhlist);
- Student *st;
- struct hlist_node *i,*j;
- hlist_for_each_entry_safe(st, i, j, &myhlist, m_hlist)
- {
- //To do something here…
- //i和j都不須要開發者關注,僅使用st就能夠了
- }
另外,還有一組宏:
- hlist_for_each_entry_continue(tpos, pos, member)
- 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
結構體的改造了。最終的完整代碼以下所示:
頭文件修改:
- /*student.h*/
-
- #ifndef __STUDENT_H_
- #define __STUDENT_H_
-
- #include <linux/list.h>
- #define MAX_STRING_LEN 32
- #define MAX_HLIST_COUNT 2 //只有「男」、「女」兩條哈希鏈表
- typedef struct student
- {
- char m_name[MAX_STRING_LEN];
- char m_sex;
- int m_age;
- struct list_head m_list; /*把咱們的學生對象組織成雙向鏈表,就靠該節點了*/
- struct hlist_node m_hlist; /*把咱們的學生對象組織成哈希鏈表,就靠該節點了*/
- }Student;
- #endif
源文件修改:
- #include <linux/module.h>
-
- #include <linux/kernel.h>
- #include <linux/init.h>
-
- #include "student.h"
-
- MODULE_LICENSE("Dual BSD/GPL");
- MODULE_AUTHOR("Koorey Wung");
-
- static int dbg_flg = 0;
-
- LIST_HEAD(g_student_list);
- // 其中g_stu_hlist[0]表明男生;g_stu_hlist[1]表明女生
- struct hlist_head g_stu_hlist[MAX_HLIST_COUNT];
-
- //初始化男、女學生的哈希鏈表
- static void init_hlists(void)
- {
- int i = 0;
- for(i=0;i< MAX_HLIST_COUNT;i++){
- INIT_HLIST_HEAD(&g_stu_hlist[i]);
- }
- }
-
- static int add_stu(char* name,char sex,int age)
- {
- Student *stu,*cur_stu;
-
- list_for_each_entry(cur_stu,&g_student_list,m_list){ //僅遍歷是否有同名學生,因此用該接口
- if(0 == strcmp(cur_stu->m_name,name))
- {
- printk("Error:the name confict!\n");
- return -1;
- }
- }
-
- stu = kmalloc(sizeof(Student), GFP_KERNEL);
- if(!stu)
- {
- printk("kmalloc mem error!\n");
- return -1;
- }
-
- memset(stu,0,sizeof(Student));
- strncpy(stu->m_name,name,strlen(name));
- stu->m_sex = sex;
- stu->m_age = age;
- INIT_LIST_HEAD(&stu->m_list); //初始化宿主結構裏的雙向鏈表節點m_list
- INIT_HLIST_NODE(&stu->m_hlist); //初始化宿主結構裏的哈希節點m_hlist
-
- if(dbg_flg)
- printk("(Add)name:[%s],\tsex:[%c],\tage:[%d]\n",stu->m_name,stu->m_sex,stu->m_age);
-
-
- list_add_tail(&stu->m_list,&g_student_list); //將新學生插入到鏈表尾部,很簡單吧
-
- return 0;
- }
- EXPORT_SYMBOL(add_stu); //導出該函數,後面咱們要在其餘模塊裏調用,爲了便於測試,下面其餘幾個接口相似
-
- static int del_stu(char *name)
- {
- Student *cur,*next;
- int ret = -1;
- list_for_each_entry_safe(cur,next,&g_student_list,m_list){ //由於要刪除鏈表的節點,因此必須有帶有「safe」的宏接口
- if(0 == strcmp(name,cur->m_name))
- {
- list_del(&cur->m_list);
- printk("(Del)name:[%s],\tsex:[%c],\tage:[%d]\n",cur->m_name,\
- cur->m_sex,cur->m_age);
- kfree(cur);
- cur = NULL;
- ret = 0;
- break;
- }
- }
- return ret;
- }
- EXPORT_SYMBOL(del_stu);
-
- static void dump_students(void)
- {
- Student *stu;
- int i = 1;
- printk("===================Student List================\n");
- list_for_each_entry(stu,&g_student_list,m_list){ //一樣,也僅遍歷鏈表而已
- printk("(%d)name:[%s],\tsex:[%c],\tage:[%d]\n",i++,stu->m_name,\
- stu->m_sex,stu->m_age);
- }
- printk("===============================================\n");
- }
- EXPORT_SYMBOL(dump_students);
-
- static void dump_hlist(int id)
- {
- Student *stu;
- struct hlist_node *i;
- struct hlist_head *head;
- int count = 1;
-
- if(!(id>=0 && id< MAX_HLIST_COUNT)){
- printk("Invalid id[%d] !\n",id);
- return;
- }
- head = &g_stu_hlist[id];
-
- printk("===================%s List===================\n",((id == 0)?"Boy":"Girl"));
- //由於該接口只遍歷哈希表,並不會插入、刪除節點,因此用hlist_for_each_entry(),注意四個入參的類型、做用和意義
- hlist_for_each_entry(stu, i, head,m_hlist){
- printk("(%d)name:[%s],\tsex:[%c],\tage:[%d]\n",count++,stu->m_name,\
- stu->m_sex,stu->m_age);
- }
- printk("==============================================\n");
- }
- EXPORT_SYMBOL(dump_hlist);
-
- //分別打印男女學生,各自哈希鏈表上的狀況
- static void dump_hlists(void)
- {
- dump_hlist(0);
- dump_hlist(1);
- }
- EXPORT_SYMBOL(dump_hlists);
-
-
- //按照性別對學生進行分類
- static void classify_stu(void)
- {
- Student *cur,*next;
- int id = 0;
-
- list_for_each_entry_safe(cur,next,&g_student_list,m_list){
- //將從cur從g_student_list鏈表上移下來,但並不會釋放cur學生的內存空間,同時對其m_list成員從新初始化
- list_del_init(&cur->m_list);
- if('m' == cur->m_sex){
- id = 0;
- }
- else if('f' == cur->m_sex){
- id = 1;
- }
- else{
- printk("Get error!\n");
- return;
- }
- //根據id,以m_hlist將學生按性別組織成哈希表
- hlist_add_head(&(cur->m_hlist),&(g_stu_hlist[id]));
- }
- printk("Finished!\n");
- }
- EXPORT_SYMBOL(classify_stu);
-
-
- static void init_system(void)
- {
- //初始化男、女學生哈希鏈表頭
- init_hlists();
-
- /*系統啓動初始化時,向鏈表g_student_list裏添加6個學生*/
- add_stu("Tom",'m',18);
- add_stu("Jerry",'f',17);
- add_stu("Alex",'m',18);
- add_stu("Conory",'f',18);
- add_stu("Frank",'m',17);
- add_stu("Marry",'f',17);
- }
-
- /*釋放全部哈希鏈表上的內存空間*/
- static void clean_up_hlist(void)
- {
- int i;
- Student *stu;
- struct hlist_node *cur,*next;
-
- for(i=0;i< MAX_HLIST_COUNT;i++){
- printk("===================%s List================\n",((i == 0)?"Boy":"Girl"));
- hlist_for_each_entry_safe(stu, cur, next, &(g_stu_hlist[i]), m_hlist){
- hlist_del(&(stu->m_hlist));
- printk("Destroy [%s]\n",stu->m_name);
- kfree(stu);
- }
- printk("===========================================\n");
- }
- }
-
- /*釋放雙向表上的內存空間*/
- static void clean_up_list(void)
- {
- Student *stu,*next;
- printk("===========Unclassified Student List===========\n");
- list_for_each_entry_safe(stu,next,&g_student_list,m_list){
- list_del(&stu->m_list);
- printk("Destroy [%s]\n",stu->m_name);
- kfree(stu);
- }
- printk("===============================================\n");
- }
-
- /*由於沒有數據庫,因此當咱們的模塊退出時,須要釋放內存。*/
- static void clean_up(void)
- {
- clean_up_list();
- clean_up_hlist();
- }
-
- /*模塊初始化接口*/
- static int student_mgt_init(void)
- {
- printk("Student Managment System,Initializing...\n");
-
- init_system();
- dbg_flg = 1; //今後之後,再調用add_stu()時,都會有有內核打印信息,詳見實例訓練
- dump_students();
-
- return 0;
- }
-
- static void student_mgt_exit(void)
- {
- clean_up();
- printk("System Terminated!\n");
- }
-
- module_init(student_mgt_init);
- 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()作準備:
其實能夠看到,哈希鏈表的用法也是蠻簡單的。其實內核裏諸如通知鏈、鏈表、哈希表等等這些基礎數據結構,掌握了原理後使用起來都不難。 未完,待續....