【Linux】一文搞懂 Linux 内核链表(保姆级详解)

我们学习数据结构中,除了队列、栈等,其中有一项是链表,链表分为单向链表、双向链表和循环链表。我们在使用链表中,如果自己去实现增删改查等操作,势必比较麻烦和费时间,想找个库来使用,Linux内核链表list.h本身就是一个特别好的库。

内核链表为循环链表模式,传统链表包含数据域和指针域,内核链表有别于传统链表就在节点本身不包含数据域,只包含指针域。故而可以很灵活的拓展数据结构,应用更方便灵活。

首先先解析两个宏,内核链表里面大量用到了这两个宏 offsetof 和 container_of。

一、offset_of

#define offset_of(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMBER)

1.1、作用
给定一个结构体类型 TYPE 和其成员 MEMBER,获取结构体成员相对于结构体起始位置的偏移量。

1.2、解析
参数 (TYPE, MEMBER)
TYPE是要计算偏移量的结构体的类型。
MEMBER是这个结构体中的一个成员。

类型转换 ((TYPE *)0)
(TYPE *)0 将地址0强制转换为指向结构体 TYPE 的指针。这并不会真正访问该地址,因为我们只不过是想利用指针运算的特性来计算偏移量,而不是实际访问。

成员访问 (->MEMBER)
((TYPE *)0)->MEMBER 意味着我们以类型为 TYPE 的结构体对象来访问 MEMBER。在这里,0地址被用作基地址,通过指针指向该结构体的假设位置。

取地址 (&)
&操作符用于获取 MEMBER 的地址。因为基础地址是0,所以这里的地址实际上就是成员在结构体中的偏移量。

转换为size_t
将最后的结果显式转换为 size_t 类型,以确保结果是一个无符号整数,通常用于表示内存大小或偏移量。

二、container_of

#define container_of(ptr, type, member)                        \
    ({                                                         \
        const __typeof__(((type *)0)->member) *__mptr = (ptr); \
        (type *)((char *)__mptr - offset_of(type, member));    \
    })

2.1、作用
用于从指向结构体成员的指针获取指向包含该成员的结构体的指针。

2.2、解析
宏定义

#define container_of(ptr, type, member)

此宏定义使用了三个参数:
ptr: 指向某个结构体成员的指针,对应成员变量 member。
type: 包含该成员的结构体类型。
member: 在结构体中的成员变量,对应指针 ptr。

复合表达式
({ … }):GNU C扩展语法,允许在宏中定义一个复合语句/表达式,这样可以在表达式位置使用多条语句,并且返回最后一个表达式的值。

类型推断

const typeof(((type *)0)->member) *__mptr = (ptr);

typeof:这一语法用于推断指定表达式的类型,在这里它推断的是成员变量member的类型。
((type *)0)->member:这一句和我们之前的offset_of类似,通过将0转换为结构体类型的指针并获取成员,实际上不会访问位置0,而是用于获取该成员的类型。
__mptr:是一个const类型的指针,指向member成员类型,用于确保ptr与成员类型一致。

计算结构体起始地址

(type *)((char *)__mptr - offset_of(type, member));

(char *)__mptr:将__mptr(即ptr)转换为char*,这样可以进行字节级的指针运算。
offset_of(type, member):计算成员在结构体中的偏移量。见之前对offset_of的解释,该宏通过指针运算实现。
((char *)__mptr - offset_of(type, member)):通过从ptr中减去成员相对于结构体的偏移,得到整个结构体在内存中的起始地址。
(type *):把计算得到的地址转换为type结构体类型的指针,得到指向整个结构体的指针。

总结
offset_of:对于给定的一个结构的成员,获取其成员相对于首地址的偏移。
container_of:对于给定结构成员的地址,返回其结构体指针(所有者)首地址。

三、list_head

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

要了解内核链表,就不得不提 list_head。这个结构很有意思,整个结构没有数据域,只有两个指针域。

链表初始化

内核提供两种方式来初始化链表:宏初始化和接口初始化。

宏初始化
#define LIST_HEAD_INIT(name) \
    { &(name), &(name) }

#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name)

