详细解析Linux内核链表

目录

内核链表函数一览

1、内核链表初始化

1.1 内核链表结构体

1.2 静态初始化

1.2.1 函数原型:LIST_HEAD_INIT、LIST_HEAD

1.2.2 初始化示例

1.3 动态初始化

1.3.1 函数原型:INIT_LIST_HEAD

1.3.2 初始化示例

1.4 动态初始化与静态初始化的区别

1.4.1  内存分配时机

1.4.2 使用场景

1.4.3 灵活性

1.4.4 内存位置

1.4.5 性能上考虑

1.4.6 线程安全

2、添加节点

2.1 函数原型:list_add()、list_add_tail()

2.2 示例代码

3、删除节点

3.1 函数原型:list_del()

3.2 示例代码

4、遍历

4.1 list_for_each:简单遍历,不修改链表结构

4.1.1 函数原型

4.1.2 函数作用与注意事项

4.1.3 示例代码

4.2 list_for_each_entry:方便地访问数据结构,不修改链表

4.2.1 函数原型

4.2.2 函数作用与注意事项

4.2.3 示例代码

4.3 list_for_each_safe:允许删除节点,但需要额外步骤访问数据

4.3.1 函数原型

4.3.2 函数作用与注意事项

4.3.3 示例代码

4.4 list_for_each_entry_safe:允许访问数据和删除节点

4.4.1 函数原型

4.4.2 函数作用与注意事项

4.4.3 示例代码

5、获取

5.1 list_entry:

5.1.1 函数原型

5.1.2 函数作用

5.1.3 示例代码

5.2 container_of

5.2.1 函数原型

5.2.2 函数分析


内核链表函数一览

类别宏/函数作用
初始化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 函数分析

  1. typeof( ((type *)0)->member ):
    • 这部分创建了一个类型,该类型与结构体中指定成员的类型相同。
    • (type *)0 是一个类型转换,将 0 转换为 type * 类型。
    • ->member 然后访问这个假想结构体的成员。
    • typeof() 获取这个成员的类型。
  2. const typeof(...) *__mptr = (ptr);:
    • 创建一个指向成员类型的常量指针 __mptr,并将其初始化为传入的 ptr
    • 这一步骤主要是为了类型检查,确保传入的 ptr 与成员类型匹配。
  3. offsetof(type, member):
    • 这是一个标准 C 宏,计算成员在结构体中的偏移量。
  4. (char *)__mptr - offsetof(type,member):
    1. 将成员指针转换为 char *,然后减去偏移量。
    2. 这实际上是在计算结构体的起始地址。
  5. (type *)( ... ):
    1. 最后将计算得到的地址转换回结构体指针类型。
  • 7
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值