Linux内核 hlist_head/hlist_node结构解析

linux内核里面的双向循环链表和哈希链表有什么不同呢?1、双向循环链表是循环的,哈希链表不是循环的 2、双向循环链表不区分头结点和数据结点,都用list_head表示,而哈希链表区分头结点(hlist_head)和数据结点(hlist_node)。与哈希链表有关的两个数据结构如下:

struct hlist_head {
    struct hlist_node *first;  //指向每一个hash桶的第一个结点的指针
};
struct hlist_node {
    struct hlist_node *next;   //指向下一个结点的指针
    struct hlist_node **pprev; //指向上一个结点的next指针的指针
};

int fz_divisor;

表示散列表fz_hash的容量,以及散列表桶的数目。

这个数据结构与一般的hash-list数据结构定义有以下的区别:

1) 首先,hash的头节点仅存放一个指针,也就是first指针,指向的是list的头结点,没有tail指针也就是指向list尾节点的指针,这样的考虑是为了节省空间——尤其在hash bucket很大的情况下可以节省一半的指针空间.

2) list的节点有两个指针,但是需要注意的是pprev是指针的指针,它指向的是前一个节点的next指针;其中首元素的pprev指向链表头的fist字段,末元素的next为NULL. (见下图).

1、哈希链表为什么要区分头结点和数据结点?

头结点和数据结点如果都使用list_head的话,那岂不是更容易实现。内核list.h中描述得很明白:

/*
 * Double linked lists with a single pointer list head.
 * Mostly useful for hash tables where the two pointer list head is
 * too wasteful.
 * You lose the ability to access the tail in O(1).
 */

意思是说这种双向链表的头结点只有一个指针成员(即struct hlist_node *first),它主要使用在哈希表中。因为哈希表会有很多表项,每个表项如果使用list_head这样含有两个指针成员的数据结构的话,会造成内存空间的浪费。所以,为了尽可能的减少内存空间的浪费,就要使数据结构变得稍微复杂一些,鱼和熊掌不可兼得啊。

2、hlist_node的pprev成员为什么是struct hlist_node **类型的?

如果hlist_node的定义是下面这样的话

struct hlist_node {
    struct hlist_node *next;
    struct hlist_node *prev;
};

第一个数据结点的prev成员应该指向头结点,但是因为prev成员指向的是hlist_node的数据类型的指针,而头结点的数据类型是hlist_head,所以无法实现。为了解决这样的问题,才有了下面的hlist_node的定义

struct hlist_node {
    struct hlist_node *next;
    struct hlist_node **pprev;
};

在上图中,第一个数据结点的pprev成员(数据类型struct hlist_node **)指向头结点的first成员(数据类型struct hlist_node *),第二个数据结点的pprev成员(数据类型struct hlist_node **)指向第一个数据结点的next成员(数据类型struct hlist_node *),从而对于第一个数据结点的操作和非第一个数据结点的操作就没有区别了,都统一起来了。这样就不用对第一个数据结点进行特殊处理了,为编写代码带来了极大的好处,这种设计有点小艺术。

其他的hlist的初始化、插入、删除、遍历请自行参阅list.h源文件,源文件是最好的老师。

现在疑问来了:为什么pprev不是prev也就是一个指针,用于简单的指向list的前一个指针呢?这样即使对于first而言,它可以将prev指针指向list的尾结点.

主要是基于以下几个考虑:
1) hash-list中的list一般元素不多(如果太多了一般是设计出现了问题),即使遍历也不需要太大的代价,同时需要得到尾结点的需求也不多.

2) 如果对于一般节点而言,prev指向的是前一个指针,而对于first也就是hash的第一个元素而言prev指向的是list的尾结点,那么在删除一个元素的时候还需要判断该节点是不是first节点进行处理.而在hlist提供的删除节点的API中,并没有带上hlist_head这个参数,因此做这个判断存在难度.

3) 以上两点说明了为什么不使用prev,现在来说明为什么需要的是pprev,也就是一个指向指针的指针来保存前一个节点的next指针--因为这样做即使在删除的节点是first节点时也可以通过*pprev = next;直接修改指针的指向.来看删除一个节点和修改list头结点的两个API:

static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
 {
     struct hlist_node *first = h->first;
     n->next = first;
     if (first)
         first->pprev = &n->next;
     h->first = n;
     n->pprev = &h->first; //此时n是hash的first指针,因此它的pprev指向的是hash的first指针的地址
}

static inline void __hlist_del(struct hlist_node *n)
 {
     struct hlist_node *next = n->next;
     struct hlist_node **pprev = n->pprev;
     *pprev = next; // pprev指向的是前一个节点的next指针,而当该节点是first节点时指向自己,因此两种情况下不论该节点是一般的节点还是头结点都可以通过这个操作删除掉所需删除的节点
    if (next)
         next->pprev = pprev;
 }

原文链接:linux内核list分析三:哈希链表 | 周子江  https://zhouzijiang.github.io/2015/11/08/linux%E5%86%85%E6%A0%B8list%E5%88%86%E6%9E%90%E4%B8%89-%E5%93%88%E5%B8%8C%E9%93%BE%E8%A1%A8/

Linux内核 hlist_head/hlist_node结构解析 - tao546377318的博客 - CSDN博客  https://blog.csdn.net/tao546377318/article/details/60772209

原文地址:https://www.cnblogs.com/tongongV/p/11038991.html