LIST_HEAD_INIT 设计的很精妙,这个宏本身不包含任何数据类型,也就是说没有限定唯一的数据类型,这就使得整个链表足够灵活,具备通用性。

LIST_HEAD 这个宏定义了对于任意给定的结构指针,将前驱 (prev) 和后继 (next) 指针都指向自己,作为链表头指针。

宏初始化用一句话表示,如下:

struct list_head name = { &(name), &(name) }

可见,结构体 name 的指针next、prev都指向自己。

接口初始化
static inline void INIT_LIST_HEAD(struct list_head *list) {
    list->next = list;
    list->prev = list;
}

接口操作就比较直接明了,和宏实现的意图一样,直接将链表头指针的前驱 (prev) 和后继 (next) 都指向自己。

前面说到 list_head 只有指针域,没有数据域,如果只是这样就没什么意义了。所以我们需要创建一个结构体,然后此结构体包含list_head结构体,也包含其他字段,这个list_head就是结构体中的指针域,而包含的其他字段就是数据域,如下:

struct my_data_list{
	int data; // 数据域
	/* list head , 这个至关重要,后期遍历通过 container_of 解析 my_data_list 地址 */
	struct list_head list;  // 指针域
}

创建一个节点:

struct my_data_list first_data{
	.data = 1,
	/* 这里有点绕,事实上就是将first_data.list , 前驱和后继都指向自己进行初始化 */
	.list = LIST_HEAD_INIT(first_data.list), 
}

在这里插入图片描述
这里 list 的 prev 和 next 都指向 list 自己了,并且 list 属于 my_data_list 的成员。只需要遍历到 list 节点就能根据前面讲的 container_of 推导得到其宿主结构的地址,从而访问 data 值。如果有其他方法,也可访问。

分析到这里,应该逐渐明晰,为何 list_head 设计很有意思?本身不包含数据域,却能衍生出无数数据类型,不受特定的数据类型限制。

四、插入

static inline void __list_add(struct list_head *new_entry, struct list_head *prev, struct list_head *next) {
    next->prev = new_entry;
    new_entry->next = next;
    new_entry->prev = prev;
    prev->next = new_entry;
}

static inline void list_add(struct list_head *new_entry, struct list_head *head) {
    __list_add(new_entry, head, head->next);
}

static inline void list_add_tail(struct list_head *new_entry, struct list_head *head) {
    __list_add(new_entry, head->prev, head);
}

内核链表提供了相应的添加节点的接口:list_add、list_add_tail。

list_add 和 list_add_tail 最终调用的都是 __list_add 函数。

list_add 是头部插入一个节点,list_add_tail 是尾部插入一个节点。

链表的头部插入和尾部插入是指在链表的两种不同位置添加新节点的操作。下面是对这两种操作的详细解读,并使用具体实例来演示它们的含义和实现。

1、头部插入
定义:头部插入是指将新节点插入到链表的最前面,即成为新的头节点。

操作步骤
创建新节点 D。
将新节点 D 的 next 指向当前头节点 A。
更新头指针,使其指向新节点 D。
演示
原链表为:head -> A -> B -> C
头部插入 D 后的链表为:head -> D -> A -> B -> C

2、尾部插入
定义:尾部插入是指将新节点插入到链表的最后一个位置,即成为尾节点。
操作步骤
创建新节点 D。
遍历链表,直到找到最后一个节点。
将最后节点的 next 指向新节点 D。
演示
原链表为:A -> B -> C -> head
尾部插入 D 后的链表为:A -> B -> C -> D -> head

总结
头部插入将新节点添加到链表的开头,每次插入都会更新头指针,使其指向新节点。
尾部插入将新节点添加到链表的末尾,需要遍历链表找到最后的节点并更新其指针。

list_add 示例如下
【1】创建一个链表头:listHead。

LIST_HEAD(listHead);

在这里插入图片描述
【2】再创建第一个链表节点。

struct my_data_list first_data{
	.data = 1,
	/* 这里有点绕,事实上就是将first_data.list , 前驱和后继都指向自己进行初始化 */
	.list = LIST_HEAD_INIT(first_data.list), 
}

在这里插入图片描述
【3】把这个节点插入到 listHead 后。

