哈希散列表hlist_head - linux内核经典实例

news2024/11/19 9:22:13

hlist_headhlist_node用于散列表,分别表示列表头(数组中的一项)和列表头所在双向链表中的某项,两者结构如下:

include/linux/types.h(line 190)

struct hlist_head {
	struct hlist_node *first;
};

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

在内核中的普通双向链表基本上都是通过list_head实现的:

include/linux/types.h(line 186)

struct list_head {
	struct list_head *next, *prev;
};

list_head很好理解,但是hlist_headhlist_node为何要这样设计呢?

先看下hlist_headhlist_node的示意图:

hash_table为散列表(数组),其中的元素类型为struct hlist_head。以hlist_head为链表头的链表,其中的节点hash值是相同的(也叫冲突)。first指针指向链表中的节点①,然后节点①的pprev指针指向hlist_head中的first,节点①的next指针指向节点②。以此类推。

hash_table的列表头仅存放一个指针,也就是first指针,指向的是对应链表的头结点,没有tail指针,也就是指向链表尾节点的指针,这样的考虑是为了节省空间——尤其在hash bucket(数组size)很大的情况下可以节省一半的指针空间。

为什么pprev是一个指向指针的指针呢?按照这个设计,我们如果想要得到尾节点,必须遍历整个链表,可如果是一个指向节点的指针,那么头结点现在的pprev便可以直接指向尾节点,也就是list_head的做法。

对于散列表来说,一般发生冲突的情况并不多(除非hash设计出现了问题),所以一个链表中的元素数量比较有限,遍历的劣势基本可以忽略。

在删除链表头结点的时候,pprev这个设计无需判断删除的节点是否为头结点。如果是普通双向链表的设计,那么删除头结点之后,hlist_head中的first指针需要指向新的头结点。通过下面2个函数来加深理解:

include/linux/list.h(line 669)

static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
{
	struct hlist_node *first = h->first;
	n->next = first; //新节点的next指针指向原头结点
	if (first) 
		first->pprev = &n->next;//原头结点的pprev指向新节点的next字段
	WRITE_ONCE(h->first, n);//first指针指向新的节点(更换了头结点)
	n->pprev = &h->first;//此时n是链表的头结点,将它的pprev指向list_head的first字段
}

include/linux/list.h(line 644)

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

相关API

API说明
HLIST_HEAD_INIT静态初始化hlist_head
HLIST_HEAD静态初始化hlist_head
INIT_HLIST_HEAD动态初始化hlist_head
INIT_HLIST_NODE动态初始化hlist_node
hlist_unhashed判断hlist_node是否添加到hash链表中
hlist_empty判断hash链表是否为空
hlist_del在hash链表中删除一个节点
hlist_del_init在hash链表中删除一个节点
hlist_add_head在hash链表头添加一个节点
hlist_add_before在指定节点之前添加一个节点
hlist_add_behind在指定节点之后添加一个节点
hlist_add_fake
hlist_fake
hlist_is_singular_node判断hlist是否只有一个节点
hlist_move_list将一个hash链表从一个hlist_head移动到另外一个hlist_head中
hlist_entry根据hlist_node找到其外层结构体
hlist_entry_safe同上
hlist_for_each遍历hash链表
hlist_for_each_safe同上
hlist_for_each_entry遍历hash链表
hlist_for_each_entry_safe同上
hlist_for_each_entry_continue从当前节点之后遍历hash链表
hlist_for_each_entry_from从当前节点开始遍历hash链表

程序示例

写一个测试模块,验证一下各个API

