漫谈Linux内核哈希表(1)
关于哈希表,在内核里设计两个很重要的数据结构:
哈希链表节点:
点击(此处)折叠或打开
- .x [include/linux/types.h]*/
- struct hlist_node {
- struct hlist_node *next, **pprev;
- };
哈希链表表头:
点击(此处)折叠或打开
- .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;
点击(此处)折叠或打开
- ];
- node];
简而言之啥意思呢?往哈希链表里插入元素时,如果在表头的第一个位置上插入元素,和插入在哈希链表的其他位置上的代码处理逻辑是不一样的。因为哈希表头是list_head类型,而其他节点都是list_node类型。
2)、同样,如果删除节点时,对于非首节点,以node2为例:
点击(此处)折叠或打开
- node2->pprev->next = node2->next;
- node2->next->pprev = node2->pprev;
点击(此处)折叠或打开
- ((struct hlist_head*)(node1->pprev))->first = node1->next;
- node1->next->pprev = ( struct hlist_node*)&mylist; 或者 node1->next->pprev = node1->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,最终和我们上面推断的结果是一致的:
内核提供了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内核哈希表(1)的更多相关文章
- 漫谈Linux内核哈希表(2)
对照前面介绍过的内核通知链.链表,本章我们将要介绍的哈希表的初始化和定义也是如出一辙的: 点击(此处)折叠或打开 定义并初始化一个名为name的哈希链表表头 #define HLIST_HEAD(na ...
- Linux内核哈希表分析与应用
目录(?)[+] Linux内核哈希表分析与应用 Author:tiger-johnTime:2012-12-20mail:jibo.tiger@gmail.comBlog:http:// ...
- Linux内核静态映射表的建立过程
/* * 平台: s5pv210 * 内核版本号: 2.6.35.7 */ kernel/arch/arm/mach-s5pv210/mach-smdkc110.c 这个文件是由三星在 ...
- 操作系统 之 哈希表 Linux 内核 应用浅析
1.基本概念 散列表(Hash table.也叫哈希表).是依据关键码值(Key value)而直接进行訪问的数据结构. 也就是说,它通过把关键码值映射到表中一个位置来訪问记录.以 ...
- Linux内核架构与底层--读书笔记
linux中管道符"|"的作用 命令格式:命令A|命令B,即命令1的正确输出作为命令B的操作对象(下图应用别人的图片) 1. 例如: ps aux | grep "tes ...
- [PHP内核探索]PHP中的哈希表
在PHP内核中,其中一个很重要的数据结构就是HashTable.我们常用的数组,在内核中就是用HashTable来实现.那么,PHP的HashTable是怎么实现的呢?最近在看HashTable的数据 ...
- Linux内核监控模块-2-系统调用表地址的获取(Linux内核版本3.13)
那么在Linux内核2.6之后,不能直接导出sys_call_table的地址后,我们要如何获得系统调用表的地址,从而实现系统调用的截获呢. 先贴上我实现好的代码,然后再来讲解吧. modu.c #i ...
- linux内核符号表
我们已经看到 insmod 如何对应共用的内核符号来解决未定义的符号. 表中包含了全局内 核项的地址 -- 函数和变量 -- 需要来完成模块化的驱动. 当加载一个模块, 如何由模块 输出的符号成为内核 ...
- 深入理解PHP内核(六)哈希表以及PHP的哈希表实现
原文链接:http://www.orlion.ga/241/ 一.哈希表(HashTable) 大部分动态语言的实现中都使用了哈希表,哈希表是一种通过哈希函数,将特定的键映射到特定值得一种数据 结构, ...
随机推荐
- MVC中Control和View之间数据传递的方式
1:ViewBag和ViewData 具体区别不做讨论,本处只演示ViewData的具体示例: Controler代码:ViewData["Employee"] = emp; Vi ...
- 用java代码把docx转换成pdf文件
import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio ...
- NPM 相关
1. 官方网站 https://docs.npmjs.com/ 2. 显示Global Module安装过什么 p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font ...
- Eclipse 4.2 (Juno) 'Cannot create a server using the selected type' in Tomcat 7 - Stack Overflow.html
[太神奇了,真的可以呀] 原文:http://stackoverflow.com/questions/13423593/eclipse-4-2-juno-cannot-create-a-server- ...
- KBMMW 4.93.10 win64 一个BUG 修正
经常有人提到kbmmw 4.93.10 的64 位版本没有32位版本稳定. 经过官方确认,是delphi 编译器生成64 位代码内存偏移地址的错误. 在kbmMWGlobal.pas 中 有一个函数k ...
- HTTP权威协议笔记-4.连接管理
4.1 TCP连接 TCP为HTTP提供了一条可靠的比特传输管道,按顺序正确的传输,步骤如下: 浏览器解析主机名. 查询这个主机名的IP地址(DNS) 获得端口号. 浏览器对服务器该端口号发起连接. ...
- @MappedSuperclass注解的使用说明
转载自:http://blog.sina.com.cn/s/blog_7085382f0100uk4p.html 基于代码复用和模型分离的思想,在项目开发中使用JPA的@MappedSuperclas ...
- JS的封装
aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAl8AAADiCAYAAABwdKKfAAAYLGlDQ1BJQ0MgUHJvZmlsZQAAWIWVeQ
- 去掉IE下input的叉号
IE10下的Input Text和谷歌下面的 input search 一旦输入内容,会在最右端出现一个叉号,点击后,内容就会自动清空,看似方便,其实有些场景并不需要,需要写代码清除掉. 代码如下: ...
- SUID, SGID, SBIT
基本上SUID有这样的限制与功能: SUID权限仅对二进位程序(binaryprogram)有效,不能用在shellscript上面: 运行者对於该程序需要具有x的可运行权限: 本权限仅在运行该程序的 ...