list_add(&frist_data.list, &listHead);

在这里插入图片描述
【4】再创建、插入第二个链表节点

struct my_data_list second_data {
	.data = 2,
	.list = LIST_HEAD_INIT(second_data .list), 
}
list_add(&second_data.list, &listHead);

在这里插入图片描述
以此类推,每次插入一个新节点,都是紧靠着 header 节点,而之前插入的节点依次排序靠后,那最后一个节点则是第一次插入 header 后的那个节点。先来的节点靠后,而后来的节点靠前。

list_add_tail 的示例如下
【1】创建一个链表头:listHead。

LIST_HEAD(listHead);

在这里插入图片描述
【2】再创建第一个链表节点。

struct my_data_list first_data{
	.data = 1,
	/* 这里有点绕,事实上就是将first_data.list , 前驱和后继都指向自己进行初始化 */
	.list = LIST_HEAD_INIT(first_data.list), 
}

在这里插入图片描述
【3】插入第一个节点。

list_add(&frist_data.list, &listHead);

在这里插入图片描述
【4】创建、插入第二个节点

struct my_data_list second_data {
	.data = 2,
	.list = LIST_HEAD_INIT(second_data .list), 
}
list_add(&second_data.list, &listHead);

在这里插入图片描述

每次插入的新节点都是紧挨着 header 表尾,而插入的第一个节点排在了第一位,第二个排在了第二位。先插入的节点排在前面,后插入的节点排在后面。

五、删除

内核链表同样定义了删除节点的两个接口 list_del、list_del_init。

static inline void __list_del(struct list_head *prev, struct list_head *next) {
    next->prev = prev;
    prev->next = next;
}

static inline void __list_del_entry(struct list_head *entry) { __list_del(entry->prev, entry->next); }

static inline void list_del(struct list_head *entry) {
    __list_del(entry->prev, entry->next);
    entry->next = (struct list_head *)LIST_POISON1;
    entry->prev = (struct list_head *)LIST_POISON2;
}

static inline void list_del_init(struct list_head *entry) {
    __list_del_entry(entry);
    INIT_LIST_HEAD(entry);
}

list_del 和 list_del_init 是内核链表外部接口,__list_del 和 __list_entry 是内核链表内部节点。

__list_del 这个接口,根据prev/next 删除其节点,删除的节点必须是已知的并且 prev 和 next 不为空。
__list_del_entry 本质还是调用了__list_del 。

list_del 作用是删除双链表中的一个节点,并将节点的 prev 和 next 都指向特定位置LIST_POSITION1和LIST_POSITION2。
list_del_init 作用是删除双链表中的一个节点,并将节点的 prev 和 next 都指向自己,回到最开始创建节点前的状态。

被删除的节点,list_del 中将其 prev、next 指针分别被设为 LIST_POSITION2和LIST_POSITION1两个特值,这样设置是为了保证不在链表中的节点项不可访问,对LIST_POSITION1和LIST_POSITION2的访问都将引起页故障。与之相对应,list_del_init 函数将节点从链表中解下来之后,调用LIST_INIT_HEAD 将节点置为空链状态。

利用 list_del 和 list_del_init 接口都可以删除链表中的任意节点了,需注意,前提条件是这个节点是已知的,既在链表中真实存在,切prev,next指针都不为NULL。

list_del 和 list_del_int 示例
在这里插入图片描述

六、搬移

内核链表提供了将原本属于一个链表的节点移动到另一个链表的操作,并根据插入到新链表的位置分为两类:头部搬移和尾部搬移。搬移的本质就是删除加插入

内核链表提供了相应的搬移节点的接口:list_move、list_move_tail。

头部搬移

static inline void list_move(struct list_head *list, struct list_head *head) {
    __list_del_entry(list);
    list_add(list, head);
}

头部搬移即在一个链表中用内部函数 __list_del_entry 删除该节点,在另一个链表用 list_add 头部插入该节点。

尾部搬移

static inline void list_move_tail(struct list_head *list, struct list_head *head) {
    __list_del_entry(list);
    list_add_tail(list, head);
}

尾部搬移即在一个链表中用内部函数 __list_del_entry 删除该节点,在另一个链表用 list_add_tail 尾部插入该节点。

