Linux 内核 hlist 详解

2023-05-16

在Linux内核中,hlist(哈希链表)使用非常广泛。本文将对其数据结构和核心函数进行分析。

和hlist相关的数据结构有两个:hlist_head 和 hlist_node

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

结构如下图所示:


hlist_head结构体只有一个域,即first。 first指针指向该hlist链表的第一个节点。
hlist_node结构体有两个域,next 和pprev。 next指针很容易理解,它指向下个hlist_node结点,倘若该节点是链表的最后一个节点,next指向NULL。
pprev是一个二级指针, 它指向前一个节点的next指针的地址
。为什么我们需要这样一个指针呢?它的好处是什么?
在回答这个问题之前,我们先研究另一个问题:为什么哈希表的实现需要两个不同的数据结构?
哈希表的目的是为了方便快速的查找,所以哈希表中hash桶的数量通常比较大,否则“冲突”的概率会非常大,这样也就失去了哈希表的意义。如何做到既能维护一张大表,又能不使用过多的内存呢?就只能从数据结构上下功夫了。所以对于哈希表的每个hash桶,它的结构体中只存放一个指针,解决了占用空间的问题。现在又出现了另一个问题:数据结构不一致。显然,如果hlist_node采用传统的next,prev指针,对于第一个节点和后面其他节点的处理会不一致。这样并不优雅,而且效率上也有损失。
hlist_node巧妙地将pprev指向上一个节点的next指针的地址,由于hlist_head的first域指向的结点类型和hlist_node指向的下一个结点的结点类型相同,这样就解决了通用性!
如果要删除hash桶对应链表中的第一个普通结点

对应的程序代码如下:

static inline void __hlist_del(struct hlist_node *n) 
{ 
 struct hlist_node *next = n->next;//获取指向第二个普通结点的指针 
 struct hlist_node **pprev = n->pprev;//保留待删除的第一个结点的pprev域(即头结点first域的地址),此时 pprev = &first 
 *pprev = next; 
 /*
 因为pprev = &first,所以*pprev = next,相当于 first = next
 即将hash桶的头结点指针指向原来的第二个结点,如上图中的黑线1
 */
 if (next) //如果第二个结点不为空
  next->pprev = pprev;//将第二个结点的pprev域设置为头结点first域的地址,如上图中的黑线2 
}

如果要删除hash桶对应链表中的非第一个结点


对应的程序代码如下:

static inline void __hlist_del(struct hlist_node *n) 
{ 
 struct hlist_node *next = n->next;//获取指向待删除结点的下一个普通结点的指针 
 struct hlist_node **pprev = n->pprev;//获取待删除结点的pprev域 
 *pprev = next; //修改待删除结点的pprev域,逻辑上使待删除结点的前驱结点指向待删除结点的后继结点,如上图中的黑线1

 if (next) //如果待删除结点的下一个普通结点不为空
      next->pprev = pprev;//设置下一个结点的pprev域,如上图中的黑线2,保持hlist的结构 
}

可以看到删除第一个普通结点和删除非第一个普通结点的代码是一样的。
下面再来看看如果hlist_node中包含两个分别指向前驱结点和后继结点的指针

很明显删除hash桶对应链表中的非第一个普通结点,只需要如下两行代码:

n->next->prev = n->prev;
n->prev->next = n->next;

可是,如果是删除的hash桶对应链表中的第一个普通结点:
此时 n->prev->next = n->next 就会出问题,因为hash桶的表头结点没有next域
所以,明显在这种情况下删除hash桶对应链表的第一个普通结点和非第一个普通结点的代码是不一样的。
同样的情况也存在于插入操作。
附一张在hash桶头结点之后,插入第一个普通结点的图:


在遍历上,如果使用hlist_hode, list_node指针进行遍历,两者过程大致相似。

#define list_for_each(pos, head) \
        for (pos = (head)->next; prefetch(pos->next), pos != (head); \
                pos = pos->next)
 
#define hlist_for_each(pos, head) \
        for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \
             pos = pos->next)

如果使用其寄生结构的指针进行遍历,则 hlist list 也略有不同, hlist 在遍历时需要一个指向 hlist_node 的临时指针,该指针的引入,一是为了遍历,而 list 的遍历在 list_entry 的参数中实现了,更主要的目的在于判断结束,因为 hlist 最后一个节点的 next NULL ,只有 hlist_node 指向 NULL 时才算结束,而这个 NULL 不包含在任何寄生结构内,不能通过 tpos->member 的方式访问到,故临时变量 pos 的引入时必须的。

