关于哈希表,在内核里设计两个很重要的数据结构:哈希链表节点:

点击(此处)折叠或打开

/*Kernel Version:3.4.x[include/linux/types.h]*/

struct hlist_node{

struct hlist_node*next, **pprev;

};可以看到哈希节点和内核普通双向链表的节点唯一的区别就在于,前向节点pprev是个两级指针,至于为什么这样设计而不采用struct list_head{}来作为哈希链表的节点,我们后面会详细介绍。另外一个重要的数据结构是,哈希链表的表头。哈希链表表头:点击(此处)折叠或打开

/*Kernel Version:3.4.x[include/linux/types.h]*/

struct hlist_head{

struct hlist_node*first;

};因为哈希链表并不需要双向循环的技能,它一般适用于单向散列的场景。所以,为了减少开销,并没有用struct hlist_node{}来代表哈希表头,而是重新设计struct hlist_head{}这个数据结构。此时,一个哈希表头就只需要4Byte了,相比于struct hlist_node{}来说,存储空间已经减少了一半。这样一来,在需要大量用到哈希链表的场景,其存储空间的节约是非常明显的,特别是在嵌入式设备领域。接下来,我们来重点回答一下哈希节点里那个两级指针的问题。先讲个小插曲,记得本人当年刚参加工作时,导师给安排了一个活儿,那时候年轻气盛、血气方刚,没一会儿功夫,三下五除二就搞定了。然后拿着自己的“杰作”去师傅看,师傅瞄了一眼说,你这函数简直是一坨shi(和乔老爷当年骂另外一个程序员的用词、语气差不多),谁让你函数入参传个三级指针进去的?这段代码TM能维护么?谁看得懂?完了之后感觉自己还受了莫大的委屈一样,不过谁的人生没有那么点波澜壮阔的过往呢,就像有句名言说的:程序写出来是给人看的,顺带能在机器上运行。OK,那这个故事跟我们要介绍的哈希节点的关系在哪儿呢?没错,就是struct hlist_node{}里那个前向的两级指针的存在意义。

关于两级指针的目的与意义,让我们采用反证法来看看,如果struct

hlist_node{}被设计成如下一级指针的样子,会发生什么:

点击(此处)折叠或打开

struct hlist_node{

struct hlist_node*next, *pprev;

};假如我们现在已经有一个哈希链表了myhlist(先别管这个链表是怎么来的),链表里有4个节点node1~node4:

然后就有以下两个问题跟着冒出来:1)、在往哈希链myhlist里插入node1时必须这么写:

点击(此处)折叠或打开

mylist.first=node1;

node1->pprev=(struct hlist_node*)&mylist;

除此之外,在插入node2~node4以及后续其他节点时(假如按顺序插入的话),写法如下(X>=2):点击(此处)折叠或打开

node[X]->next =node[X+1];

node[X]->pprev=node[X-1];

简而言之啥意思呢?往哈希链表里插入元素时,如果在表头的第一个位置上插入元素,和插入在哈希链表的其他位置上的代码处理逻辑是不一样的。因为哈希表头是list_head类型,而其他节点都是list_node类型。

2)、同样,如果删除节点时,对于非首节点,以node2为例:

点击(此处)折叠或打开

node2->pprev->next=node2->next;

node2->next->pprev=node2->pprev;

如果要删除首节点node1呢,则写法如下:点击(此处)折叠或打开

((struct hlist_head*)(node1->pprev))->first=node1->next;

node1->next->pprev=(struct hlist_node*)&mylist;或者node1->next->pprev=node1->pprev;很明显,内核开发者们怎么会容许这样的代码存在,而且还要充分考虑效率的问题。那么,当hlist_node.pprev被设计成两级指针后有啥好处?还是以删除节点为例,如果要删除首节点,因为node1->pprev里保存的是myhlist的地址,而myhlist.first永远都指向哈希链表的第一个节点,我们要间接改变表头里的hlist_node类型的first指针的值,能想到的最直接的办法当然是二级指针,这是两级指针的宿命所决定的,为了间接改变一级指针所指的内存地址的场景。这样一来,node节点里的pprev其实指向的是其前一个节点里的第一个指针元素的地址。对于hlist_head来说,它里面只有一个指针元素,就是first指针;而对于hlist_node来说,第一个指针元素就是next。具体如下所示:

所以,记住,当我们在代码中看到类似与*(hlist_node->pprev)这样的代码时,我们心里应该清楚,此时正在哈希表里操作当前节点前一个节点里的第一个指针元素所指向的内存地址,只是以间接的方式实现罢了。那么回到删除哈希链表节点的场景,当删除首节点时,此时情况就变成了:

点击(此处)折叠或打开

*(node1->pprev) =node1->next;

node1->next->pprev=node1->pprev;删除非首节点的情况也一样:点击(此处)折叠或打开

*(node2->pprev) =node2->next;

node2->next->pprev=node2->pprev;

这样一来,我们对hlist_node里的谅解指针pprev的存在价值与意义应该很明白了,以后不至于再被眼花缭乱的取地址操作符给弄晕了。OK,扯了这么多,让我们看看内核是如何实现删除哈希链表里的节点的__hlist_del():

大家自行将上述函数里的入参n换成node2,最终和我们上面推断的结果是一致的:

在标准的哈希链表里,因为最后一个节点的next=NULL,所以在执行第二句有效代码前首先要对当前节点的next值进行判断才行。内核提供了hlist_add_head(),用于实现向哈希链表里插入节点:点击(此处)折叠或打开

hlist_add_head(struct hlist_node*n,struct hlist_head*h)其中n表示待插入的节点,h表示哈希链表表头。在刚初始化完哈希表myhlist的情况下,依次调用四次hlist_add_head(),每次调用后myhlist哈希表的情况如下:

(备注:双箭头表示两级指针,单箭头表示一级指针)理论上说,内核应该再提供一个对称的方法hlist_add_tail()才算完美,用于将哈希链表操作成如下的样子:

还有hlist_add_behind()和hlist_add_before(),在3.17版本之前hlist_add_behind()的名字还是hlist_add_after(),不过作用都一样。两个函数原型分别如下:

点击(此处)折叠或打开

hlist_add_before(struct hlist_node*n,struct hlist_node*next);

hlist_add_behind(struct hlist_node*n,struct hlist_node*prev);

其中n是待插入的节点,next或者prev都是n的相对位置参考节点,其作用分别是:hlist_add_before():在next节点的前面插入n节点;hlist_add_behind():在prev节点的后面插入n节点;

接下来,让我们…..

1)、在node4节点的前面插入node3:

注意hlist_add_before()有个约束条件,那就是next!=NULL。2)、在node1的节点后面插入node5:

同样的约束条件也适用于hlist_add_behind(),即prev!=NULL。

未完,待续...