七、合并

内核链表还提供两组合并操作,将两条链表合并在一起,list_splice 和 list_splice_init,list_splice_tail 和 list_splice_tail_init。

static inline void __list_splice(const struct list_head *list, struct list_head *prev, struct list_head *next) {
    struct list_head *first = list->next;
    struct list_head *last = list->prev;

    first->prev = prev;
    prev->next = first;

    last->next = next;
    next->prev = last;
}

static inline void list_splice(const struct list_head *list, struct list_head *head) {
    if (!list_empty(list)) __list_splice(list, head, head->next);
}

static inline void list_splice_tail(struct list_head *list, struct list_head *head) {
    if (!list_empty(list)) __list_splice(list, head->prev, head);
}

static inline void list_splice_init(struct list_head *list, struct list_head *head) {
    if (!list_empty(list)) {
        __list_splice(list, head, head->next);
        INIT_LIST_HEAD(list);
    }
}

static inline void list_splice_tail_init(struct list_head *list, struct list_head *head) {
    if (!list_empty(list)) {
        __list_splice(list, head->prev, head);
        INIT_LIST_HEAD(list);
    }
}

list_splice 将源链表的所有节点插入到目标链表的头部,不改变源链表头指针的状态,源链表头指针仍然指向原来的节点。
list_splice_tail 将源链表的所有节点插入到目标链表的尾部,不改变源链表头指针的状态,源链表头指针仍然指向原来的节点。
list_splice_init 将源链表的所有节点插入到目标链表的头部,用 INIT_LIST_HEAD 将源链表设置为空链,源链表的头指针的 prev 和 next 都指向自己,回到最开始初始化前的状态。
list_splice_tail_init 将源链表的所有节点插入到目标链表的尾部,用 INIT_LIST_HEAD 将源链表设置为空链,源链表的头指针的 prev 和 next 都指向自己,回到最开始初始化前的状态。

用 list_splice 和 list_splice_tail 函数合并链表 list1 和 list2 时,当 list2 被挂接到 list1 之后,作为原表头指针的 list2 的 next、prev 仍然指向原来的节点,为了避免引起混乱,Linux提供了一个 list_splice_init 和 list_splice_tail_init 函数,该函数在将 list2 合并到 list1 链表的基础上,调用 INIT_LIST_HEAD 将 list2 设置为空链。

下面为合并函数的示例
下图中数据2、1、0为目标链表,数据为5、4、3为源链表,将源链表插入到目标链表中,示例如下图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

八、替换

内核链表还提供一组替换链表节点的操作。
list_replace:将新的节点替换到旧的节点上。
list_replace_init:将新的节点替换到旧的节点上。同时将旧的节点的 prev 和 next 指向自己,反初始化。

static inline void list_replace(struct list_head *old, struct list_head *new_entry) {
    new_entry->next = old->next;
    new_entry->next->prev = new_entry;
    new_entry->prev = old->prev;
    new_entry->prev->next = new_entry;
}

static inline void list_replace_init(struct list_head *old, struct list_head *new_entry) {
    list_replace(old, new_entry);
    INIT_LIST_HEAD(old);
}

九、检测

检测是否是链表最后一个节点

static inline int list_is_last(const struct list_head *list, const struct list_head *head) {
    return list->next == head;
}

list_is_last 用于检测一个节点 list 是否为链表 head 中的最后一个节点。换句话说,它检查指定的节点的下一个节点是否指向链表头,从而确定该节点是否是链表中的最后一个有效节点。

如果节点 list 是链表中的最后一个节点,则返回 true(非零值),否则返回 false(0)。

检测链表是否为空

static inline int list_empty(const struct list_head *head) { return head->next == head; }

list_empty 用于检查链表是否为空。它通过检查链表头的下一个节点是否指向链表头本身来确定链表是否没有元素。该函数是很重要的工具,用于在对链表进行操作之前验证是否有需要处理的元素,从而避免不必要的操作或访问无效内存。

如果 head 链表为空,则返回 true(非零值),否则返回 false(0)。

static inline int list_empty_careful(const struct list_head *head) {
    struct list_head *next = head->next;
    return (next == head) && (next == head->prev);
}