#define list_for_each_entry(pos, head, member) \
        for (pos = list_entry((head)->next, typeof(*pos), member); \
             prefetch(pos->member.next), &pos->member != (head); \
             pos = list_entry(pos->member.next, typeof(*pos), member))
 
#define hlist_for_each_entry(tpos, pos, head, member) \
        for (pos = (head)->first; \
             pos && ({ prefetch(pos->next); 1;}) && \
                ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
             pos = pos->next)

另外, list hlist 的遍历都实现了 safe 版本,因在遍历时,没有任何特别的东西来阻止对链表执行删除操作(通常在使用链表时使用锁来保护并发访问)。安全版本的遍历函数使用临时存放的方法使得检索链表时能不被删除操作所影响。

#define list_for_each_safe(pos, n, head) \
        for (pos = (head)->next, n = pos->next; pos != (head); \
                pos = n, n = pos->next)
 
#define hlist_for_each_safe(pos, n, head) \
        for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \
             pos = n)

附上linux内核中与hlist相关的完整代码:

//hash桶的头结点 
struct hlist_head { 
     struct hlist_node *first;//指向每一个hash桶的第一个结点的指针 
}; 
//hash桶的普通结点 
struct hlist_node { 
 struct hlist_node *next;//指向下一个结点的指针 
 struct hlist_node **pprev;//指向上一个结点的next指针的地址 
}; 
//以下三种方法都是初始化hash桶的头结点 
#define HLIST_HEAD_INIT { .first = NULL } 
#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL } 
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL) 

//初始化hash桶的普通结点 
static inline void INIT_HLIST_NODE(struct hlist_node *h) 
{ 
 h->next = NULL; 
 h->pprev = NULL; 
} 

//判断一个结点是否已经存在于hash桶中 
static inline int hlist_unhashed(const struct hlist_node *h) 
{ 
     return !h->pprev; 
} 

//判断一个hash桶是否为空 
static inline int hlist_empty(const struct hlist_head *h) 
{ 
     return !h->first; 
} 

static inline void __hlist_del(struct hlist_node *n) 
{ 
 struct hlist_node *next = n->next;//获取指向待删除结点的下一个结点的指针 
 struct hlist_node **pprev = n->pprev;//保留待删除结点的pprev域 
 *pprev = next;//修改待删除结点的pprev域,逻辑上使待删除结点的前驱结点指向待删除结点的后继结点 
 if (next) 
  next->pprev = pprev;//设置待删除结点的下一个结点的pprev域,保持hlist的结构 
} 

static inline void hlist_del(struct hlist_node *n) 
{ 
 __hlist_del(n);//删除结点之后,需要将其next域和pprev域设置为无用值 
 n->next = LIST_POISON1; 
 n->pprev = LIST_POISON2; 
} 

static inline void hlist_del_init(struct hlist_node *n) 
{ 
 if (!hlist_unhashed(n)) 
 { 
  __hlist_del(n); 
  INIT_HLIST_NODE(n); 
 } 
} 

//将普通结点n插入到头结点h对应的hash桶的第一个结点的位置 
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; 
} 

/* next must be != NULL */ 
//在next结点之前插入结点n,即使next结点是hash桶中的第一个结点也可以 
static inline void hlist_add_before(struct hlist_node *n, struct hlist_node *next) 
{ 
 n->pprev = next->pprev; 
 n->next = next; 
 next->pprev = &n->next; 
 *(n->pprev) = n; 
} 

//在结点n之后插入结点next 
static inline void hlist_add_after(struct hlist_node *n, struct hlist_node *next) 
{ 
 next->next = n->next; 
 n->next = next; 
 next->pprev = &n->next; 

 if(next->next) 
  next->next->pprev = &next->next; 
} 

/* 
 * Move a list from one list head to another. Fixup the pprev 
 * reference of the first entry if it exists. 
 */ 
static inline void hlist_move_list(struct hlist_head *old, struct hlist_head *new) 
{ 
 new->first = old->first; 
 if (new->first) 
  new->first->pprev = &new->first; 
 old->first = NULL; 
} 

//通过一个结构体内部一个成员的地址获取结构体的首地址 
#define hlist_entry(ptr, type, member) container_of(ptr,type,member) 

#define hlist_for_each(pos, head) \ 
 for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \ 
      pos = pos->next) 

#define hlist_for_each_safe(pos, n, head) \ 
 for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \ 
      pos = n) 