模块代码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/list.h>
struct node {
	int val;
	struct hlist_node list;
};
static int __init hlist_test_init(void)
{
	struct hlist_head  head;
	struct node a, b, c, d, e;
	struct node *pos;
	struct hlist_node *p;
	printk(KERN_ALERT "[Hello] hlist_test \n");
	INIT_HLIST_HEAD(&head); //初始化链表头
	a.val = 1;
	b.val = 2;
	c.val = 3;
	d.val = 4;
	e.val = 5;
	hlist_add_head(&a.list, &head); //添加节点
	hlist_add_head(&b.list, &head);
	hlist_add_head(&c.list, &head);
	printk(KERN_ALERT "-------------------------------------- \n");
	//遍历链表,打印结果 方法1
	hlist_for_each_entry(pos, &head, list) {
		printk(KERN_ALERT "node.val = %d\n", pos->val);
	} // print 3 2 1
	printk(KERN_ALERT "-------------------------------------- \n");
	// 遍历链表,打印结果 方法2
	hlist_for_each(p, &head) {
		pos = hlist_entry(p, struct node, list);
		printk(KERN_ALERT "node.val = %d\n", pos->val);
	} // print 3 2 1
	printk(KERN_ALERT "-------------------------------------- \n");
	hlist_del_init(&b.list); // 删除中间节点
	hlist_for_each_entry(pos, &head, list) {
		printk(KERN_ALERT "node.val = %d\n", pos->val);
	} // print 3 1
	printk(KERN_ALERT "-------------------------------------- \n");
	hlist_add_before(&d.list, &a.list); //在最后一个节点之前添加新节点
	hlist_for_each_entry(pos, &head, list) {
		printk(KERN_ALERT "node.val = %d\n", pos->val);
	} // print 3 4 1
	printk(KERN_ALERT "-------------------------------------- \n");
	hlist_add_behind(&e.list, &a.list);//在最后一个节点之后添加新节点
	hlist_for_each_entry(pos, &head, list) {
		printk(KERN_ALERT "node.val = %d\n", pos->val);
	} // print 3 4 1 5
	return 0;
}
static void __exit hlist_test_exit(void)
{
	printk(KERN_ALERT "[Goodbye] hlist_test\n");
}
module_init(hlist_test_init);
module_exit(hlist_test_exit);
MODULE_LICENSE("GPL");

执行结果

[  944.056943] [Hello] hlist_test 
[  944.056947] -------------------------------------- 
[  944.056948] node.val = 3
[  944.056949] node.val = 2
[  944.056950] node.val = 1
[  944.056951] -------------------------------------- 
[  944.056952] node.val = 3
[  944.056953] node.val = 2
[  944.056954] node.val = 1
[  944.056955] -------------------------------------- 
[  944.056956] node.val = 3
[  944.056957] node.val = 1
[  944.056958] -------------------------------------- 
[  944.056959] node.val = 3
[  944.056960] node.val = 4
[  944.056961] node.val = 1
[  944.056962] -------------------------------------- 
[  944.056963] node.val = 3
[  944.056964] node.val = 4
[  944.056965] node.val = 1
[  944.056965] node.val = 5

其他

内核中用hlist来实现 hash table,在内核上一般有如下的hash table

# dmesg | grep "hash table entries" 
 

[    0.000000] PV qspinlock hash table entries: 256 (order: 0, 4096 bytes)
[    0.000000] PID hash table entries: 4096 (order: 3, 32768 bytes)
[    0.294869] Dentry cache hash table entries: 524288 (order: 10, 4194304 bytes)
[    0.296328] Inode-cache hash table entries: 262144 (order: 9, 2097152 bytes)
[    0.296589] Mount-cache hash table entries: 8192 (order: 4, 65536 bytes)
[    0.296595] Mountpoint-cache hash table entries: 8192 (order: 4, 65536 bytes)
[    0.614525] TCP established hash table entries: 32768 (order: 6, 262144 bytes)
[    0.614769] TCP bind hash table entries: 32768 (order: 9, 2621440 bytes)
[    0.616607] UDP hash table entries: 2048 (order: 6, 393216 bytes)
[    0.616794] UDP-Lite hash table entries: 2048 (order: 6, 393216 bytes)
[    1.053747] futex hash table entries: 1024 (order: 5, 131072 bytes)
[    1.079062] Dquot-cache hash table entries: 512 (order 0, 4096 bytes)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/29514.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

护眼灯真的可以保护眼睛吗?2022双十二选哪款护眼灯对孩子眼睛好

