Linux list.h 浅析
# Linux 中的 list.h
基于内核版本5.15.158
分析,该文件位于/include/linux/list.h
,是在 Linux 中的众多数据结构中使用的双向链表结构。
原理架构
/include/linux/types.h
中定义了链表节点struct list_head
,包含了指向前后两个节点的双向指针。
1 | struct list_head { |
区别于常用的 List 节点存储节点对象地址的形式,list.h
这个双向链表的使用方式是将struct list_head
声明为节点数据结构的变量成员。这种方式的好处是不再需要单独的一个链表头对象才能访问到各个节点。当索引到某个节点时,通过成员相对节点数据结构的结构体地址的偏移来从struct list_head
还原这个节点的地址。
例如,在 Linux 的 pcb 结构struct task_struct
中就有多个struct list_head
成员,将一个 pcb 实例放在多个功能不同的链表上进行管理。
1 | struct task_struct { |
操作
list 的初始化有两种方式,一种是用LIST_HEAD
宏直接定义struct list_head
并且初始化,适用于单独使用的链表节点;另一种是使用INIT_LIST_HEAD
初始化声明好的struct list_head
,两种初始化都会将节点的双向指针指向自己。
list 提供的操作主要为add
、del
、replace
等,其功能简单如字面意思。值得注意的是有些功能有一些特别的地方,如在add
操作中
1 | static inline void __list_add(struct list_head *new, |
head->next
应该是一个多核同步的变量(即实质 volatile 的),为防止编译器优化将其放入寄存器或者 cache,使用这个宏使其这次写是直接写入内存的,保持并行场景中更严格的内存模型。
另外,在del
操作中
1 | static inline void list_del(struct list_head *entry) { |
Linux 内核为防止访问未初始化或者已删除的 entry,会将这些 entry 的两个指针指向两个非法的预设值,称为POISON
,对其进行访问会触发 page fault(dmesg 应该里面会报 page not present 的错),这样也可以区分访问非法指针的错误出现在代码的哪里。
list.h
中也有用于遍历链表的宏,可以方便地替换手写的循环,也有 lock-free 的 rcu 版本。
1 | /** |
hlist
上述均说的是 list,其实list.h
中还有一种哈希链表 hlist,它的数据结构定义如下:
1 | struct hlist_head { |
其中hlist_node::pprev
指向上一个节点的 next 指针,由于哈希链表有异质的链表头,而struct hlist_head
和struct hlist_node
在起始地址都有相同的成员struct hlist_node*
,所以这个双重指针可以做到无论节点还是链表头做到操作的统一并且节约空间。
参考资料
list.h - include/linux/list.h - Linux source code v5.15.158 - Bootlin Elixir Cross Referencer