目录
1.2.1 函数原型:LIST_HEAD_INIT、LIST_HEAD
2.1 函数原型:list_add()、list_add_tail()
4.1 list_for_each:简单遍历,不修改链表结构
4.2 list_for_each_entry:方便地访问数据结构,不修改链表
4.3 list_for_each_safe:允许删除节点,但需要额外步骤访问数据
4.4 list_for_each_entry_safe:允许访问数据和删除节点
内核链表函数一览
类别 | 宏/函数 | 作用 |
---|---|---|
初始化 | LIST_HEAD_INIT(name) | 静态初始化链表头 |
LIST_HEAD(name) | 声明并初始化链表头 | |
INIT_LIST_HEAD(ptr) | 动态初始化链表头 | |
添加 | list_add(new, head) | 在链表头后添加新节点 |
list_add_tail(new, head) | 在链表尾添加新节点 | |
删除 | list_del(entry) | 删除指定节点 |
list_del_init(entry) | 删除节点并重新初始化 | |
遍历 | list_for_each(pos, head) | 正向遍历整个链表 |
list_for_each_prev(pos, head) | 反向遍历整个链表 | |
list_for_each_safe(pos, n, head) | 安全遍历(可删除节点) | |
list_for_each_entry(pos, head, member) | 遍历获取链表数据结构 | |
list_for_each_entry_safe(pos, n, head, member) | 安全遍历获取数据结构 | |
判断 | list_empty(head) | 判断链表是否为空 |
移动 | list_move(list, head) | 将节点移到另一个链表头 |
list_move_tail(list, head) | 将节点移到另一个链表尾 | |
合并 | list_splice(list, head) | 将一个链表插入到另一个链表 |
list_splice_init(list, head) | 合并后初始化原链表 | |
获取 | list_entry(ptr, type, member) | 获取包含链表节点的结构体 |
list_first_entry(ptr, type, member) | 获取第一个入口 | |
list_last_entry(ptr, type, member) | 获取最后一个入口 | |
替换 | list_replace(old, new) | 用新节点替换旧节点 |
list_replace_init(old, new) | 替换并重新初始化旧节点 |
1、内核链表初始化
1.1 内核链表结构体
struct list_head {
struct list_head *next, *prev;
};
1.2 静态初始化
1.2.1 函数原型:LIST_HEAD_INIT、LIST_HEAD
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
1.2.2 初始化示例
LIST_HEAD(my_list);
1.3 动态初始化
1.3.1 函数原型:INIT_LIST_HEAD
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
1.3.2 初始化示例
struct list_head dynamic_list;
INIT_LIST_HEAD(&dynamic_list);
// 或者作为结构体成员
struct my_struct {
int data;
struct list_head list;
};
struct my_struct *item = kmalloc(sizeof(*item), GFP_KERNEL);
if (item) {
INIT_LIST_HEAD(&item->list);
}
1.4 动态初始化与静态初始化的区别
1.4.1 内存分配时机
静态初始化:
- 在编译时分配内存
- 内存在程序加载时就已分配
- 通常用于全局变量或静态变量
动态初始化:
- 在运行时分配内存
- 可以在程序执行的任何时候进行
- 通常用于局部变量或动态分配的内存
1.4.2 使用场景
静态初始化:
- 适用于已知链表头且在整个程序生命周期内存在的情况
- 常用于模块级别或全局链表
动态初始化:
- 适用于运行时创建的链表
- 用于函数内的局部链表或动态分配的结构体成员
1.4.3 灵活性
静态初始化:
- 更简洁,一行代码完成声明和初始化
- 不够灵活,无法在运行时决定是否创建
动态初始化:
- 更灵活,可以在条件判断后再决定是否初始化
- 可以用于任何地方,包括函数内部或结构体成员
1.4.4 内存位置
静态初始化:
- 通常位于数据段或 BSS 段
- 内存位置在程序启动时就确定
动态初始化:
- 可能在栈上(局部变量)或堆上(动态分配)
- 内存位置在运行时确定
1.4.5 性能上考虑
静态初始化:
- 静态初始化在编译时完成,不会增加运行时开销
动态初始化:
- 动态初始化虽然开销很小,但在高频率调用的场景下可能需要考虑
1.4.6 线程安全
静态初始化:
- 静态初始化的全局链表在多线程环境中需要额外的同步机制
动态初始化:
- 动态初始化的局部链表通常不需要考虑线程安全问题(除非被共享)
2、添加节点
2.1 函数原型:list_add()、list_add_tail()
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);
}
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;
WRITE_ONCE(prev->next, new);
}
2.2 示例代码
struct my_struct *new_item = kmalloc(sizeof(*new_item), GFP_KERNEL);
if (new_item) {
// 初始化新节点
INIT_LIST_HEAD(&new_item->list);
// 添加到链表头部
list_add(&new_item->list, &my_list);
}
3、删除节点
3.1 函数原型:list_del()
static inline void list_del(struct list_head *entry)
{
__list_del_entry(entry);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
WRITE_ONCE(prev->next, next);
}
#define LIST_POISON1 ((void *) 0x100 + POISON_POINTER_DELTA)
#define LIST_POISON2 ((void *) 0x200 + POISON_POINTER_DELTA)
#ifdef CONFIG_ILLEGAL_POINTER_VALUE
# define POISON_POINTER_DELTA _AC(CONFIG_ILLEGAL_POINTER_VALUE, UL)
#else
# define POISON_POINTER_DELTA 0
#endif
在上面的函数原型中,有这两行代码,他们的含义是什么呢
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
LIST_POISON1 和 LIST_POISON2 是 Linux 内核用来标记已删除链表节点的特殊值。它们增强了内存安全性,有助于检测和防止与已删除节点相关的编程错误。这是内核开发者采用的一种巧妙的防御性编程技术,有助于编写更安全、更健壮的内核代码。
3.2 示例代码
struct my_struct *item;
list_for_each_entry(item, &my_list, list) {
if (item->some_condition) {
list_del(&item->list);
kfree(item);
break;
}
}
4、遍历
4.1 list_for_each:简单遍历,不修改链表结构
4.1.1 函数原型
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
/*
@pos: struct list_head *类型,用作迭代器,指向当前遍历到的节点
@head: struct list_head *类型,指向链表头
*/
4.1.2 函数作用与注意事项
- 用于简单遍历整个链表
- 提供对 struct list_head 的直接访问
- 在遍历过程中不能安全地删除或修改节点
- 需要额外的步骤(使用list_entry)来访问实际的数据结构
4.1.3 示例代码
struct list_head *pos;
struct list_head my_list;
list_for_each(pos, &my_list) {
// 使用 pos
// 注意: 需要使用 list_entry 来获取包含的结构
}
4.2 list_for_each_entry:方便地访问数据结构,不修改链表
4.2.1 函数原型
#define list_for_each_entry(pos, head, member) \
for (pos = list_first_entry(head, typeof(*pos), member); \
&pos->member != (head); \
pos = list_next_entry(pos, member))
/*
@pos: 指向包含list_head的结构体类型的指针,用作迭代器
@head: struct list_head *类型,指向链表头
@member: 结构体中list_head成员的名称
*/
4.2.2 函数作用与注意事项
- 遍历链表并直接访问包含链表的数据结构
- 更方便,不需要使用 list_entry
- 提供了类型安全的遍历
- 在遍历过程中不能安全地删除节点
4.2.3 示例代码
struct my_struct {
int data;
struct list_head list;
};
struct my_struct *pos;
struct list_head my_list;
list_for_each_entry(pos, &my_list, list) {
// 直接使用 pos->data
}
4.3 list_for_each_safe:允许删除节点,但需要额外步骤访问数据
4.3.1 函数原型
#define list_for_each_safe(pos, n, head) \
for (pos = (head)->next, n = pos->next; pos != (head); \
pos = n, n = pos->next)
/*
@pos: struct list_head *类型,用作迭代器,指向当前遍历到的节点
@n: struct list_head *类型,用于暂存下一个节点,使得可以安全删除当前节点
@head: struct list_head *类型,指向链表头
*/
4.3.2 函数作用与注意事项
- 安全遍历链表,允许在遍历过程中删除节点
- 使用额外的指针 n 来保存下一个节点的信息
- 适用于需要在遍历过程中修改链表结构的情况
- 相比普通遍历有轻微的性能开销
4.3.3 示例代码
struct list_head *pos, *n;
struct list_head my_list;
list_for_each_safe(pos, n, &my_list) {
// 可以安全地删除 pos 指向的节点
// 例如: list_del(pos);
}
4.4 list_for_each_entry_safe:允许访问数据和删除节点
4.4.1 函数原型
#define list_for_each_entry_safe(pos, n, head, member) \
for (pos = list_first_entry(head, typeof(*pos), member), \
n = list_next_entry(pos, member); \
&pos->member != (head); \
pos = n, n = list_next_entry(n, member))
/*
@pos: struct list_head *类型,用作迭代器,指向当前遍历到的节点
@n: struct list_head *类型,用于暂存下一个节点,使得可以安全删除当前节点
@head: struct list_head *类型,指向链表头
@member: 结构体中list_head成员的名称
*/
4.4.2 函数作用与注意事项
- 安全遍历链表,同时直接访问包含链表的数据结构
- 允许在遍历过程中删除节点
- 结合了 list_for_each_entry 和 list_for_each_safe 的优点
- 在需要遍历、访问数据并可能删除节点的场景下非常有用
- 有轻微的性能开销,但提供了最大的灵活性和安全性
4.4.3 示例代码
struct my_struct {
int data;
struct list_head list;
};
struct my_struct *pos, *n;
struct list_head my_list;
list_for_each_entry_safe(pos, n, &my_list, list) {
// 可以安全地删除当前节点
// 例如:
if (pos->data == some_value) {
list_del(&pos->list);
kfree(pos);
}
}
5、获取
5.1 list_entry:
5.1.1 函数原型
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
/*
@ptr: 指向 struct list_head 的指针
@type: 包含 struct list_head 的结构体类型
@member: struct list_head 在结构体中的成员名称
*/
5.1.2 函数作用
list_entry 利用了 C 语言的一个特性:结构体成员的偏移量是固定的。它计算出 struct list_head 成员在整个结构体中的偏移,然后从这个 list_head 指针反推出整个结构体的起始地址。
5.1.3 示例代码
struct my_struct {
int id;
char name[20];
struct list_head list;
};
struct list_head *ptr;
struct my_struct *item;
// 假设 ptr 指向一个 list_head,我们想获取包含它的 my_struct
item = list_entry(ptr, struct my_struct, list);
// 现在我们可以访问 item 的其他成员
printf("ID: %d, Name: %s\n", item->id, item->name);
list_entry 经常与 list_for_each 一起使用:
struct list_head *pos;
struct my_struct *item;
struct list_head my_list;
list_for_each(pos, &my_list) {
item = list_entry(pos, struct my_struct, list);
// 使用 item
}
5.2 container_of
5.2.1 函数原型
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
/*
@ptr: 指向 struct list_head 的指针
@type: 包含 struct list_head 的结构体类型
@member: struct list_head 在结构体中的成员名称
*/
5.2.2 函数分析
typeof( ((type *)0)->member )
:
- 这部分创建了一个类型,该类型与结构体中指定成员的类型相同。
(type *)0
是一个类型转换,将 0 转换为type *
类型。->member
然后访问这个假想结构体的成员。typeof()
获取这个成员的类型。const typeof(...) *__mptr = (ptr);
:
- 创建一个指向成员类型的常量指针
__mptr
,并将其初始化为传入的ptr
。- 这一步骤主要是为了类型检查,确保传入的
ptr
与成员类型匹配。offsetof(type, member)
:
- 这是一个标准 C 宏,计算成员在结构体中的偏移量。
(char *)__mptr - offsetof(type,member)
:
- 将成员指针转换为
char *
,然后减去偏移量。- 这实际上是在计算结构体的起始地址。
(type *)( ... )
:
- 最后将计算得到的地址转换回结构体指针类型。