传统的台灯只是单一色光&#xff0c;无法调节台灯的照度和色温&#xff0c;长时间使用不但不可以护眼&#xff0c;而且还会导致近视、散光等各种问题的发生。现在的护眼台灯大多都是使用led灯珠作为发光源&#xff0c;不但本身比较高效节能&#xff0c;而且光线可调控&#xff…

react--redux

此篇文章非学习使用&#xff0c;学习勿入 redux 文档&#xff1a; http://www.redux.org.cn 用于做状态管理的js库 集中管理react中多个组件共享的状态 安装&#xff1a; cnpm i redux 给形参赋值&#xff0c;表示形参的默认值 错误&#xff1a; 对象不能作为一个dom元素…

NPDP认证|制造业产品经理日常工作必备技能,快来学习提升吧!

不同阶段的产品经理对技能的掌握程度要求不同&#xff0c;侧重点也不同&#xff0c;一般包括需求分析、数据分析、竞品分析、商业分析、行业分析、需求收集、产品设计、版本管理、用户调研等。这些技能&#xff0c;是我们必须要掌握的专业技能。 比如&#xff1a;对于刚入行的…

异常检测算法分类总结(含常用开源数据集)

作者&#xff1a;云智慧算法工程师 Chris Hu 异常检测是识别与正常数据不同的数据&#xff0c;与预期行为差异大的数据。本文详细介绍了异常检测的应用领域以及总结梳理了异常检测的算法模型分类。文章最后更是介绍了常用的异常算法数据集。 异常的概念与类型 目前异常检测主…

硝酸根离子深度去除树脂

普通的阴离子交换树脂对阴离子的交换次序是&#xff1a;SO42-&#xff1e;NO3-&#xff1e;HCO3-&#xff0c;对硝酸盐没有选择性&#xff0c;优先交换水中硫酸根&#xff0c;造成树脂再生频繁&#xff0c;产水中氯离子含量增高&#xff0c;出水水质稳定性差&#xff0c;树脂交…

[注塑]各种进胶方式优缺点分析

[注塑]各种进胶方式优缺点分析1.直接进胶2.测胶口3.搭接式浇口4.扇形浇口5.潜胶6.弧线浇口7.针形浇口结构设计的时&#xff0c;分析浇口的进胶方式尤为重要&#xff0c;为了简便我们的设计&#xff0c;常常需要将一些常用的标准形式&#xff0c;以下是我们常见的一些浇口形式。…

死磕sparkSQL源码之TreeNode

InternalRow体系 学习TreeNode之前&#xff0c;我们先了解下InternalRow。 对于我们一般接触到的数据库关系表来说&#xff0c;我们对于数据库中的数据操作都是按照“行”为单位的。在spark sql内部实现中&#xff0c;InternalRow是用来表示这一行行数据的类。看下源码中的解…

Spring Cloud(十二):Spring Cloud Security

主要内容 Spring Security 模块使用设置用户名密码基于内存基于UserDetailsService 接口基于配置类WebSecurityConfigurerAdapter基于DB 用户-角色-权限自定义登录页面登录认证流程自定义成功、自定义失败会话管理&#xff08;Session)会话控制会话超时会话并发控制集群sessio…

【Webpack】webpack的基础使用详细总结 下(建议收藏)

1- 前言 昨天已经介绍了weback的基础使用了&#xff0c;详细总结看这边博客&#xff01;&#xff01;&#xff01; 【Webpack】webpack的基础使用详细总结 上&#xff08;建议收藏&#xff09; 今天来总结一下剩余的常用 &#xff01;&#xff01;&#xff01;&#xff01; …

微信抽奖活动有什么作用_分享微信抽奖小程序开发的好处

在H5游戏中&#xff0c;抽奖是最受消费者喜爱的模式之一。将H5微信抽奖活动结合到营销中&#xff0c;可以带来意想不到的效果&#xff0c;带流量和曝光率&#xff0c;所以许多企业也会在做活动时添加上不同类型的H5微信抽奖活动。 那么&#xff0c;新手怎么搭建微信抽奖活动&am…

01背包、完全背包、多重背包、分组背包总结