/** 
 * hlist_for_each_entry	- iterate over list of given type 
 * @tpos:	the type * to use as a loop cursor. 
 * @pos:	the &struct hlist_node to use as a loop cursor. 
 * @head:	the head for your list. 
 * @member:	the name of the hlist_node within the struct. 
 */ 
#define hlist_for_each_entry(tpos, pos, head, member)	 \ 
 for (pos = (head)->first;	 \ 
      pos && ({ prefetch(pos->next); 1;}) &&	 \ 
  ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ 
      pos = pos->next) 

/** 
 * hlist_for_each_entry_continue - iterate over a hlist continuing after current point 
 * @tpos:	the type * to use as a loop cursor. 
 * @pos:	the &struct hlist_node to use as a loop cursor. 
 * @member:	the name of the hlist_node within the struct. 
 */ 
#define hlist_for_each_entry_continue(tpos, pos, member)	 \ 
 for (pos = (pos)->next;	 \ 
      pos && ({ prefetch(pos->next); 1;}) &&	 \ 
  ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ 
      pos = pos->next) 

/** 
 * hlist_for_each_entry_from - iterate over a hlist continuing from current point 
 * @tpos:	the type * to use as a loop cursor. 
 * @pos:	the &struct hlist_node to use as a loop cursor. 
 * @member:	the name of the hlist_node within the struct. 
 */ 
#define hlist_for_each_entry_from(tpos, pos, member)	 \ 
 for (; pos && ({ prefetch(pos->next); 1;}) &&	 \ 
  ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ 
      pos = pos->next) 

/** 
 * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry 
 * @tpos:	the type * to use as a loop cursor. 
 * @pos:	the &struct hlist_node to use as a loop cursor. 
 * @n:	 another &struct hlist_node to use as temporary storage 
 * @head:	the head for your list. 
 * @member:	the name of the hlist_node within the struct. 
 */ 