时间: 2024-10-07 06:55:42

Linux内核 hlist_head/hlist_node结构解析的相关文章

【驱动】网卡驱动·linux内核网络分层结构

原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://liucw.blog.51cto.com/6751239/1221140 Preface   Linux内核对网络驱动程序使用统一的接口,并且对于网络设备采用面向对象的思想设计. Linux内核采用分层结构处理网络数据包.分层结构与网络协议的结构匹配,既能简化数据包处理流程,又便于扩展和维护. 内核网络结构 在Linux内核中,对网络部分按照网络协议层.网络设备层.设备驱动功能层

Linux 网络设备驱动开发(一) —— linux内核网络分层结构

Preface Linux内核对网络驱动程序使用统一的接口,并且对于网络设备采用面向对象的思想设计. Linux内核采用分层结构处理网络数据包.分层结构与网络协议的结构匹配,既能简化数据包处理流程,又便于扩展和维护. 一.内核网络结构 在Linux内核中,对网络部分按照网络协议层.网络设备层.设备驱动功能层和网络媒介层的分层体系设计. 网络驱动功能层主要通过网络驱动程序实现. 在Linux内核,所有的网络设备都被抽象为一个接口处理,该接口提供了所有的网络操作. net_device结构表示网络设

Linux内核源代码的结构(转)

源代码所有在目录:/usr/src/linux (大部分linux发行版本中)  init 内核初始化代码  kernel 内核核心部分:进程.定时.程序执行.信号.模块...  mm 内存处理  arch 平台相关代码  i386 IBM的PC体系结构  kernel 内核核心部分  mm 内存管理  math-emu 浮点单元软件仿真  lib 硬件相关工具函数  boot 引导程序  compressed 压缩内核处理  tools 生成压缩内核映像的程序  alpha 康柏的Alpha体

如何切入 Linux 内核源代码

Makefile不是Make Love 从前在学校,混了四年,没有学到任何东西,每天就是逃课,上网,玩游戏,睡觉.毕业的时候,人家跟我说Makefile我完全不知,但是一说Make Love我就来劲了,现在想来依然觉得丢人. 毫不夸张地说,Kconfig和Makefile是我们浏览内核代码时最为依仗的两个文件.基本上,Linux内核中每一个目录下边都会有一个 Kconfig文件和一个Makefile文件.对于一个希望能够在Linux内核的汪洋代码里看到一丝曙光的人来说,将它们放在怎么重要的地位都

Linux内核经典书籍

1.<Linux内核设计与实现> 本书重在原理.适合入门的最佳图书.作者是为2.6内核加入了抢占的人,对调度部分非常精通,而调度是整个系统的核心,因此本书是很权威的. 2.<深入理解Linux内核> 此书比上一本多了些细节.是Linux内核黑客在推荐图书时的首选.写的比较简单易懂,适合刚刚接触LINUX内核的.此书图表很多,形象地给出了关键数据结构的定义,与<Linux内核源代码情景分析>相比,本书内容紧凑,不会一个问题讲解动辄上百页,有提纲挈领的功用,但是深度上要逊于

linux内核的组成,王明学learn

linux内核的组成 一.linux内核源代码目录结构 arch: 包含和硬件体系结构相关的代码, 每种平台占一个相应的目录, 如 i386.ARM.PowerPC.MIPS 等. block:块设备驱动程序 I/O 调度. crypto:常用加密和散列算法(如 AES.SHA 等) ,还有一些压缩和 CRC 校验算法. Documentation:内核各部分的通用解释和注释. drivers:设备驱动程序,每个不同的驱动占用一个子目录,如 char.block.net.mtd .i2c 等.

Linux内核学习方法

Makefile不是Make Love 从前在学校,混了四年,没有学到任何东西,每天就是逃课,上网,玩游戏,睡觉.毕业的时候,人家跟我说Makefile我完全不知,但是一说Make Love我就来劲了,现在想来依然觉得丢人. 毫不夸张地说,Kconfig和Makefile是我们浏览内核代码时最为依仗的两个文件.基本上,Linux内核中每一个目录下边都会有一个Kconfig文件和一个Makefile文件.对于一个希望能够在Linux内核的汪洋代码里看到一丝曙光的人来说,将它们放在怎么重要的地位都不

Linux内核编译过程分析

http://pan.baidu.com/s/1ntqLA7v 其中是我总结生成的一些文档,以便于理解当我们输入make uImage后,系统是怎么一步一步生成uImage的,我采用的是逆向分析的方法. Linux内核的编译结构还是挺复杂的,我分析的还不到位,文档还有欠缺. 下面是缩略图,在共享的目录中有一个swf的文件,可以用浏览器打开,然后就可以动态的阅读了,同时还提供了通过网页.PDF以及word阅读的文件. Linux内核编译过程分析,布布扣,bubuko.com

初识Linux内核

Linux诞生于1991年,出自LinusTorvalds. POSIX 表示可移植操作系统接口(Portable Operating System Interface).POSIX是在Uni标准化过程中出现的产物.POSIX 1003.1标准定义了一个最小的Unix操作系统接口任何操作系统只有符合这一标准,才有可能运行Unix程序. GNU 是 GNU Is Not Unix 的递归缩写,是自由软件基金会的一个项目.GNU 项目产品包括 emacs 编辑器.著名的GNU C 和 Gcc编译器等