文章目录一、01背包问题二、完全背包问题三、多重背包问题四、分组背包一、01背包问题 n个物品&#xff0c;每个物品的重量是wiw_iwi​&#xff0c;价值是viv_ivi​&#xff0c;背包的容量是mmm 若每个物品最多只能装一个&#xff0c;且不能超过背包容量&#xff0c;则背包的最…

【ABAP】SAP发送消息至RabbitMQ

SAP发送消息至RabbitMQ ——以下关于RabbitMQ的内容大致转载于朱忠华老师的《RabbitMQ实战指南》一书 【基础知识】 消息队列中间件(Message Queue Middleware,即MQ)也可以称之为消息队列或者消息中间件,是指利用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数…

面试官: B 树和 B+ 树有什么区别?

问各位小可爱一个问题&#xff1a;MySQL 中 B 树和 B 树的区别&#xff1f; 请自己先思考5秒钟&#xff0c;看看是否已经了然如胸&#xff1f; 好啦&#xff0c;时间到&#xff01; B 树和 B 树是两种数据结构&#xff0c;构建了磁盘中的高速索引结构&#xff0c;因此不仅 …

上海亚商投顾:沪指窄幅震荡 “中字头”概念股又暴涨

上海亚商投顾前言&#xff1a;无惧大盘大跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪沪指今日窄幅震荡&#xff0c;深成指、创业板指盘中跌超1%&#xff0c;午后探底回升一度翻红。光伏、储能等赛道午后…

[Spring Cloud] GateWay自定义过滤器/结合Nacos服务注册中心

✨✨个人主页:沫洺的主页 &#x1f4da;&#x1f4da;系列专栏: &#x1f4d6; JavaWeb专栏&#x1f4d6; JavaSE专栏 &#x1f4d6; Java基础专栏&#x1f4d6;vue3专栏 &#x1f4d6;MyBatis专栏&#x1f4d6;Spring专栏&#x1f4d6;SpringMVC专栏&#x1f4d6;SpringBoot专…

DocuWare Workflow Manager(工作流管理器)

DocuWare Workflow Manager 公司是按流程运转的。销售、人力资源、财务等部门需要流畅、可靠的信息传输&#xff0c;以便在正确的时间做出正确的决策。订单管理、员工入职和发票审批等流程可以根据您的精确需求进行设计和自动化&#xff0c;避免时间浪费。 适用于复杂业务的简…

Mysql数据库相关面试题

1.关系型和非关系型数据库的区别是什么? 关系型和非关系型数据库的主要差异是数据存储的方式,关系型数据库天然就是表格存储,因此存储在数据表的行和列中,数据表可以彼此关联协作存储,很容易提取数据. 优点: 易于维护:都是使用表结构,格式一致,使用方便:sql语言通用,可以用于复…

MyBatis逆向工程和分页插件

1、分页插件 MyBatis 通过提供插件机制&#xff0c;让我们可以根据自己的需要去增强MyBatis 的功能。需要注意的是&#xff0c;如果没有完全理解MyBatis 的运行原理和插件的工作方式&#xff0c;最好不要使用插件&#xff0c; 因为它会改变系底层的工作逻辑&#xff0c;给系统带…

2022年全国职业院校技能大赛:网络系统管理项目-模块B--Windows样题7

初始化环境1.默认账号及默认密码 Username: Administrator Password: ChinaSkill22! Username: demo Password: ChinaSkill22! 注:若非特别指定,所有账号的密码均为 ChinaSkill22! 项目任务描述你作为技术工程师,被指派去构建一个公司的内部网络,要为员工提供便捷、安…

超算云平台在线功能Q-Flow、Q-Studio V2.1版本升级,web端在线建模+DFT计算

建模DFT计算还可以这么玩&#xff1f; Q-Flow&#xff08;在线可视化提交任务功能&#xff09;以及 Q-Studio&#xff08;在线建模功能&#xff09;依托Mcloud平台免费向用户开放使用。告别Linux编辑代码提交任务的模式&#xff0c;Q-Flow可在浏览器里通过拖拽图形化的第一性原…