文章目录
- 一、前言
- 二、List详细分析
-
- 1.毒药指针
- 2.链表节点和初始化
-
- [2.1.链表节点结构体 `struct list_head`](#2.1.链表节点结构体
struct list_head
) - [2.2.宏 `LIST_HEAD(name)`](#2.2.宏
LIST_HEAD(name)
) - [2.3.宏 `INIT_LIST_HEAD(ptr)`](#2.3.宏
INIT_LIST_HEAD(ptr)
) - 2.4.关键设计思想
- [2.1.链表节点结构体 `struct list_head`](#2.1.链表节点结构体
- 3.链表操作函数
-
- 3.1.插入函数
-
- [3.1.1.`__list_add` 函数](#3.1.1.
__list_add
函数) - [3.1.2.`list_add` 函数](#3.1.2.
list_add
函数) - [3.1.3. `list_add_tail` 函数](#3.1.3.
list_add_tail
函数) - 3.1.4.示例代码
- [3.1.1.`__list_add` 函数](#3.1.1.
- 3.2.删除函数
-
- [3.2.1. `__list_del` 函数](#3.2.1.
__list_del
函数) - [3.2.2. `list_del` 函数](#3.2.2.
list_del
函数)
- [3.2.1. `__list_del` 函数](#3.2.1.
- 3.3.替换函数
- 3.4.移动函数
- 3.5.其他操作函数
- 4.相关宏定义
- [5.RCU 相关操作](#5.RCU 相关操作)
- 三、哈希链表详细分析
- 四、设计哲学总结
-
- [1.侵入式设计(Intrusive Design)](#1.侵入式设计(Intrusive Design))
- [2. 安全性设计](#2. 安全性设计)
- [3. 多种链表类型](#3. 多种链表类型)
- 4.完整的API生态系统
- 5.重要使用注意事项
一、前言
本文详细分析Linux 2.6.10内核的list
实现,源码位置include/linux/list.h
二、List详细分析
1.毒药指针
用于检测未初始化或已释放的链表节点的非法使用
c
#define LIST_POISON1 ((void *) 0x00100100)
#define LIST_POISON2 ((void *) 0x00200200)
标记无效的链表指针
- 当链表节点(
struct list_head
)被删除或未初始化时,内核会将其next
和prev
指针设置为LIST_POISON1
和LIST_POISON2
。 - 这些值是非法的指针值 (指向内核空间中通常不会映射的地址),如果代码错误地解引用这些指针,会触发页面错误(Page Fault),导致内核崩溃(Oops)并打印调用栈,从而帮助定位问题
防止野指针
- 如果一个链表节点被释放后,其指针未被清空,后续代码可能误用它。通过填充
LIST_POISON
,可以尽早暴露这种错误
实际使用
c
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
2.链表节点和初始化
c
struct list_head {
struct list_head *next, *prev;
};
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
#define INIT_LIST_HEAD(ptr) do { \
(ptr)->next = (ptr); (ptr)->prev = (ptr); \
} while (0)
2.1.链表节点结构体 struct list_head
c
struct list_head {
struct list_head *next, *prev;
};
- 作用:定义了一个双向链表的节点结构。
- 成员:
next
:指向下一个节点的指针(类型为struct list_head*
)。prev
:指向前一个节点的指针(类型为struct list_head*
)。
- 特点:
- 侵入式设计 :链表节点是独立的,不包含实际数据。数据结构需要嵌入
struct list_head
作为成员
- 侵入式设计 :链表节点是独立的,不包含实际数据。数据结构需要嵌入
宏 LIST_HEAD_INIT(name)
c
#define LIST_HEAD_INIT(name) { &(name), &(name) }
- 作用 :初始化一个链表头节点,使其
next
和prev
指针均指向自己(表示空链表) - 参数:
name
:链表头节点的变量名(注意这里是变量名,而非字符串)
- 展开结果:
LIST_HEAD_INIT(my_list)
会展开为{ &(my_list), &(my_list) }
- 细节:
&(name)
:取变量name
的地址(即struct list_head*
类型)- 大括号
{}
:表示结构体的初始化列表(C 语言语法) - 自引用 :
next
和prev
都指向自身,形成循环链表的初始状态
2.2.宏 LIST_HEAD(name)
c
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
-
作用 :定义并初始化一个链表头节点
-
参数:
name
:链表头节点的变量名
-
展开结果:
cLIST_HEAD(my_list)
会展开为:
cstruct list_head my_list = { &(my_list), &(my_list) };
2.3.宏 INIT_LIST_HEAD(ptr)
c
#define INIT_LIST_HEAD(ptr) do { \
(ptr)->next = (ptr); (ptr)->prev = (ptr); \
} while (0)
-
作用:动态初始化一个链表头节点(通常用于已分配的内存或指针变量)
-
参数:
ptr
:指向struct list_head
的指针(注意是指针,而非变量名)
-
展开结果:
INIT_LIST_HEAD(&my_list)
会展开为:
cdo { (&my_list)->next = (&my_list); (&my_list)->prev = (&my_list); } while (0);
-
细节:
do { ... } while (0)
技巧:- 将多条语句包装为一个整体,避免宏展开时的语法问题(例如与
if
结合使用时) - 确保宏在代码中像一条普通语句(以分号结尾)
- 将多条语句包装为一个整体,避免宏展开时的语法问题(例如与
- 指针解引用:
(ptr)->next
:访问指针ptr
指向的结构体的next
成员- 这里
ptr
可以是任意合法的struct list_head*
类型指针
2.4.关键设计思想
-
LIST_HEAD(name)
:用于静态变量(如全局变量或局部变量) -
INIT_LIST_HEAD(ptr)
:用于动态分配的链表头,如struct list_head *head = kmalloc(...); INIT_LIST_HEAD(head);
-
struct list_head
是通用的,实际数据结构通过嵌入 该结构体来支持链表操作(例如container_of
宏从节点获取宿主结构体,container_of
详细内容可参考博客 https://blog.csdn.net/weixin_51019352/article/details/151870691container_of
宏是怎么实现的 一节)
3.链表操作函数
3.1.插入函数
c
static inline void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
3.1.1.__list_add
函数
c
static inline void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
next->prev = new; // (1) 将 next 的前驱指向 new
new->next = next; // (2) 将 new 的后继指向 next
new->prev = prev; // (3) 将 new 的前驱指向 prev
prev->next = new; // (4) 将 prev 的后继指向 new
}
作用
在 prev
和 next
之间插入一个新节点 new
,并维护双向链表的正确性
示意图(插入 new
到 prev
和 next
之间)
text
before:
prev <-> next
after:
prev <-> new <-> next
3.1.2.list_add
函数
c
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next); // 在 head 之后插入 new
}
作用
将新节点 new
插入到链表头 head
之后(即作为链表的第一个实际节点)
new
:待插入的新节点。head
:链表头节点(通常是LIST_HEAD
定义的静态或动态初始化的节点)
c
__list_add(new, head, head->next)
-
prev = head
(新节点的前驱是head
) -
next = head->next
(新节点的后继是原head
的后继节点) -
效果:新节点
new
成为head
的直接后继
示意图(在 head
后插入 new
)
text
before:
head <-> node1 <-> node2
after:
head <-> new <-> node1 <-> node2
3.1.3. list_add_tail
函数
c
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head); // 在 head 之前插入 new
}
作用
将新节点 new
插入到链表头 head
之前(即作为链表的最后一个实际节点)
c
__list_add(new, head->prev, head)
-
prev = head->prev
(新节点的前驱是原链表的最后一个节点) -
next = head
(新节点的后继是head
) -
效果:新节点
new
成为head
的直接前驱
示意图(在 head
前插入 new
)
text
before:
head <-> node1 <-> node2
after:
head <-> node1 <-> node2 <-> new
3.1.4.示例代码
使用 list_add
(头插法)
c
LIST_HEAD(my_list); // 初始化链表头
struct list_head new_node;
list_add(&new_node, &my_list); // 插入到链表头部
使用 list_add_tail
(尾插法)
c
LIST_HEAD(my_list);
struct list_head new_node;
list_add_tail(&new_node, &my_list); // 插入到链表尾部
3.2.删除函数
c
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
3.2.1. __list_del
函数
c
static inline void __list_del(struct list_head *prev, struct list_head *next)
{
next->prev = prev; // 将后驱节点的 prev 指针指向前驱节点
prev->next = next; // 将前驱节点的 next 指针指向后驱节点
}
-
参数:
prev
:待删除节点的前驱节点(entry->prev
)。next
:待删除节点的后驱节点(entry->next
)。
-
关键操作:
-
next->prev = prev
:让后驱节点的prev
跳过当前节点,直接指向前驱节点。 -
prev->next = next
:让前驱节点的next
跳过当前节点,直接指向后驱节点。
-
-
结果 :
链表逻辑上移除了
prev
和next
之间的节点,但该节点的next
和prev
指针仍保留原值(可能成为野指针)
3.2.2. list_del
函数
c
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1; // 污染被删除节点的 next 指针
entry->prev = LIST_POISON2; // 污染被删除节点的 prev 指针
}
-
作用 :
从链表中删除
entry
节点,并主动污染被删除节点的指针,以检测非法访问 -
参数:
entry
:待删除的链表节点(struct list_head
类型)
-
关键操作:
-
__list_del(entry->prev, entry->next)
:调用底层函数,解除entry
与前后节点的连接。 -
entry->next = LIST_POISON1
:将entry->next
赋值为一个特殊值(LIST_POISON1
,通常是0x00100100
),用于标记节点已删除 -
entry->prev = LIST_POISON2
:将entry->prev
赋值为另一个特殊值(LIST_POISON2
,通常是0x00200200
)
-
-
目的:
- 安全性 :污染指针后,若代码误操作已删除的节点,如解引用
next
/prev
),会触发明显的错误 - 调试 :通过检查
LIST_POISON1/2
,可以快速定位链表使用后的非法访问
- 安全性 :污染指针后,若代码误操作已删除的节点,如解引用
3.3.替换函数
c
static inline void list_replace_rcu(struct list_head *old, struct list_head *new){
new->next = old->next; // 第1行:让new指向old的下一个节点
new->prev = old->prev; // 第2行:让new指向old的前一个节点
smp_wmb(); // 第3行:内存屏障,避免前两步和后两步乱序执行
new->next->prev = new; // 第4行:更新后一个节点的prev指针指向new
new->prev->next = new; //第5行:更新前一个节点的next指针指向new
}
smp_wmb()
(写内存屏障)确保:
-
第1-2行的写入操作在第4-5行之前完成
-
其他CPU看到新指针时,新节点的链接已经正确设置
3.4.移动函数
c
static inline void list_move(struct list_head *list, struct list_head *head)
{
__list_del(list->prev, list->next);
list_add(list, head);
}
static inline void list_move_tail(struct list_head *list,
struct list_head *head)
{
__list_del(list->prev, list->next);
list_add_tail(list, head);
}
3.5.其他操作函数
c
static inline int list_empty(const struct list_head *head)
{
return head->next == head;
}
static inline int list_empty_careful(const struct list_head *head)
{
struct list_head *next = head->next;
return (next == head) && (next == head->prev);
}
static inline void __list_splice(struct list_head *list,
struct list_head *head)
{
struct list_head *first = list->next;
struct list_head *last = list->prev;
struct list_head *at = head->next;
first->prev = head;
head->next = first;
last->next = at;
at->prev = last;
}
static inline void list_splice(struct list_head *list, struct list_head *head)
{
if (!list_empty(list))
__list_splice(list, head);
}
-
list_empty
:检查链表是否为空 -
list_empty_careful
:检查head
的prev
和next
是否一致,确保在并发删除时判断是真的链表为空c// list_del() 的典型实现(两步操作): static inline void list_del(struct list_head *entry) { entry->next->prev = entry->prev; // 步骤1:更新后一个节点的prev entry->prev->next = entry->next; // 步骤2:更新前一个节点的next // 在两个步骤之间,链表处于不一致状态 }
-
假设有链表:head ↔ B ↔ C,正在删除B:
- 步骤1完成后 :
head.next = B
, 但C.prev
指向head(已更新) - 此时检查 :
head->next != head->prev
,函数返回false
- 步骤1完成后 :
-
并发添加并不能检测到,因为节点添加过程中并不会马上修改原先的指针指向
-
-
list_splice
:将list
链表插入到head
链表的开头位置
4.相关宏定义
4.1.list_entry()宏
c
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
从链表节点指针获取包含它的结构体指针
container_of
宏详细介绍可参考博客 https://blog.csdn.net/weixin_51019352/article/details/151870691 container_of
宏是怎么实现的 一节
4.2.基本遍历
c
#define list_for_each(pos, head) \
for (pos = (head)->next, prefetch(pos->next); pos != (head); \
pos = pos->next, prefetch(pos->next))
-
pos = (head)->next
:将迭代指针pos
指向链表的第一个实际节点 -
prefetch(pos->next)
:预取下一个节点的数据到CPU缓存,不同CPU架构有自己的预取指令 -
检查当前节点是否为链表头(链表头作为遍历结束标志)
-
pos = pos->next
:移动到下一个节点 -
prefetch(pos->next)
:继续预取下个节点的数据 -
使用举例
c
struct list_head *pos;
list_for_each(pos, &head) {
// 循环体处理每个节点
printk("Current node: %p\n", pos);
}
4.3.安全遍历
c
#define list_for_each_safe(pos, n, head) \
for (pos = (head)->next, n = pos->next; pos != (head); \
pos = n, n = pos->next)
- 安全删除:保存下一个节点的指针,允许在遍历时删除当前节点
4.4.高级遍历
#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), \
prefetch(pos->member.next))
用于遍历链表并直接获取包含链表的结构体指针
-
pos
:指向包含链表节点的结构体的指针(遍历变量) -
head
:链表头指针 -
member
:链表节点在结构体中的成员名 -
list_entry((head)->next, typeof(*pos), member)
:(head)->next
:获取链表的第一个节点typeof(*pos)
:获取pos指向类型的类型信息,即包含链表节点的结构体的类型member
:链表节点在结构体的成员名- 作用:通过链表节点指针计算出包含它的结构体的起始地址
-
使用举例
c
struct my_data {
int value;
char name[20];
struct list_head list; // 链表节点
};
// 基础版本,my_list是链表头结点
struct list_head *pos;
list_for_each(pos, &my_list) {
struct my_data *entry = list_entry(pos, struct my_data, list);
printk("Value: %d\n", entry->value);
}
// 高级版本
struct my_data *entry;
list_for_each_entry(entry, &my_list, list) {
printk("Value: %d\n", entry->value);
}
5.RCU 相关操作
5.1.RCU插入函数
c
static inline void __list_add_rcu(struct list_head * new,
struct list_head * prev, struct list_head * next)
{
new->next = next;
new->prev = prev;
smp_wmb();
next->prev = new;
prev->next = new;
}
static inline void list_add_rcu(struct list_head *new, struct list_head *head)
{
__list_add_rcu(new, head, head->next);
}
static inline void list_add_tail_rcu(struct list_head *new,
struct list_head *head)
{
__list_add_rcu(new, head->prev, head);
}
这里和正常插入函数的代码类似,唯一不同是这里用到了smp_wmb()
,smp_wmb()
的具体实现可参考博客 https://blog.csdn.net/weixin_51019352/article/details/151968561 smp_wmb()
的实现 一节,RCU
相关知识可参考博客 https://blog.csdn.net/weixin_51019352/article/details/152009510
c
smp_wmb();
-
这是实现允许多线程并发安全访问的链表数据结构的关键
-
smp_wmb()
: 写内存屏障 (Write Memory Barrier) -
作用 :确保屏障之前 的所有写操作(
new->next = next;
和new->prev = prev;
)先于屏障 之后的所有写操作(next->prev = new;
和prev->next = new;
) -
为什么需要它?
-
编译器和CPU会对指令进行重排 以优化性能。如果没有屏障,编译器或CPU可能会先执行
next->prev = new;
和prev->next = new;
-
如果
prev->next = new;
先于new->next = next;
执行,那么会发生什么?在某个极短的瞬间,prev->next
指向了一个next
指针还未正确初始化的新节点new
。如果恰巧在此时,另一个CPU核正在遍历链表,它通过prev->next
读到了new
,然后去访问new->next
,就会读到一个内核不存在的地址,导致崩溃或错误
-
-
smp_wmb()
彻底杜绝了这种错误的发生顺序。它保证了新节点自身必须首先变得"完整"(指针全部初始化),然后才能被链入主链表从而变得"可见"
5.2.RCU删除函数
c
static inline void list_del_rcu(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->prev = LIST_POISON2;
}
从 RCU 保护的双向链表中删除 entry
节点,但不立即清理 next
指针
为什么保留 next
指针?
- RCU 遍历的需求 :
RCU 允许读者(遍历链表的线程)在无锁情况下访问链表。如果删除节点时立即清理所有指针,读者可能访问到被污染的指针
5.3.RCU遍历
c
#define list_for_each_rcu(pos, head) \
for (pos = (head)->next, prefetch(pos->next); pos != (head); \
pos = rcu_dereference(pos->next), prefetch(pos->next))
三、哈希链表详细分析
1.哈希链表结构
c
struct hlist_head {
struct hlist_node *first;
};
struct hlist_node {
struct hlist_node *next, **pprev;
};
-
hlist_head
是链表头,常用于哈希表的桶数组,比正常链表节点节省内存 -
怎么节省的内存?
c// 普通双向链表头(两个指针) struct list_head { struct list_head *next, *prev; // 16字节(64位系统) }; // 哈希链表头(一个指针) struct hlist_head { struct hlist_node *first; // 8字节(64位系统) };
-
为什么节省内存?
- 哈希表的每个桶通常有一个链表头,哈希桶数一般有很多,因此链表头数量也有很多,但每个链表的平均长度短,所以节省链表头内存很有意义
-
hlist_node
中pprev
的作用,指向前一个节点的next指针的地址 -
为什么使用
pprev
,不使用prev
?-
统一的删除操作,无需判断是否为头节点
hlist
的一个关键特征是:哈希表数组(struct hlist_head *
)中存放的是struct hlist_node *
(即第一个节点的指针)。对于第一个节点来说,它的"前一个指针"是存储在哈希表数组里的- 如果使用
prev
:在删除第一个节点时,你需要特殊处理,因为node->prev
指向的是一个struct hlist_head
结构,而不是struct hlist_node
结构。你需要判断node->prev
是另一个节点还是哈希桶的头,这会使代码变得复杂 - 如果使用
pprev
:无论要删除的节点是第一个节点(*pprev
是哈希表数组里的一个桶)还是中间节点(*pprev
是前一个节点的next
指针),删除操作*(node->pprev) = node->next;
完全一样 ,无需任何判断。因为pprev
存储的就是"指向当前节点"的那个指针的地址。这个设计非常巧妙地将两种情况的逻辑统一了
- 如果使用
-
节省内存(这是最主要的原因)
hlist
通常用于实现哈希表的拉链法。哈希表有一个很大的数组(struct hlist_head table[TABLE_SIZE]
)- 使用
prev
:struct hlist_head
也需要包含一个prev
指针来保证链表完整性,或者第一个节点的prev
指针需要指向一个虚拟节点。无论哪种方式,每个哈希桶都相当于多了一个指针的开销。对于一个有 10000 个桶的哈希表,这就浪费了 10000 * 8字节 = 80KB 的内存(在 64 位系统上) - 使用
pprev
:struct hlist_head
只需要一个单指针(first
)。struct hlist_node
的pprev
指向的是前一个节点的next
指针(对于第一个节点,就是hlist_head
的first
指针)。哈希表数组本身没有额外的内存开销。在拥有大量哈希桶的哈希表中,这种节省是非常可观的
- 使用
-
-
常用于需要高频插入/删除且内存敏感的场景(如哈希表、网络邻居表等)
2.哈希链表节点初始化
c
#define HLIST_HEAD_INIT { .first = NULL }
#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
#define INIT_HLIST_NODE(ptr) ((ptr)->next = NULL, (ptr)->pprev = NULL)
3.哈希链表删除操作
c
static inline void __hlist_del(struct hlist_node *n)
{
struct hlist_node *next = n->next;
struct hlist_node **pprev = n->pprev;
*pprev = next;
if (next)
next->pprev = pprev;
}
static inline void hlist_del(struct hlist_node *n)
{
__hlist_del(n);
n->next = LIST_POISON1;
n->pprev = LIST_POISON2;
}
从哈希链表(hlist
)中删除一个节点n
-
*pprev = next
pprev
是指向前驱节点的next
指针的指针(或指向哈希桶的头指针)- 通过解引用
pprev
,直接修改前驱节点的next
指针,使其跳过当前节点n
,指向n->next
- 如果
n
是头节点 ,pprev
指向哈希桶的头指针,此时*pprev = next
会直接修改桶的头指针。
-
next->pprev = pprev
- 如果
n
不是最后一个节点(即next != NULL
),更新next
的pprev
指针,使其指向n
的前驱指针(即pprev
) - 这样,
next
节点仍然可以通过pprev
找到链表的前驱关系
- 如果
-
不修改
n
本身的指针- 删除后,
n->next
和n->pprev
仍然保持原值,但链表结构已正确更新 - 这使得
n
可以安全地重新插入到另一个链表中
- 删除后,
4.哈希链表添加操作
c
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指针
h->first = n; // 更新链表头指针,指向新节点n
n->pprev = &h->first; // 新节点的 pprev 指向链表头的 first 指针
}
first
指向当前链表的第一个节点(如果链表为空,则first = NULL
)- 新节点
n
的next
指向原第一个节点first
(即n
成为新的头节点) - 如果
first
非空(即链表之前有节点),则修改first->pprev
,使其指向新节点的next
指针(即&n->next
) - 链表头的
first
现在指向新节点n
(即n
成为链表的第一个节点) - 新节点
n
的pprev
指向h->first
(即链表头的first
指针的地址)
5.哈希链表遍历
c
#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)
-
初始化 (
pos = (head)->first
)- 将
pos
指向链表的第一个节点(head->first
)
- 将
-
循环条件 (
pos && ({ prefetch(pos->next); 1; })
)-
pos
:检查当前节点是否非空(即链表是否遍历完毕) -
({ prefetch(pos->next); 1; })
-
-
prefetch(pos->next)
:预取pos->next
1
:确保整个条件表达式的结果为true
(因为prefetch
无返回值,而&&
需要一个布尔值)
-
({ ... })
:GCC 扩展语法,允许在表达式中执行多条语句,并将最后一条语句的执行结果返回 -
迭代 (
pos = pos->next
)- 将
pos
移动到下一个节点(pos->next
)
- 将
6.哈希链表RCU相关操作
6.1.删除操作
c
static inline void hlist_del_rcu(struct hlist_node *n)
{
__hlist_del(n);
n->pprev = LIST_POISON2;
}
见 5.2.RCU删除函数 解释
6.2.添加操作
c
static inline void hlist_add_head_rcu(struct hlist_node *n,
struct hlist_head *h)
{
struct hlist_node *first = h->first;
n->next = first;
n->pprev = &h->first;
smp_wmb();
if (first)
first->pprev = &n->next;
h->first = n;
}
这里将 n->pprev = &h->first;
提取到前面执行,是为了确保在添加节点的过程中保证不让RCU读侧读到无效数据
可参考 5.1.RCU插入函数 和 4.哈希链表添加操作 的解释
6.3.遍历操作
c
#define hlist_for_each_rcu(pos, head) \
for ((pos) = (head)->first; pos && ({ prefetch((pos)->next); 1; }); \
(pos) = rcu_dereference((pos)->next))
可参考 5.哈希链表遍历 的解释
四、设计哲学总结
1.侵入式设计(Intrusive Design)
- 链表节点嵌入到数据结构中
2. 安全性设计
- 毒药指针:检测use-after-free错误
- 安全遍历 :
_safe
版本允许在遍历时删除 - 内存屏障:保证多核并发安全性
3. 多种链表类型
- 普通双向链表 :
struct list_head
- 哈希链表 :
struct hlist_head/hlist_node
- RCU版本:无锁读取支持
4.完整的API生态系统
c
// 初始化
LIST_HEAD(), INIT_LIST_HEAD()
// 添加
list_add(), list_add_tail()
// 删除
list_del(), list_del_init()
// 遍历
list_for_each(), list_for_each_entry()
// 移动
list_move(), list_move_tail()
// 判空
list_empty(), list_empty_careful()
// 合并
list_splice(), list_splice_init()
5.重要使用注意事项
-
初始化必须:使用前必须初始化链表头
-
并发保护:多核访问时需要额外的锁机制
-
类型安全 :正确使用
list_entry
和container_of
-
内存序:RCU操作需要正确使用内存屏障