list_empty 和 list_empty_careful 两个函数,都是用于检查链表是否为空。list_empty 仅检查链表头 head 的 next 指针是否指向链表头自身。list_empty_careful 更严格,它需要链表头 head 的 next 和 prev 都指向链表头本身。

static inline int list_is_singular(const struct list_head *head) {
    return !list_empty(head) && (head->next == head->prev);
}

list_is_singular是Linux内核链表中的一个函数,用来检查链表是否只包含一个元素。当且仅当链表中只有一个元素,则返回 true(非零值),空链表或含有多个元素的链表,则返回 false(0)。

十、左旋转

static inline void list_rotate_left(struct list_head *head) {
    struct list_head *first;

    if (!list_empty(head)) {
        first = head->next;
        list_move_tail(first, head);
    }
}

list_rotate_left 用于将链表向左旋转。将链表的第一个元素移动到链表的末尾,这相当于将链表“左旋”一次。
list_rotate_left 函数是调用 list_move_tail 函数,由上文可知道 list_move_tail 的本质就是删除加插入,即删除头节点,再在链表尾部插入该节点。

十一、切割

static inline void __list_cut_position(struct list_head *list, struct list_head *head, struct list_head *entry) {
    struct list_head *new_first = entry->next;
    list->next = head->next;
    list->next->prev = list;
    list->prev = entry;
    entry->next = list;
    head->next = new_first;
    new_first->prev = head;
}

static inline void list_cut_position(struct list_head *list, struct list_head *head, struct list_head *entry) {
    if (list_empty(head)) return;
    if (list_is_singular(head) && (head->next != entry && head != entry)) return;
    if (entry == head)
        INIT_LIST_HEAD(list);
    else
        __list_cut_position(list, head, entry);
}

list_cut_position 用于从指定位置将一个链表切分为两个链表。这个操作会生成两个链表,一个链表包含从起始到指定节点(包含该节点)的节点,一个链表包含剩余的节点。

list:这是一个新的链表头,它将包含原链表从头到entry位置的部分。
head:这是原链表的链表头。
entry:这是指定位置参考点,原链表在这个节点之后的部分将留下。

切割的示例如下
链表数据为3、2、1,从指定位置节点2开始切割,切割后,新链表为3、2,原链表为1。
在这里插入图片描述

十二、遍历

内核链表提供了一组宏进行遍历操作,参数中 member 表示结构体中一个成员的名称,head 表示链表头。

#define list_entry(ptr, type, member) container_of(ptr, type, member)

遍历的关键就是这个 list_entry 宏。本质就是 container_of 宏。这个宏的主要作用就是获取宿主结构的指针地址。前文提到,我们是以 list 指针为节点组成的一条双链表,遍历的过程中只能得到 list 的地址,那么对于其所有者地址就是通过这个宏获取的。
ptr: 指向 struct list_head 的指针,即链表节点。
type: 链表节点所属的结构体类型。
member: struct list_head 成员在结构体中的名称。

#define list_first_entry(ptr, type, member) list_entry((ptr)->next, type, member)

list_first_entry 用于获取链表中第一个元素的指针。使用 list_first_entry 之前,需要确保链表不是空的,否则可能会解引用一个无效指针。
ptr:指向链表头部的指针 (struct list_head 类型)。
type:包含 struct list_head 成员的结构体类型。
member:结构体内 struct list_head 类型的成员名。

#define list_last_entry(ptr, type, member) list_entry((ptr)->prev, type, member)

list_last_entry 用于获取链表中最后一个元素的指针。使用 list_last_entry 前必须确认链表不为空,否则会导致内存访问错误。
ptr:指向链表头部的指针(类型为 struct list_head)。
type:包含 struct list_head 成员的结构体类型。
member:此结构体中的 struct list_head 成员的名称。

#define list_first_entry_or_null(ptr, type, member) (!list_empty(ptr) ? list_first_entry(ptr, type, member) : NULL)

