因为很多的原因,我们在实际项目的代码中,很多地方都用到了源自linux kernel的list。也因为一些使用习惯、团队风格等问题,我们在移植的过程中对其进行了一定的改进。但基本框架和基本功能都没有做任何的改变。正好书中很多地方也是用到了linux kernel list,所以我们有必要对其进行系统而详细的介绍,正巧我也觉得这一章可以单独的拿出来,所以就有了这篇文章。
因为我们面面俱到的讲解了list,所以相对来说文章很长。希望大家能有耐心。
linux kernel list,linux内核的链表结构,它使用了最简洁的方式实现了一个几乎是万能的链表。
说起链表,一共有3种:单向链表、双向链表、循环链表。
单向链表是链表中最简单的一种形式,它只有一个后继指针(一般代码中是next)来维护结构。所以,单向链表只能沿着后继指针做向后操作,不能向前操作。单向链表也不能从中间插入或者删除一个元素,因为没有前向指针,所以无法获取前一个元素,从中间删除链表元素会引起断链。单向链表示意图如下:

双向链表:双向链表比单向链表复杂一些,它除了有一个后续指针next指向下一个元素之外还有一个前驱指针prev指向前一个元素。所以,双向链表不仅能向后遍历,还能向前遍历。同时,双向链表也能对链表中的任一元素进行操作,并且在任何位置都可以进行增加和删除操作,因为它不会断链。双向链表的示意图如下:

循环链表:循环链表是双向链表的升级版。相比双向链表,循环列表首尾相连。示意图如下:

本节所要讨论的kernel list是一个循环链表结构。我注意并且运用linux kernel list是从2.6内核开始的。linux内核的链表和我们一般定义的链表最大的差别有2个:
结构定义。linux内核的链表定义非常简单明了,并且严格遵守了自定义结构不定义数据的规则。在list.h文件中定义如下:
- struct list_node {
- struct list_node *prev;
- struct list_node *next;
- };
在linux的kernel中结构体的名称是list_head,只是因为习惯问题,我们团队觉得list_head使用起来不太顺手,而list_node比较习惯,所以我们在移植的时候统一改了一下结构体的名称。list_node的定义相当清晰:前驱指针和后继指针。list_node的定义和我们通常对于链表结构的定义最大的不同是list_node链表结构没有数据域。通常我们要实现双向链表结构会将本身的链表关系结构和实体数据属性合并到一起。比如有一个学生结构体,属性为学号(id),姓名(name),年龄(age)。那么这样的一个链表定义如下:
- struct student {
- int id;
- char *name;
- int age;
- struct student *prev;
- struct student *next;
- };
这个结构体正确,并且可用,但是并不通用。
linux kernel list使用的时候需要一个表头。因为它的通用化设计和追求更简单的算法实现,list必须要一个简单而通用的方法通过自身来判断链表是否存在数据、链表是否已经遍历到最后等等。所以linux kernel list不能像我们通常的定义一样来区分,而是通过在使用链表之前置一个链表头来实现。链表头是在链表初始化的时候确定的,所以链表可以通过前驱和后继指针都指向自己来确定链表目前的状态是空的。
linux kernel list使用起来也很简单。当我们使用它来表示链表的时候,需要使用数据包含结构的方式而不是我们上面的结构和数据混合方式。比如同样的定义一个student结构,使用linux kernel list的代码如下:
- struct student {
- struct list_node chain;
- int id;
- char *name;
- int age;
- };
随后,在使用链表操作之前一定要先初始化链表头,再使用linux kernel list提供的API进行增加、删除、遍历等操作。
初始化
linux kernel list提供了3种初始化链表的方式,前面2种其实是一样的,属于创建型,API如下:
- struct list_node name = LIST_HEAD_INIT(name)
第3种是初始化模式,这对于结构体内嵌链表头比较适合,比如我们上面内存池中的slot_chain、larger_chain和cleanup_chain。
- static inline void INIT_LIST_HEAD(struct list_node *list) {
- list->next = list;
- list->prev = list;
- }
不管是那个API初始化链表,初始化的时候始终只在做一件事情:就是把head的prev指针和next指针都指向head本身。初始化后的链表结构如下图:

添加链表项
linux kernel list提供了2个添加链表项的API,分别是在链表的头部添加一个链表项和在链表的尾部添加一个列表项。因为linux kernel list定义链表结构的时候只定义了前向和后继两个指针,添加链表项也只是指针指向的变动;又因为从开始设计linux kernel list的时候就设定链表头是肯定存在的,所以也没有了链表是否为空之类的繁琐判断,整个代码显得非常的简洁。linux kernel list提供的代码如下:
- /*
- * 在两个已知连续的实体间插入一个新实体
- * 这个方法仅使用在我们知道实体的prev/next指针的内部链表操作
- */
- static inline void __list_add(struct list_node *new,
- struct list_node *prev,
- struct list_node *next) {
- next->prev = new;
- new->next = next;
- new->prev = prev;
- prev->next = new;
- }
注意:这是一个私有函数。在linux kernel list定义中,形如"__XXXX()"的函数,也就是双下划线开始的函数,被内定为内部函数,外部不许调用(下同,不在累述)。 在私有函数中,一般只会进行最简单的操作,而对于边界问题、特殊值等检查和校验都会在对外的接口函数中完成。
- /**
- * list_add - 添加一个实体
- * @new: 被添加的新实体
- * @head: 链表头部,新实体被添加在这个head后面
- *
- * 在指定的链表头部后面添加一个新实体。
- * 这有利于实现栈。
- */
- static inline void list_add(struct list_node *new, struct list_node *head) {
- __list_add(new, head, head->next);
- }
这是一个链表的添加函数,将实体添加到链表的head后面,也就是next指针处。比如初始化链表头后的示意图如上图所示,那么添加实体后的整个链表示意图如下所示:

- /**
- * list_add_tail - 添加一个新实体
- * @new: 被添加的新实体
- * @head: 链表头部,新实体加被添加在这个head之前
- *
- * 在指定的链表头部之前添加一个新实体。
- * 这对于实现队列有用。
- */
- static inline void list_add_tail(struct list_node *new, struct list_node *head) {
- __list_add(new, head->prev, head);
- }
这也是一个链表的添加函数,和上面的函数不同的是此函数将实体添加到链表的末尾,也就是head的prev指针指向处。如果在上图的基础上将实体添加到链表的末尾,添加后的链表示意图如下所示:

删除链表项
删除链表项和添加链表项如出一辙,也是简单的指针操作。相比添加链表项,因为双向链表中的项每个都有前向和后继两个指针,所以删除链表项只需要明确单个链表项即可,并不存在从头部删除或者是从尾部删除的区别。所以,linux kernel list的代码如下:
- /*
- * 通过实体的prev/next指针相互指向对方删除实体
- *
- * 这个方法仅使用在我们知道实体的prev/next指针的内部链表操作
- */
- static inline void __list_del(struct list_node * prev, struct list_node * next) {
- next->prev = prev;
- prev->next = next;
- }
- static inline void __list_del_entry(struct list_node *entry) {
- __list_del(entry->prev, entry->next);
- }
同上,这2个函数是链表删除的真正操作,因为是"__"双下划线开头,故是一个私有函数。
- /**
- * list_del - 从链表中删除一个实体.
- * @entry: 从链表中欲删除的实体.
- * Note: 在entry上执行list_del操作后,对entry执行list_empty操作将不返回true。
- * 因为entry属于未定义状态。
- */
- static inline void list_del(struct list_node *entry) {
- __list_del(entry->prev, entry->next);
- entry->next = NULL;
- entry->prev = NULL;
- }
上面的函数是删除链表项的对外API。如有原始链表如下图:

执行list_del操作,删除实体2后,链表图示如下:

对于这个函数,我们在移植的过程中进行了更改,原本的代码是这样的:
- static inline void list_del(struct list_head *entry) {
- __list_del(entry->prev, entry->next);
- entry->next = LIST_POISON1; //注意这里的不同
- entry->prev = LIST_POISON2;
- }
我们更改的是将prev和next指针指向了NULL,而linux kernel是指向了LIST_POISON1和LIST_POISON2两个宏。查看一下这两个宏定义:
有点莫名其妙的定义,在往下查,POISON_POINTER_DELTA的宏定义如下:
还是没看懂。没看懂也不重要,查了一下linux的官方解析,LIST_POISON是一段内核内的无效的地址区,当开发者误用这个地址时,会发出页错误。我们为了简单起见,将其改成了NULL,也无伤大雅。
linux还提供了一个删除链表项实体的API,如下:
- /**
- * list_del_init - 从链表中删除一个实体,并且重新将该实体初始化为另一链表的head.
- * @entry: 从链表中欲删除的实体.
- */
- static inline void list_del_init(struct list_node *entry) {
- __list_del_entry(entry);
- INIT_LIST_HEAD(entry);
- }
这个API和上面的删除链表项API的不同是:会将被删除的实体初始化为另一个链表的head。同上一样删除实体2,本API会将实体2初始化为head。示意图如下:

替换操作
linux kernel list定义了2个替换API。第一个API操作仅仅只是替换而已,第二个API在替换的基础上还增加了初始化被替换实体的操作。linux kernel list代码如下:
- /**
- * list_replace - 将old实体替换成new实体
- * @old : 被替换掉的实体
- * @new : 替换的实体
- *
- * 如果old是一个空的(并不是NULL,空的是指prev/next都指向自己,比如head),
- * old将会被重写.
- */
- static inline void list_replace(struct list_node *old,
- struct list_node *new) {
- new->next = old->next;
- new->next->prev = new;
- new->prev = old->prev;
- new->prev->next = new;
- }
此API是直接将old的实体替换成了new,比如有一个new的实体,链表示意图如下:

将new替换成实体2后,链表示意图如下:

注意,这里有个问题:替换后的元素原来的prev和next指针指向并没有发生改变,还是保持原来的指向,所以这是一个bug的温床,大家要注意处理。最简单的办法就是把替换下来的元素prev、next指针都设置成NULL就可以了。
- /**
- * list_replace_init - 将old实体替换成new实体,并且重新将old初始化为另一链表的head
- * @old : 被替换掉的实体
- * @new : 替换的实体
- *
- * 如果old是一个空的(并不是NULL,空的是指prev/next都指向自己,比如head),
- * old将会被重写.
- */
- static inline void list_replace_init(struct list_node *old,
- struct list_node *new) {
- list_replace(old, new);
- INIT_LIST_HEAD(old);
- }
此API将会在替换掉old以后,将old初始化为另一个链表的head。和list_del_init异曲同工。替换后链表示意图如下:

对于替换操作,有一个特例是当链表为空的时候,也就是只有head一个元素的时候,示意图如下:

如果将head替换掉,将会形成如下的链表形式:

移动操作
linux kernel list定义了两个移动操作的API。移动操作是将原本属于链表A中的实体移动到链表B中,相当于将元素从链表A中删除再加入到链表B中。linux kernel list的代码如下:
- /**
- * list_move - 将list从一个链表移动到另一个链表中
- * @list: 欲移动的实体
- * @head: 欲加入的新链表的head
- */
- static inline void list_move(struct list_node *list, struct list_node *head) {
- __list_del_entry(list);
- list_add(list, head);
- }
有两个链表,如下图所示:

假设移动实体2,将实体2从左边的链表移动到右边的链表,即自行list_move(实体2,右边链表的head),则两个链表的示意图如下所示:

从示意图中可以看出,list_move函数是将元素移植到链表的头部,其实不仅仅是头部,只要是list_node元素,都可以满足要求,确切来说,移动到参数head元素的next指针指向的位置才是正确的。
有移动到next位置,就会有移动到prev指针指向位置的,所以代码如下:
- /**
- * list_move_tail - 将list从一个链表移动到另一个链表尾部
- * @list: 欲移动的实体
- * @head: 欲加入的新链表的head
- */
- static inline void list_move_tail(struct list_node *list,
- struct list_node *head) {
- __list_del_entry(list);
- list_add_tail(list, head);
- }
同样的道理,将实体2移动到右边链表的tail位置,示意图如下:

和list_move函数一样,确切的意义是将元素移动到参数head的prev指针指向的位置。
- /**
- * list_rotate_left - 左向移动节点,把第一个元素移动到最后一个
- * @head: 链表的head节点
- */
- static inline void list_rotate_left(struct list_node *head) {
- struct list_node *first;
- if (!list_empty(head)) {
- first = head->next;
- list_move_tail(first, head);
- }
- }
该函数是将head元素的next指针指向的实体向后移动到head元素prev指针指向的位置。链表移动示意图如下所示:

如果这个函数执行多次,将会把整个链表的顺序正好做一个180度的翻转。
判断操作
linux kernel list为链表增加了判断特殊状态的API。判断链表的状态并没有特别大的难度,linux kernel list需要注意的地方就是:链表为empty的时候,链表并不是NULL,也不是一个元素没有。链表至少有一个元素,就是它本身的head元素。
- /**
- * list_is_last - 判断list节点是不是head链表的最后一个节点
- * @list: 欲测试的节点
- * @head: 链表的head节点
- */
- static inline int list_is_last(const struct list_node *list,
- const struct list_node *head) {
- return list->next == head;
- }
- /**
- * list_empty - 判断链表是否是空,即一个节点都不存在
- * @head: 欲测试的链表
- */
- static inline int list_empty(const struct list_node *head) {
- return head->next == head;
- }
- /**
- * list_empty_careful - 测试链表是都为空且未被修改
- * @head: 欲测试的链表
- *
- *
- * 说明:
- * 测试链表是否为空的,并且检查没有另外的CPU正在更改成员(next或者是prev指针)
- *
- * 注意:如果在没有同步的情况下使用list_empty_careful函数,
- * 除非其他cpu的链表操作只有list_del_init(),否则仍然不能保证线程安全。
- *
- */
- static inline int list_empty_careful(const struct list_node *head) {
- struct list_node *next = head->next;
- return (next == head) && (next == head->prev);
- }
- /**
- * list_is_singular - 判断链表是否只有一个节点(除head节点外)
- * @head: 欲测试的链表.
- */
- static inline int list_is_singular(const struct list_node *head) {
- return !list_empty(head) && (head->next == head->prev);
- }
切分
linux kernel list为链表定义了切分操作。切分操作发生在两个链表中,所以相对比较复杂,同时会修改两个链表的指针指向。linux kernel list的代码定义如下:
- /**
- * __list_cut_position - 将一个列表切分成两个链表
- * 从head节点开始(不包括head)到entry节点结束(包括entry)的节点
- * 全部切割到list链表
- * @list : 一个新的链表,将切分的节点全部加入到这个链表
- * @head : 待切分链表的开始节点
- * @entry : 待切分链表的结束节点,必须和head在一个链表内
- * 注意:list必须是一个空链表,或者是无所谓丢失原有节点的链表,
- * 切分操作将会替换掉list链表原有的元素
- */
- static inline void __list_cut_position(struct list_node *list,
- struct list_node *head,
- struct list_node *entry) {
- struct list_node *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;
- }
- /**
- * list_cut_position - 将一个链表切分成2个
- * @list : 一个新的链表,将切分的节点全部加入到这个链表
- * @head : 待切分链表的开始节点
- * @entry : 待切分链表的结束节点,必须和head在一个链表内
- *
- */
- static inline void list_cut_position(struct list_node *list,
- struct list_node *head, struct list_node *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);
- }
严格按照linux kernel的规则书写代码,双下划綫开头的函数是内部函数,没有加任何的参数验证,而对外开放的API则增加了参数校验。切分链表的操作和list_move比较相像,也是从一个链表中将一堆元素删除,再加到另外一个链表中。和list_move的差别可能只是移动的元素数量不同了。
因为链表切分函数一次操作的元素变多了,所以形成了一个区间。对于参数head和entry来说,切分的区间在head上是开区间,切分的时候不会包含head自身;而在entry上是闭区间,切分的时候包含entry自身。
设有两个链表,如下图:

假设,我们需要将元素2-元素4区间的全部元素切分到另外一个链表中,执行list_cut_position后,链表的状态如下图所示:

注意:另外一个链表中的元素6和元素7已经被替换掉了。这种替换容易引起野指针,一定要引起注意和重视。
- /**
- * list_clear - 清除以head为头部的链表,将其所有元素挂载到list链表上
- * @head : 欲清除节点的链表head节点
- * @list : 一个新的链表,原链表上的所有节点都将挂载到该链表上
- *
- */
- static inline void list_clear(struct list_node *head,struct list_node *list){
- if(list_empty(head)) return;
- list_cut_position(list,head,head->next);
- }
list_clear函数是list_cut_position函数的更彻底版本,它将一个完整的链表给切分到了另外一个链表中,同样是上图的两个链表初始图,经过调用list_clear函数后,两个链表将会总体调个个,图示如下:

合并
合并操作和上一节的切分操作很类似。两者的基本区分在于对于原有list的处理:如有list1和list2两个链表,如果是合并操作,将list1上的元素合并到list2的时候,并不会删除掉list2上原有的元素,而切分操作会将list2上的元素替换掉。
- /**
- * __list_splice - 合并链表,将list链表合并到prev和next指向的节点中间
- * @list : 一个要被合并掉的链表
- * @prev : 一个要合并其它链表的前驱指针
- * @next : 一个要合并其它链表的后继指针
- */
- static inline void __list_splice(const struct list_node *list,
- struct list_node *prev,
- struct list_node *next) {
- struct list_node *first = list->next;
- struct list_node *last = list->prev;
- first->prev = prev;
- prev->next = first;
- last->next = next;
- next->prev = last;
- }
私有的合并函数和list通常的私有函数一样没有参数校验,只是做了最基本的指针操作。
- /**
- * list_splice - 合并两个链表,将list合并到以head为头链表的前面,这是为了栈设计的
- * @list: 要被合并的list
- * @head: 添加到第一个链表中的位置
- */
- static inline void list_splice(const struct list_node *list,
- struct list_node *head) {
- if (!list_empty(list))
- __list_splice(list, head, head->next);
- }
这是合并两个list,假设我们又list1和list2,分别如下图:

执行list_splice函数,将list2合并到list1后,会将list2中的元素合并到list1的head指针后面,效果图如下:

- /**
- * list_splice_tail - 将list合并到head为头链表的尾部,这两个链表都是队列
- * @list: 要被合并的list
- * @head: 添加到一个列表中的位置.
- */
- static inline void list_splice_tail(struct list_node *list,
- struct list_node *head) {
- if (!list_empty(list))
- __list_splice(list, head->prev, head);
- }
此函数和上面的list_splice函数一样,只是合并的位置不同。将list2合并到了list1的尾部,也就是list1的head指针prev指向处。效果图如下:

上面两个API只是合并了链表,但是对于被合并的list2链表head元素的prev和next指针都没有进行处理,还是指向原来的元素。这样的操作可能会引起bug,所以linux kernel list增加了2个重置list2链表head的API,如下:
- /**
- * list_splice_init - 合并两个链表,并且重新初始化list为空链表
- * @list: 要被合并的list
- * @head: 添加到第一个链表中的位置
- *
- */
- static inline void list_splice_init(struct list_node *list,
- struct list_node *head) {
- if (!list_empty(list)) {
- __list_splice(list, head, head->next);
- INIT_LIST_HEAD(list);
- }
- }
同list_splice,只是会将list2链表head重新初始化。效果图如下:

- /**
- * list_splice_tail_init - 合并两个链表,并且重新初始化list为空链表
- * @list: 要被合并的list
- * @head: 添加到一个列表中的位置
- *
- */
- static inline void list_splice_tail_init(struct list_node *list,
- struct list_node *head) {
- if (!list_empty(list)) {
- __list_splice(list, head->prev, head);
- INIT_LIST_HEAD(list);
- }
- }
同list_splice_tail,只是会将list2链表head重新初始化。效果图如下:

宏定义
linux kernel list除了定义了一些函数API之外,还定义了一些宏。这些宏主要是为了方便链表的内部操作和给开发者更方便的使用链表。
指针位置计算
list_entry是linux kernel list提供的一个根据list_node元素在链表元素结构体类型中的指针和结构体自身来计算出该结构体实体的首地址。list_entry其实就是container_of的重定义。container_of在前面的章节已经有过介绍,如有不清楚的,可以参照前面的章节。
- /**
- * list_entry - 得到实体首地址
- * @ptr: list_node在实体中的指针.
- * @type: list_node所在的结构体类型
- * @member: list_node类型的元素在结构体中的名称.
- */
- #define list_entry(ptr, type, member) container_of(ptr,type,member)
根据位置得到实体
linux kernel list为了自身和第三方开发者的方便,也定义了根据位置信息获取实体的API。linux kernel list的代码定义如下:
- /**
- * list_first_entry - 得到链表中的第一个实体,即head的next指针指向的实体
- * @ptr: 链表的head指针.
- * @type: 链表实体的结构体类型.
- * @member: list_node的元素在结构体中的名称.
- *
- * 注意:链表不能为空。
- */
- #define list_first_entry(ptr, type, member) \
- list_entry((ptr)->next, type, member)
- /**
- * list_last_entry - 得到链表中最后一个实体,即head的prev指针指向的实体
- * @ptr: 链表的head指针.
- * @type: 链表实体的结构体类型.
- * @member: list_node的元素在结构体中的名称.
- *
- * 注意:链表不能为空。
- */
- #define list_last_entry(ptr, type, member) \
- list_entry((ptr)->prev, type, member)
- /**
- * list_first_entry_or_null - 得到链表中的第一个实体,即head的next指针指向的实体
- * 如果链表为空,即返回空
- * @ptr: 链表的head指针.
- * @type: 链表实体的结构体类型.
- * @member: list_node的元素在结构体中的名称
- *
- * 注意:该API是list_first_entry的一个扩展
- */
- #define list_first_entry_or_null(ptr, type, member) \
- (!list_empty(ptr) ? list_first_entry(ptr, type, member) : NULL)
- /**
- * list_next_entry - 得到当前链表中pos的下一个实体
- * @pos: list_node所在结构体的实体指针
- * @member: list_node在结构体中的名称
- *
- */
- #define list_next_entry(pos,type, member) \
- list_entry((pos)->member.next, type, member)
- /**
- * list_prev_entry - 得到当前链表中pos的前一个实体
- * @pos: list_node所在结构体的实体指针
- * @member: list_node在结构体中的名称
- */
- #define list_prev_entry(pos, type, member) \
- list_entry((pos)->member.prev, type, member)
遍历操作
对链表的操作,最常用的应该就是遍历了吧。每当需要对所有的元素做一些操作的时候,遍历是必不可少的。linux kernel list也不例外。linux kernel list定义的代码如下:
- /**
- * list_for_each - 往后遍历链表
- * @pos: list_node结构体的指针,用来作为遍历链表的游标.
- * @head: 链表的head指针.
- */
- #define list_for_each(pos, head) \
- for (pos = (head)->next; pos != (head); pos = pos->next)
- /**
- * list_for_each_prev - 前向遍历链表
- * @pos: list_node结构体的指针,用来作为遍历链表的游标
- * @head: 链表的head指针.
- */
- #define list_for_each_prev(pos, head) \
- for (pos = (head)->prev; pos != (head); pos = pos->prev)
- /**
- * list_for_each_safe - 安全的后向遍历链表,适用于在遍历的过程中需要删除实体的情况
- * @pos: list_node结构体的指针,用来作为遍历链表的游标
- * @n: 另外一个list_node结构体的指针,临时存储当前遍历的游标
- * @head: 链表的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_prev_safe - 安全的前向遍历链表,适用于在遍历的过程中需要删除实体的情况
- * @pos: list_node结构体的指针,用来作为遍历链表的游标
- * @n: 另外一个list_node结构体的指针,临时存储当前遍历的游标
- * @head: 链表的head指针.
- */
- #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_entry - 顺序遍历链表中的实体元素
- * @pos: 链表中实体元素的指针.
- * @type : 链表中实体元素的结构体类型
- * @head: lise_node类型的链表头.
- * @member: list_node元素在实体结构式类型中的名称.
- */
- #define list_for_each_entry(pos,type, head, member) \
- for (pos = list_first_entry(head, type, member) \
- &pos->member != (head); \
- pos = list_next_entry(pos, member))
- /**
- * list_for_each_entry_reverse - 倒序遍历链表中的实体元素.
- * @pos: 链表中实体元素的指针.
- * @type : 链表中实体元素的结构体类型
- * @head: lise_node类型的链表头.
- * @member: list_node元素在实体结构式类型中的名称
- */
- #define list_for_each_entry_reverse(pos, type,head, member) \
- for (pos = list_last_entry(head, type, member); \
- &pos->member != (head); \
- pos = list_prev_entry(pos, member))
- /**
- * list_prepare_entry - 准备一个实体指针,为list_for_each_entry_continue()中的pos
- * @pos: 链表中实体元素的指针.
- * @type : 链表中实体元素的结构体类型
- * @member: list_node元素在实体结构式类型中的名称.
- *
- * 准备一个实体指针来作为函数list_for_each_entry_continue()中的开始节点
- */
- #define list_prepare_entry(pos, type,head, member) \
- ((pos) ? : list_entry(head, type, member))
- /**
- * list_for_each_entry_continue - 以链表中的任何一个元素(不包括当前元素)作为起始点遍历链表
- * @pos: 链表中实体元素的指针,通常来自于list_prepare_entry.
- * @head: lise_node类型的链表头.
- * @member: list_node元素在实体结构式类型中的名称
- *
- * 从当前pos所属的位置遍历链表
- */
- #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_reverse - 以链表中的任何一个元素(不包括当前元素)作为起始点反向遍历链表
- * @pos: 链表中实体元素的指针,通常来自于list_prepare_entry.
- * @head: lise_node类型的链表头.
- * @member: list_node元素在实体结构式类型中的名称
- *
- * 从当前pos所属的位置反向遍历链表.
- */
- #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_from - 以当前元素位置遍历链表,便利中包括当前元素
- * @pos: 链表中实体元素的指针
- * @head: lise_node类型的链表头.
- * @member: list_node元素在实体结构式类型中的名称
- *
- *
- */
- #define list_for_each_entry_from(pos, head, member) \
- for (; &pos->member != (head); \
- pos = list_next_entry(pos, member))
- /**
- * list_for_each_entry_safe - 安全的遍历链表中每一个元素,适用于在遍历的过程中需要删除实体的情况
- * @pos: 遍历链表过程中的实体指针.
- * @n: 遍历过程中临时存储下一个实体的指针
- * @head: 链表中list_node类型的head指针.
- * @member: 链表中list_node类型的原书名.
- */
- #define list_for_each_entry_safe(pos, n,type, head, member) \
- for (pos = list_first_entry(head, type, 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指向的当前元素,适用于在遍历的过程中需要删除实体的情况
- * @pos: 遍历链表过程中的实体指针.
- * @n: 遍历过程中临时存储下一个实体的指针
- * @head: 链表中list_node类型的head指针.
- * @member: 链表中list_node类型的原书名.
- *
- */
- #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_from - 安全的遍历链表中的每一个元素,包括pos指向的当前元素,适用于在遍历的过程中需要删除实体的情况
- * @pos: 遍历链表过程中的实体指针.
- * @n: 遍历过程中临时存储下一个实体的指针
- * @head: 链表中list_node类型的head指针.
- * @member: 链表中list_node类型的原书名.
- *
- */
- #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_reverse - 安全的反向遍历链表中的每一个元素,适用于在遍历的过程中需要删除实体的情况
- * @pos: 遍历链表过程中的实体指针.
- * @n: 遍历过程中临时存储下一个实体的指针
- * @head: 链表中list_node类型的head指针.
- * @member: 链表中list_node类型的元素名.
- *
- */
- #define list_for_each_entry_safe_reverse(pos, n,type, head, member) \
- for (pos = list_last_entry(head, type, member), \
- n = list_prev_entry(pos, member); \
- &pos->member != (head); \
- pos = n, n = list_prev_entry(n, member))
- /**
- * list_safe_reset_next - 在list_for_each_entry_safe循环中重置next指针
- * @pos : list_for_each_entry_safe遍历链表过程中的实体指针
- * @n: 遍历过程中临时存储下一个实体的指针
- * @member: 链表中list_node类型的元素名
- *
- * 通常情况下,连边会被同时的更改,所以list_safe_reset_next是线程不安全的
- * 例如:在循环体内锁被释放
- * 如果pos游标指向的实体被固定在链表中,并且list_safe_reset_next不在被锁的循环体内调用,则出现异常。
- * 调用list_safe_reset_next请一定要自行注意线程安全问题
- */
- #define list_safe_reset_next(pos, n, member) \
- n = list_next_entry(pos, member)
曾经看到过一句话,大概意思就是:最好的算法只维护结构,不操作实际数据。虽然已经忘记了看到的原话,也忘记了具体在哪里看到的。但唯一还能清楚的记起的就是这句话的意思。看到这句话的时候一直到很久远的以前,也就是在没有看到linux kernel list的实现之前,我一直不太明白这句话的含义,也不太明白到底应该怎么样才能优雅的去解决结构和数据之间的问题。诚然,那个时候是很小白的。但是自从看到了linux kernel list的设计和实现后,一切又豁然开朗了。从接触计算机开始,到大学的各种语言课程、数据结构课程、算法课程,双向列表几乎是必有的。但它们的实现却千篇一律而又统一,可以说,linux kernel list的设计和实现从某些程度上颠覆了很多人对于数据结构和算法的重新认知。
linux kernel list,确实是一个不可多得的简洁而又优雅的设计和实现。
原文地址: https://blog.youkuaiyun.com/Msy3TU4dFuUZ4/article/details/78930020
本文详细介绍了Linux内核中的链表实现,包括其结构定义、初始化、添加、删除等操作,以及各种遍历和特殊操作。通过对这些内容的学习,读者能够深入理解Linux内核链表的设计思想。
914

被折叠的 条评论
为什么被折叠?