#define hlist_for_each_entry_safe(tpos, pos, n, head, member) \ 
 for (pos = (head)->first;	 \ 
      pos && ({ n = pos->next; 1; }) && \ 
  ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ 
      pos = n)


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux 内核 hlist 详解 的相关文章

  • 如何查明 Ubuntu 上安装了哪个版本的 GTK+?

    我需要确定 Ubuntu 上安装了哪个版本的 GTK 男人似乎不帮忙 这个建议 https stackoverflow com a 126145 会告诉您安装了哪个 2 0 的次要版本 不同的主要版本将具有不同的包名称 因为它们可以在系统上
  • 将数组传递给函数名称冲突

    Specs GNU bash 版本 3 1 17 无法升级 Premise 我一直在摆弄数组 我想知道是否有任何方法可以让函数的本地变量与所述函数外部的数组同名 Example 在下面的示例中 我将尝试显示该问题 Working bin b
  • 怎样才能使 Windows 成为一个开箱即用的 POSIX 兼容操作系统?

    这个问题的动机是我的一个牵强的梦想 即 nix 平台上可用的许多优秀软件可以轻松移植到 Windows 微软最近对开源和开放性采取了不同的方法 所以我真的很想知道如果微软有这样的倾向 这样的事情会有多可行 我很好奇的一些更具体的事情是 是否
  • gethostbyname() 或 getnameinfo() 如何在后台工作?

    How gethostbyname or getnameinfo 在后台工作 include
  • gentoo crontab:为什么这个简单的 crontab 不起作用?

    我使用 GENTOO 发行版 crontab e 35 12 root php5 home www cron php 当我手动运行时 php5 php5 home www cron php 这有效 它向我发送了一封电子邮件 然后我检查日期
  • 停止服务时单元陷入故障状态(状态=143)[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 这是我的问题 我有 CentOS 和 java 进程在上面运行 Java进程是通过启动 停止脚本来操作的 它也创建了 java 实例的 p
  • 为 Qt 应用程序创建 Linux 安装

    我刚刚用 Qt Creator 制作了一个很棒的程序 我对自己很满意 如何将其从台式机移至笔记本电脑 那么 最好的方法是安装程序 对吗 对于 Ubuntu 这是一个 Debian 软件包 对吗 我怎么做 有人这样做过吗 他们可以分享 QT
  • 如何不断刷新屏幕并实时更新[关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我想在linux上写一个C程序 不断刷新屏幕并实时更新 例如类似于top终端中的命令 谁能指出我正确的方向 为了保持它跨终端类型的可移
  • 如何在Python中独立于语言安装(linux)获取用户桌面路径

    我找到了 如何找到用户桌面的路径 的几个问题和答案 但在我看来它们都已失效 至少我找到的那些 原因是 如果用户安装的 Linux 不是英语 他或她的桌面很可能位于除 Desktop 例如 对于瑞典语 我相信它是在 Skrivbord 谁知道
  • 在Linux中断上下文中运行用户线程

    我正在编写一些定制的应用程序 并允许更改 Linux 内核中的中断处理程序代码 我有一个用户线程正在等待中断发生 如果发生中断 那么我要做的第一件事就是执行该用户线程 有什么办法让它发挥作用吗 Thanks 创建一个字符设备 这就是内核所做
  • 如何以编程方式从Linux中的进程名称获取进程ID

    在我的项目中 我们使用 ACE 自适应通信环境 中间件来编写可在 Windows 和 Linux 上运行的独立于操作系统的代码 要求是从进程名称中获取进程 ID 由于 ACE 不支持这一点 因此我们必须使用特定于平台的宏来分离 Window
  • 如何让“grep”从文件中读取模式?

    假设有一个很大的文本文件 我只想打印与某些模式不匹配的行 显然 我可以使用egrep v patter1 pattern2 pattern3 现在 如果所有这些模式都在一个文本文件中怎么办 最好的制作方法是什么egrep从文件中读取模式 g
  • C 程序从连接到系统的 USB 设备读取数据

    我正在尝试从连接到系统 USB 端口的 USB 设备 例如随身碟 获取数据 在这里 我可以打开设备文件并读取一些随机原始数据 但我想获取像 minicom teraterm 这样的数据 请让我知道我可以使用哪些方法和库来成功完成此操作以及如
  • 如何查找连接到 AF_INET 套接字的客户端的 UID?

    有什么方法或类似的东西ucred for AF UNIX如果是AF INET插座 TCP在我的例子中 找出连接到我的套接字的客户端的UID 还有 proc net tcp但它显示了UID of the creator插座的而不是连接的cli
  • 让 MongoDB 在 Linux 上监听远程连接

    我已在 Windows 本地计算机上 上成功安装 MongoDB 作为服务 但现在我想将 MongoDb 移动到单独的服务器 所以我将 tarball 解压到网络上的虚拟服务器 运行 Linux 当我从本地计算机使用 PuTTY 连接到服务
  • 在 Linux 上的 Python 中使用受密码保护的 Excel 工作表

    问题很简单 我每周都会收到一堆受密码保护的 Excel 文件 我必须解析它们并使用 Python 将某些部分写入新文件 我得到了文件的密码 当在 Windows 上完成此操作时 处理起来很简单 我只需导入 win32com 并使用 clie
  • xsel -o 对于 OS X 等效项

    是否有一个等效的解决方案可以在 OS X 中抓取选定的文本 就像适用于 Linux 的 xsel o 一样 只需要当前的选择 这样我就可以在 shell 脚本中使用文本 干杯 埃里克 你也许可以安装xsel在 MacOS 上 更新 根据 A
  • Elasticsearch 无法写入日志文件

    我想激活 elasticsearch 的日志 当我运行 elasticsearch 二进制文件时 我意识到我在日志记录方面遇到问题 无法加载配置 这是输出 sudo usr share elasticsearch bin elasticse
  • awk 子串单个字符

    这是columns txt aaa bbb 3 ccc ddd 2 eee fff 1 3 3 g 3 hhh i jjj 3 kkk ll 3 mm nn oo 3 我可以找到第二列以 b 开头的行 awk if substr 2 1 1
  • 无法加载 JavaHL 库。- linux/eclipse

    在尝试安装 Subversion 插件时 当 Eclipse 启动时出现此错误 Failed to load JavaHL Library These are the errors that were encountered no libs

随机推荐

  • CAS6.2.x ~ 准备(1)

    前言 CAS 企业单点登录 xff0c 目前最新版本是6 2 x Apereo 的 Central Authentication Service xff0c 通常称为CAS CAS是用于web的企业多语言单点登录解决方案 xff0c 并试图
  • MySql 安装,root初始化密码设置

    MySQL下载地址 xff1a https dev mysql com downloads mysql 2 解压MySQL压缩包 将以下载的MySQL压缩包解压到自定义目录下 我的解压目录是 C mysql 8 0 12 winx64 34
  • Python游戏项目--外星人入侵(一)

    一 安装Pygame 在终端输入 xff1a pip install user pygame 二 开始游戏项目 xff08 1 xff09 创建Pygame窗口及响应用户输入 创建一个名为alien invasion py 的文件 xff0
  • SpringSecurity OAuth2 获取Token端点TokenEndpoint、Token授权TokenGranter接口 详解

    1 前言 在 授权服务器是如何实现授权的呢 xff1f 中 xff0c 我们可以了解到服务端实现授权的流程 xff0c 同时知道 xff0c 当授权端点AuthorizationEndpoint生成授权码时 xff0c 就会重定向到客户端的
  • Make文件中赋值等号的几种类型(:=,?=,=)

    今天有一位以前仅做过Android APP开发的同学突然间问我 xff0c 说Makefile中经常可以看见 xff1a 冒号等号 61 问号等号 61 和直接等号 61 这究竟有什么区别呢 xff1f 欢迎转载 xff0c 但是请注明原出
  • 支持nvidia GPU 的硬件编解码的ffmpeg编译记要

    支持nvidia GPU 的硬件编解码的ffmpeg编译记要 中间目录 xff1a out 1 x264 下载x264 stable zip unzip x264 stable zip cd x264 stable configure en
  • 软件工程学习笔记——第三周:结构化分析方法-1

    结构化分析方法的概念 结构化分析模型 结构化分析过程
  • GBK编码表

    说明 xff1a 比如第一个 34 顿号 34 的编码就是A1A2 GBK 汉字内码扩展规范 编码表 二 全国信息技术标准化技术委员会 汉字内码扩展规范 GBK Chinese Internal Code Specification 1 0
  • SSH 使用过程中,出现的问题以及解决办法

    1 ssh登陆提示 server unexpectedly closed network connection 在使用ssh登入Linux時 xff0c 卻發生了serverunexpectedly closed network conne
  • CentOS7安装vncserver

    1 关闭防火墙和selinux systemctl stop firewalld service setenforce 0 2 安装图形支持 yum groups install 34 GNOME Desktop 34 或yum group
  • Echarts tooltip加上单位并带着图例颜色

    模仿腾讯疫情地图 xff0c Y轴有个百分比 xff0c 也就是Y轴有单位 xff0c 使用JS代码如下 xff1a tooltip trigger 39 axis 39 formatter function params var relV
  • xv6调试

    窗口1作为xv6的运行窗口 make CPUS 61 1 qemu gdb 窗口2作为gdb调试窗口 gdb multiarch kernel kernel 进入gdb后执行 set confirm off set architecture
  • mit6.s081-21-Lab1/ Xv6 and Unix utilities

    sleep Implement the UNIX program sleep for xv6 your sleep should pause for a user specified number of ticks A tick is a
  • Python+scrcpy+pyminitouch实现自动化(四)——实现语音识别自动打卡机器人

    首先要去网上下载一个想要实现自动化的软件 xff0c 下载对应的apk后拖拉到虚拟器的页面即可实现自动下载 以上是对于AS打开的模拟器进行的下载安装 xff0c 由于我找不到关于x86的企业微信 xff0c 所以我就换了逍遥模拟器 xff0
  • CMU15445 lab1 - BUFFER POOL

    本文为本人完成15445 2020fall B 43 树版本 时的一些记录 xff0c 仅作为备忘录使用 TASK 1 LRU REPLACEMENT POLICY 本任务为实现一个LRU页面置换策略 xff0c 建立一个关于面向磁盘的数据
  • 医院信息管理系统(Python与MySQL数据库的连接与相关增删改查操作)

    题目意义 医院信息管理是一项琐碎 复杂而又十分细致的工作 xff0c 这关系到医院体系能否运行起来这一关乎国民健康水平的重大问题 我们只有利用好了医院中每个医生 护士的各项资源 xff0c 才能使得医院系统能够有序而条理的进行 xff0c
  • 慢速协议-Slow Protocol-LACP

    慢速协议有三种 xff0c 包括802 3ah OAM LACP协议和Marker协议 慢速协议的特点 xff1a 1 xff0c 每秒钟传输的报文不超过10帧 xff1b 2 xff0c 报文不携带vlan tag xff1b 3 xff
  • fork() && fork() || fork()

    include lt unistd h gt include lt stdio h gt int main fork fork amp amp fork fork fork sleep 100 return 0 问题是不算main这个进程自
  • list_entry()详解

    Linux内核中 xff0c 获取节点地址的函数list entry 非常常用 xff0c 由于其定义有点晦涩 xff0c 先解析如下 xff1a list entry的宏定义 xff1a define list entry ptr typ
  • Linux 内核 hlist 详解

    在Linux内核中 xff0c hlist xff08 哈希链表 xff09 使用非常广泛 本文将对其数据结构和核心函数进行分析 和hlist相关的数据结构有两个 xff1a hlist head 和 hlist node hash桶的头结