list_first_entry_or_null 用于从一个链表中获取第一个元素的指针。如果链表为空,则该宏返回 NULL。
ptr: 指向链表头部的指针 (struct list_head 类型)。
type: 包含 struct list_head 成员的结构体类型。
member: 此结构体中的 struct list_head 成员的名称。
本质是调用list_empty 判断是否为空,list_empty 用于检查链表是否为空。它返回一个布尔值,链表为空时为真。如果链表不为空,使用 list_first_entry 返回第一个元素。如果链表为空,返回 NULL。

#define list_next_entry(pos, member) list_entry((pos)->member.next, __typeof__(*(pos)), member)

list_next_entry 用于在一个链表中获取给定元素的下一个元素的指针。在遍历链表时,请注意循环的结束条件,避免无限循环或访问越界。
pos: 当前链表元素的指针,指向一个包含 struct list_head 成员的结构体。
member: 结构体中的 struct list_head 成员的名称。

#define list_prev_entry(pos, member) list_entry((pos)->member.prev, __typeof__(*(pos)), member)

list_prev_entry 用于在一个链表中获取给定元素的前一个元素的指针。在遍历链表时,请注意循环的结束条件,以避免无限循环或访问越界。
pos: 当前链表元素的指针,指向一个包含 struct list_head 成员的结构体。
member: 结构体中的 struct list_head 成员的名称。

#define list_for_each(pos, head) for (pos = (head)->next; pos != (head); pos = pos->next)

list_for_each 用于遍历链表中的每一个元素。该宏假设链表已经初始化,即便没有元素,至少有一个有效的头节点。
pos: struct list_head 类型的指针,用于在循环中指示当前迭代位置。
head: 链表头,是一个 struct list_head 指针,指向链表的头部位置。

#define list_for_each_prev(pos, head) for (pos = (head)->prev; pos != (head); pos = pos->prev)

list_for_each_prev 从尾到头遍历一个链表,对链表的反向遍历,list_for_each 是对链表的正向遍历,从头到尾遍历一个链表。在使用 list_for_each_prev 之前,请确保链表已经正确初始化。即使链表为空,也需要有一个有效的头节点。
pos: 遍历时的游标,用于指向当前链表节点的 struct list_head 指针。
head: 链表头,一个 struct list_head 指针,指示循环链表的根。

#define list_for_each_safe(pos, n, head) for (pos = (head)->next, n = pos->next; pos != (head); pos = n, n = pos->next)

list_for_each_safe 用于遍历链表,能够在遍历过程中安全地移除链表的元素而不会导致遍历失败。这在链表操作中尤为常见,因为有时需要在循环中根据某种条件删除节点。

pos: 循环游标,一个 struct list_head 类型的指针,表示当前遍历到的链表节点。
n: 临时存储的 struct list_head 指针,用于在删除当前节点后,保存下一个节点的位置。
head: 链表头,一个 struct list_head 指针,指示循环链表的起点。

在每次循环中,pos 是当前位置节点,n 是下一个节点。在循环体结束时用 pos = n 更新 pos,使用 n = pos->next 更新 n。这样保持 n 总是指向 pos 的下一个节点,确保了即使 pos 被删除,n 仍然有效,使得链表的浏览既安全又稳定。

#define list_for_each_prev_safe(pos, n, head) \
    for (pos = (head)->prev, n = pos->prev; pos != (head); pos = n, n = pos->prev)

list_for_each_prev_safe 与 list_for_each_safe 不同之处,list_for_each_safe 是正向遍历,list_for_each_prev_safe 是反向遍历。

#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))

list_for_each_entry 主要功能是遍历一个双向链表(list_head)的所有元素,同时允许用户访问每个节点的具体结构体。

pos: 用于循环的指针,类型为实际节点结构体指针(如 struct my_struct *)。
head: 指向链表头节点的指针。
member: 链表节点在结构体中的 struct list_head 成员的名称。

list_for_each_entry 和 list_for_each有什么区别呢?
list_for_each:提供底层的遍历,适用于需要链表头结构或进行复杂指针操作的场景。但使用时需要手动转换节点类型,可能导致类型错误。
list_for_each_entry:针对特定节点类型的高层次遍历方式,简化代码并提高可读性。在大多数情况下,更推荐使用此宏,尤其当你直接需要访问节点中的数据时。

