漫谈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) 大部分动态语言的实现中都使用了哈希表,哈希表是一种通过哈希函数,将特定的键映射到特定值得一种数据 结构, ...
随机推荐
- xhprof学习笔记
一.简介 XHProf 是一个轻量级的分层性能测量分析器. 在数据收集阶段,它跟踪调用次数与测量数据,展示程序动态调用的弧线图. 它在报告.后期处理阶段计算了独占的性能度量,例如运行经过的时间.CPU ...
- python Tab自动补全命令设置
Mac/Windows下需要安装模块儿 pip install pyreadline pip install rlcompleter pip install readline 注意,需要先安装pyre ...
- Redis的持久化的两种方式drbd以及aof日志方式
redis的持久化配置: 主要包括两种方式:1.快照 2 日志 来看一下redis的rdb的配置选项和它的工作原理: save 900 1 // 表示的是900s内,有1条写入,则产生快照 save ...
- diamond专题(一)– 简介和快速使用
(转自 http://blog.csdn.net/zh_winer/article/details/50395024) 一.概况 diamond是淘宝内部使用的一个管理持久配置的系统,它的特点是简单 ...
- javascript照片墙效果
<!doctype html> <html> <head> <meta charset="UTF-8"> <title> ...
- python之路-Mysql&&ORM
1. 数据库介绍 什么是数据库? 数据库(Database)是按照数据结构来组织.存储和管理数据的仓库, 每个数据库都有一个或多个不同的API用于创建,访问,管理,搜索和复制所保存的数据. 我们也可以 ...
- oracle、mysql新增字段,字段存在则不处理
oracle: 表名:CHANNEL_TRADE_DETAIL列名:exchange_code declare v_rowcount integer; begin select count(*) in ...
- 常用前端框架Angular和React的一些认识
为什么要用AngularJs? 要了解为什么使用AngularJS首先就要接受它的思想: 首先,angularJS借助了传统MVC的架构模式(model模型 view视图 controller控制 ...
- docker 数据卷 权限
1,在运行容器的时候,给容器加特权: 示例:docker run -i -t --privileged=true -v /home/docs:/src waterchestnut/nodejs:0.1 ...
- [Docker] docker 基础学习笔记5(共6篇)
docker 配置文件的位置: centos : /etc/sysconfig/docker ubuntu: /etc/default/docker 现在比如我自己电脑上已经装好了docker,但 ...