linux内核 猪头 作用,漫谈Linux内核哈希表(1)相关推荐

  1. linux内核 猪头 作用,Linux内核驱动之一些重要数据结构

    文件操作 到现在,我们已经保留了一些设备编号给我们使用,但是我们还没有连接任何我们设备操作到这些编号上. file_operation结构是一个字符驱动如何建立这个连接.这个结构,定义在,是一个函数指 ...

  2. Linux进程top命令作用是,linux top命令详解

    top 命令主要用于查看进程的相关信息,同时它也会提供系统平均负载,cpu 信息和内存信息.下面的截图展示了 top 命令默认提供的信息: 系统平均负载 top 命令输出中的第一行是系统的平均负载,这 ...

  3. linux高级编程有作用吗,Linux 高级编程

    8种机械键盘轴体对比 本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选? 版权声明:本文为 DLonng 原创文章,可以随意转载,但必须在明确位置注明出处! 信号量 semaphore 信号量( ...

  4. linux信号值头文件位置,Linux C 信号处理机制

    一 . 信号 1. 信号:是内核发送给某一进程的一种消息 . 2. 信号机制:是Linux系统中用于进程之间相互通信或操作的一种机制. 3. 信号的来源:信号来源于内核 4. 产生原因: (1)用户通 ...

  5. linux中profile文件作用,解析Linux系统中bashrc和profile文件的作用区别

    使用终端ssh登录Linux操作系统的控制台后,会出现一个提示符号(例如:#或~),在这个提示符号之后可以输入命令,Linux根据输入的命令会做回应,这一连串的动作是由一个所谓的Shell来做处理. ...

  6. linux怎么添加头文件目录下,linux下编写c++,include的那些头文件在什么地方?

    炎炎设计 C/C++程序在linux下被编译和连接时,GCC/G++会查找系统默认的include和link的路径,以及自己在编译命令中指定的路径.自己指定的路径就不说了,这里说明一下系统自动搜索的路 ...

  7. linux中ftok的作用,Unix/Linux编程之ftok函数用法

    linux 中ftok函数的用法 1.函数作用: 系统建立IPC通讯(如消息队列.共享内存时)必须指定一个ID值.通常情况下,该id值通过ftok函数得到 2.函数原型: #include #incl ...

  8. Linux进程top命令作用是,Linux中top命令起什么作用呢?

    摘要: 下文讲述Linux中top的功能说明,如下所示: top命令功能: 用于实时显示系统中各进行对各种个资源的占用情况 top命令的语法格式: top [参数] -----常用参数说明------ ...

  9. Linux中mv的作用是,linux中的mv命令的详细解释

    linxu下的mv命令是一个常用命令,可以为文件或者目录重命名,下面由秋天网 Qiutian.ZqNF.Com小编为大家整理了linux的mv命令的详细解释的相关知识,希望对大家有帮助! 一.linu ...

最新文章

  1. 建堆 java_堆排序就这么简单
  2. Mysql的性能优化
  3. Python:列表、集合等交集、并集、差集、非集简介及其代码实现之详细攻略
  4. js 只准输入数字_js实现文本框只允许输入数字并限制数字大小的方法
  5. mysql 不显示消息错误_如何编写不吸的错误消息
  6. 总有几位老师让你一生感激不尽----我的大学老师
  7. 论文笔记_S2D.68_深度补全网络
  8. 儿童讲堂 - 学科分类
  9. cad解除块的快捷命令_cad隐藏块快捷键是什么,Auto CAD隐藏块快捷键是什么?
  10. Centos下安装FastDFS
  11. 六十星系之46廉贞独坐寅申
  12. 下列属于计算机应用,计算机的应用领域可大致分为6个方面,下列选项中属于计算机应用领域的是...
  13. SVD分解和矩阵的Lipschitz条件等
  14. w7计算机摄像头怎么打开,如何打开摄像头,详细教您Win7摄像头怎么打开
  15. 机器学习必看书籍和视频汇总
  16. 算法优化:旋转对称图最优解法及思路分享(几乎最优)
  17. 中南大学计算机博士就业,求救!中南大学博士毕业要求
  18. php deel views,Drupal视图Views可用的显示模板文件
  19. Activity启动过程源码分析
  20. 多个git账号的登录与切换

热门文章

  1. 【转】深入理解JavaScript系列(8):S.O.L.I.D五大原则之里氏替换原则LSP
  2. 双11之后,国际、大型、创业、传统、搜索等电商架构首次深度较量
  3. Java JDK 安装方法---阿哲专属版
  4. mysql的基本情况是什么意思_数据库是什么意思
  5. win7 MW300U 共享wifi
  6. php写的一个超短线选股程序(选股效果还是不错的)
  7. Milimeter-Wave UAV Communications(21-30)
  8. 摄像机互联网直播--GB/28181
  9. 用Python解析HTML,BeautifulSoup使用简介
  10. 图像的匹配、配准、融合、拼接等概念的区别