在实际开发中,如果需要快速和简洁地处理链表中的数据,建议使用 list_for_each_entry;而在处理更底层的链表操作时,可以选择 list_for_each。

#define list_for_each_entry_reverse(pos, head, member)                                  \
    for (pos = list_last_entry(head, __typeof__(*pos), member); &pos->member != (head); \
         pos = list_prev_entry(pos, member))

相比list_for_each_entry,list_for_each_entry_reverse 用于以反向顺序遍历Linux内核中的双向链表。

#define list_prepare_entry(pos, head, member) ((pos) ?: list_entry(head, __typeof__(*pos), member))

list_prepare_entry 首先检查 pos 是否有效,如果有效,直接返回 pos。如果 pos 是 NULL,则从链表头中获取一个有效节点。这对于在遍历之前确保有一个有效的节点是很有帮助的。

pos:用于指向当前要操作的链表节点的指针。它的类型通常是链表节点的具体类型(如 struct my_struct *)。
head:指向链表头节点的指针,通常是一个 struct list_head 类型的指针。它用于查找链表中的节点。
member:指向节点结构体中 list_head 成员的名称。这个成员用于在链表中链接节点

list_prepare_entry 为链表操作提供了一种便利的方式来确保所操作的节点有效。它使得链表操作更为安全和简便,在代码中防止因为指向 NULL 的指针引发的错误。这种宏在遍历或操作链表时非常有用,尤其是在需要对空链表或已删除节点进行处理时。

#define list_for_each_entry_continue(pos, head, member) \
    for (pos = list_next_entry(pos, member); &pos->member != (head); pos = list_next_entry(pos, member))

list_for_each_entry_continue 主要用于在链表遍历中继续从当前节点的下一个节点开始遍历,通常用于在遍历中间暂停或跳过某些节点后继续遍历。

pos:用于指向当前遍历到的链表节点的指针。该指针通常是结构体类型的指针,比如 struct my_struct *pos。
head:指向链表头节点的指针,通常是一个 struct list_head 类型。这是链表的起始点,用于判断遍历是否结束。
member:表示链表节点在其结构体中的 list_head 成员的名称。这通常是一个 struct list_head 类型的指针,允许宏在结构体中访问链接到链表的成员。

在实际使用中,可能会在遍历链表时需要跳过某些特定的节点。例如,如果当前节点满足某种条件(如需要被删除或不需要处理),可以使用 list_for_each_entry_continue 来继续向下游遍历。

#define list_for_each_entry_continue_reverse(pos, head, member) \
    for (pos = list_prev_entry(pos, member); &pos->member != (head); pos = list_prev_entry(pos, member))

相比list_for_each_entry_continue,list_for_each_entry_continue_reverse 目的是在链表遍历过程中,从当前节点的前一个节点开始继续反向遍历,通常用于在中间跳过一些节点后继续反向遍历。

#define list_for_each_entry_from(pos, head, member) for (; &pos->member != (head); pos = list_next_entry(pos, member))

list_for_each_entry_from 从指定节点开始遍历,此宏的主要作用是在链表中从一个特定的节点开始遍历,而不是从头节点开始。对于在中间某个节点之后继续遍历链表的场景,它非常有用。

pos:一个指针,指向当前遍历的链表节点。它的类型通常是指向链表节点结构体的指针。例如,如果链表节点是 struct my_struct,那么 pos 的类型通常是 struct my_struct *。
head:指向链表头节点的指针,通常是一个 struct list_head 类型。这是链表的起始点,用于判断遍历是否结束。
member:表示链表节点在其结构体中的 list_head 成员的名称。通常是 struct list_head 类型的指针,允许访问链表中节点的连接成员。

这个宏通常在需要处理链表中特定节点之后的元素时使用

#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))

list_for_each_entry_safe 允许在遍历链表时安全地删除当前节点。即使在删除当前节点的情况下,遍历的下一个节点仍然可以访问,这避免了因删除操作导致的遍历错误。

