对照前面介绍过的内核通知链、链表,本章我们将要介绍的哈希表的初始化和定义也是如出一辙的:

点击(此处)折叠或打开

  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一般这么用:

点击(此处)折叠或打开

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

对于哈希表中的每一个hlist_node节点,通常情况下都要调用初始化函数INIT_HLIST_NODE()来初始化:

点击(此处)折叠或打开

  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_*的学习过程:

点击(此处)折叠或打开

  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 ;
  8. LIST_HEAD(g_student_list);
  9. ]代表女生
  10. struct hlist_head g_stu_hlist[MAX_HLIST_COUNT];
  11. //初始化男、女学生的哈希链表
  12. static void init_hlists(void)
  13. {
  14. ;
  15. ;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 ;
  27. }
  28. }
  29. stu = kmalloc(sizeof(Student), GFP_KERNEL);
  30. if(!stu)
  31. {
  32. printk("kmalloc mem error!\n");
  33. return ;
  34. }
  35. memset,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. ;
  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 ;
  60. break;
  61. }
  62. }
  63. return ret;
  64. }
  65. EXPORT_SYMBOL(del_stu);
  66. static void dump_students(void)
  67. {
  68. Student *stu;
  69. ;
  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. ;
  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)?"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);
  102. dump_hlist);
  103. }
  104. EXPORT_SYMBOL(dump_hlists);
  105. //按照性别对学生进行分类
  106. static void classify_stu(void)
  107. {
  108. Student *cur,*next;
  109. ;
  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 ;
  115. }
  116. else if('f' == cur->m_sex){
  117. id ;
  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);
  135. add_stu);
  136. add_stu);
  137. add_stu);
  138. add_stu);
  139. add_stu);
  140. }
  141. /*释放所有哈希链表上的内存空间*/
  142. static void clean_up_hlist(void)
  143. {
  144. int i;
  145. Student *stu;
  146. struct hlist_node *cur,*next;
  147. ;i MAX_HLIST_COUNT;i++){
  148. printk)?"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 ; //从此以后,再调用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()做准备:

其实可以看到,哈希链表的用法也是蛮简单的。其实内核里诸如通知链、链表、哈希表等等这些基础数据结构,掌握了原理后使用起来都不难。
   未完,待续....

漫谈Linux内核哈希表(2)的更多相关文章

  1. 漫谈Linux内核哈希表(1)

    关于哈希表,在内核里设计两个很重要的数据结构:    哈希链表节点: 点击(此处)折叠或打开 .x [include/linux/types.h]*/ struct hlist_node { stru ...

  2. Linux内核哈希表分析与应用

        目录(?)[+]   Linux内核哈希表分析与应用 Author:tiger-johnTime:2012-12-20mail:jibo.tiger@gmail.comBlog:http:// ...

  3. Linux内核静态映射表的建立过程

    /* *    平台:   s5pv210 *    内核版本号: 2.6.35.7 */ kernel/arch/arm/mach-s5pv210/mach-smdkc110.c 这个文件是由三星在 ...

  4. 操作系统 之 哈希表 Linux 内核 应用浅析

    1.基本概念         散列表(Hash  table.也叫哈希表).是依据关键码值(Key  value)而直接进行訪问的数据结构. 也就是说,它通过把关键码值映射到表中一个位置来訪问记录.以 ...

  5. Linux内核架构与底层--读书笔记

    linux中管道符"|"的作用 命令格式:命令A|命令B,即命令1的正确输出作为命令B的操作对象(下图应用别人的图片) 1. 例如: ps aux | grep "tes ...

  6. [PHP内核探索]PHP中的哈希表

    在PHP内核中,其中一个很重要的数据结构就是HashTable.我们常用的数组,在内核中就是用HashTable来实现.那么,PHP的HashTable是怎么实现的呢?最近在看HashTable的数据 ...

  7. Linux内核监控模块-2-系统调用表地址的获取(Linux内核版本3.13)

    那么在Linux内核2.6之后,不能直接导出sys_call_table的地址后,我们要如何获得系统调用表的地址,从而实现系统调用的截获呢. 先贴上我实现好的代码,然后再来讲解吧. modu.c #i ...

  8. linux内核符号表

    我们已经看到 insmod 如何对应共用的内核符号来解决未定义的符号. 表中包含了全局内 核项的地址 -- 函数和变量 -- 需要来完成模块化的驱动. 当加载一个模块, 如何由模块 输出的符号成为内核 ...

  9. 深入理解PHP内核(六)哈希表以及PHP的哈希表实现

    原文链接:http://www.orlion.ga/241/ 一.哈希表(HashTable) 大部分动态语言的实现中都使用了哈希表,哈希表是一种通过哈希函数,将特定的键映射到特定值得一种数据 结构, ...

随机推荐

  1. Thoughtful function is also good for investigation

    Did you know how many friends in your IM? Some of them you are not familiar with, but your friends c ...

  2. lnmp下启动mysql报错 The server quit without updating PID file

    启动时候错误代码:Starting MySQL[FAIL.] The server quit without updating PID file (/var/run/mysqld/mysqld.pid ...

  3. 笔试常考的Linux命令大全

    1. wc -l 统计一个文件的行数.l-line.-c是字节数,-m是字符数,mc不能同时使用.-L打印最长行的长度. 2. 查看系统进程的命令:ps,查看CPU占用命令:top.df:查看磁盘使用 ...

  4. IE8下服务端获取客户端文件的路径为C:/fakePath问题的解决方案

    上一篇文章上提到,IE8下服务端获取客户端文件的路径时,会变成C:/fakePath问题,于是乎通过文件路径去获得文件大小就失败了. 上网搜了一下,主要原因是IE8因为安全考虑,在上传文件时屏蔽了真实 ...

  5. 使用反射,查找WCF异常类型

    //使用System.Reflection,查找System.ServiceModel的异常类型        public void ConsoleException()        {      ...

  6. JetBrains激活

    https://www.imsxm.com/jetbrains-license-server/ 已经累计为大家激活1360577次 :) JetBrains授权服务器:http://idea.imsx ...

  7. WAP端 经验记录2

    1. LightboxV2 插件 点击A 应该关闭弹层的效果,但是 SAMSUNG 手机上原生浏览器上,看上去不会关闭却跳转了,但当点击回退按钮的时候就会看见弹层已经消失(其实之前的关闭效果已经记录了 ...

  8. PHP中使用cURL实现Get和Post请求的方法

    1.cURL介绍  cURL 是一个利用URL语法规定来传输文件和数据的工具,支持很多协议,如HTTP.FTP.TELNET等.最爽的是,PHP也支持 cURL 库.本文将介绍 cURL 的一些高级特 ...

  9. css 隐藏超长的文本!!!

    overflow:hidden; text-overflow:ellipsis;white-space: nowrap; 一起使用!

  10. 如何在cluster上跑R脚本

    R 是一个比较不错但是有时候操蛋的语言,不错是因为用着爽的时候真的很爽,操蛋是因为这种爽不是什么时候都可以的,比如说在cluster上批处理跑R脚本. 当然说这话有些在上面跑过的各种不服气,你丫傻逼吧 ...