pos:一个指针,指向当前遍历到的链表节点。通常是在结构体中定义的,例如 struct my_struct *pos。
n:另一个指针,指向下一个将要遍历的节点。它用于保存当前节点的下一个节点,以确保在当前节点被删除时,仍能继续遍历链表。
head:指向链表头节点的指针,通常是一个 struct list_head 类型的指针。这个指针用于判断遍历是否到达链表的端点。
member:表示链表节点在其结构体中的 list_head 成员的名称。这通常是一个 struct list_head 类型的指针,允许宏在结构体中访问链接到链表的成员。

#define list_for_each_entry_safe_continue(pos, n, head, member)                                        \
    for (pos = list_next_entry(pos, member), n = list_next_entry(pos, member); &pos->member != (head); \
         pos = n, n = list_next_entry(n, member))

list_for_each_entry_safe_continue 的主要作用是在需要跳过或删除当前节点后,继续遍历链表。它提供了一个安全的方式来处理由于删除操作引起的指针失效问题。

pos:指向当前遍历的链表节点的指针。通常是某种结构体的指针类型,例如 struct my_struct *pos。
n:指向下一个节点的指针。在遍历过程中,它用于保存当前节点的下一个节点,以确保在删除当前节点时,遍历可以继续进行。
head:指向链表头节点的指针,类型通常是 struct list_head。这个指针用于检查遍历是否到达链表的最后。
member:表示链表节点结构体中的 list_head 成员的名称。它允许宏访问链表中节点的链接成员。

#define list_for_each_entry_safe_from(pos, n, head, member) \
    for (n = list_next_entry(pos, member); &pos->member != (head); pos = n, n = list_next_entry(n, member))

list_for_each_entry_safe_from 允许用户从一个指定的节点开始遍历链表,同时支持在遍历过程中安全地删除当前节点。遍历仍然可以继续进行,而不会导致指针悬挂或链表损坏。

pos:指向当前遍历的链表节点的指针。应为节点的实际结构体类型,如 struct my_struct *pos。
n:用于保存当前节点 pos 的下一个节点的指针。这是遍历过程中确保能够继续前进的重要部分。
head:指向链表头部的指针,通常为 struct list_head * 类型。用于判断遍历是否到达链表的起始位置。
member:链表节点在其结构体中的 list_head 成员的名称,此名称用于识别链接在链表中的成员。

#define list_for_each_entry_safe_reverse(pos, n, head, member)                                    \
    for (pos = list_last_entry(head, __typeof__(*pos), member), n = list_prev_entry(pos, member); \
         &pos->member != (head); pos = n, n = list_prev_entry(n, member))

list_for_each_entry_safe_reverse 的主要作用是在双向链表中从最后一个节点开始安全反向遍历,同时支持在遍历过程中删除当前节点。确保删除操作不会导致指针失效,允许后续的遍历继续进行。

pos:指向当前遍历的链表节点的指针,通常是指向节点结构体的类型,例如 struct my_struct *pos。
n:用于保存当前节点 pos 的前一个节点的指针,以便在当前节点被删除时,可以继续遍历。
head:指向链表头节点的指针,通常是一个 struct list_head 类型。它用于判断是否到达链表的起始位置。
member:表示链表节点中 list_head 成员的名称,允许宏访问链表中节点的连接成员。

#define list_safe_reset_next(pos, n, member) n = list_next_entry(pos, member)

list_safe_reset_next 可以确保在遍历链表时,当当前节点进行操作(特别是删除操作)时,能安全地更新下一个节点的指针。这样,即使当前节点被删除,遍历指针 n 仍然有效,不会导致遍历过程中的悬空指针或访问错误。

pos:指向当前遍历节点的指针,通常是一个指向链表节点的具体类型的指针,比如 struct my_struct *pos。
n:指向下一个节点的指针。在遍历过程中,它用于保存当前节点的下一个节点,以便在处理当前节点后继续遍历。
member:这是链表节点结构体中的 list_head 成员的名称。它被用来从节点结构体中获取链表的链接部分。

十三、写在最后

如需要示例代码,可添加作者微信,免费,私发。作者微信 “_PiaoYaoXiaoWei_”,微信名"骠姚校尉"。

关于内核链表详解总结完毕,由于时间仓库,难免疏漏,如有问题,也欢迎添加作者微信交流沟通,或让作者拉你加入 “嵌入式技术俱乐部” 微信群交流沟通。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值