一、链表概述
- 链表是Linux内核中
最简单、最普通的数据结构
- 链表是一种存放和操作可变数量元素(常称为节点)的数据结构。链表和静态数组的不同之处在于,它所包含的元素都是动态创建并插入链表的,在编译时不必知道具体需要创建多少个元素。另外也因为链表中每个元素的创建时间各不相同,所以它们在内存中无须占用连续内存区。正是因为元素不连续地存放,所以各元素需要通过某种方式被连接在一起。于是每个元素都包含一个指向下一个元素的指针,当有元素加入链表或从链表中删除元素时,简单调整指向下一个节点的指针就可以了
二、链表的种类
单向链表
可以用一种最简单的数据结构来表示这样一个链表:
1
2
3
4
5
6 1/* 一个链表中的一个元素 */
2struct list_element {
3 void *data; /* 有效数据 */
4 struct list_element *next; /* 指向下一个元素的指针 */
5};
6双向链表
1
2
3
4
5
6
7 1/* 一个链表中的一个元素 */
2struct list_element {
3 void *data; /* 有效数据 */
4 struct list_element *next; /* 指向下一个元素的指针 */
5 struct list_element *prev; /* 指向前一个元素的指针 */
6};
7环形链表
三、Linux内核链表的实现
- 相比普遍的链表实现方式,Linux内核的实现可以说独树一帜
- Linux内核方式与众不同,
它不是将数据结构塞入链表 ,而是将链表节点塞入数据结构
Linux的链表数据结构(list_head)
- 在过去,内核中有许多链表的实现,该选一个既简单、又高效的链表来统一它们了。 在 2.1 内核开发系列中,首次
引入了官方内核链表实现。从此内核中的所有链表现在都使用官方的链表实现了
链表代码定义于list.h头文件中,格式如下:
next:指向下一个链表节点
prev:指向前一个链表节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 1//以下代码来自于Linux 2.6.22/include/linux/list.h
2/*
3 * Simple doubly linked list implementation.
4 *
5 * Some of the internal functions ("__xxx") are useful when
6 * manipulating whole lists rather than single entries, as
7 * sometimes we already know the next/prev entries and we can
8 * generate better code by using them directly rather than
9 * using the generic single-entry routines.
10 */
11
12struct list_head {
13 struct list_head *next, *prev;
14};
15链表在内核中如何使用
如果我们普通的应用程序来表示一个链表中的节点,则其格式如下:
1
2
3
4
5
6
7
8 1struct fox {
2 unsigned long tail_length;
3 unsigned long_weight;
4 bool is_fantastic;
5 struct fox *next; //指向后一个节点
6 struct fox *prev; //指向前一个节点
7};
8
但是Linux表示一个节点,则用下面的格式:
其中list.next:指向下一个元素
其中list.prev:指向前一个元素
1
2
3
4
5
6
7 1struct fox {
2 unsigned long tail_length;
3 unsigned long_weight;
4 bool is_fantastic;
5 struct list_head list; //所有fox结构体形成地链表
6};
7container_of()宏
- 使用宏container_of()可以很方便地从链表指针找到父结构中包含的任何变量。这是因为在C语言中,一个给定结构中的变置偏移在编译时地址就被ABI固定下来了
list_entry()宏
- 使用container_of()宏,我们定义一个简单的函数便可
返回包含list_head的父类型结构体
- 依靠list_entry()方法,内核提供了
创建、操作以及其他链表管理的各种例程——所有这些方法都不需要知道list_head所嵌入对象的数据结构
以下代码来自于Linux 2.6.22/include/linux/list.h
1
2
3
4
5
6
7
8
9 1/**
2 * list_entry - get the struct for this entry
3 * @ptr: the &struct list_head pointer.
4 * @type: the type of the struct this is embedded in.
5 * @member: the name of the list_struct within the struct.
6 */
7#define list_entry(ptr, type, member) \
8 container_of(ptr, type, member)
9定义/创建一个链表(LIST_HEAD_INIT)
链表需要在使用前初始化。因为多数元素都是动态创建的(也许这就是需要链表的原因),因此最常见的方式是在运行时初始化链表,如下图所示
如果一个结构在编译期静态创建,而你需要在其中给出一个链表的直接引用,下面是最简方式:
以下代码来自于Linux 2.6.22/include/linux/list.h
1
2 1#define LIST_HEAD_INIT(name) { &(name), &(name) }
2链表头(LIST_HEAD宏)
- 内核链表最突出的特点就是:每一个链表节点中都包含一个list_head指针,于是
我们可以从任何一个节点起遍历链表,直到我们看到所有节点
以上方式确实很优美,不过有时确实也需要一个特殊指针索引到整个链表,而不从一个链表节点触发。有趣的是,这个特殊的索引节点事实上也就是一个常规的list_head
**LIST_HEAD:**该函数定义并初始化了一个链表例程,这些例程中的大多数都只接受一个或者两个参数:头节点或者头节点加上一个特殊链表节点(以下代码来自于Linux 2.6.22/include/linux/list.h)
1
2
3
4
5 1#define LIST_HEAD_INIT(name) { &(name), &(name) }
2
3#define LIST_HEAD(name) \
4 struct list_head name = LIST_HEAD_INIT(name)
5
- 例如下面定义并初始化了一个名为fox_list的链表例程,我们下面将介绍对这个例程进行操作的相关函数
四、操作链表(增加、删除、移动)
- 内核提供了一组函数来操作链表,这些函数都要使用一个或多个list_head结构体指针作参数。因为函数都是用C语言以内联函数形式实现的,所以
它们的原型在文件<linux.list.h>中
- 下面介绍的函数复杂度都为O(1)。这意味着,无论这些函数操作的链表大小如何,无论它们得到的参数如何,它们都在恒定时间内完成
检查链表是否为空(list_empty)
检查指定的链表是否为空,为空的话返回非0值;不为空返回0
1
2
3
4
5
6
7
8
9 1/**
2 * list_empty - tests whether a list is empty
3 * @head: the list to test.
4 */
5static inline int list_empty(const struct list_head *head)
6{
7 return head->next == head;
8}
9增加节点(list_add)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 1/**
2 * list_add - add a new entry
3 * @new: new entry to be added
4 * @head: list head to add it after
5 *
6 * Insert a new entry after the specified head.
7 * This is good for implementing stacks.
8 */
9#ifndef CONFIG_DEBUG_LIST
10static inline void list_add(struct list_head *new, struct list_head *head)
11{
12 __list_add(new, head, head->next);
13}
14#else
15extern void list_add(struct list_head *new, struct list_head *head);
16#endif
17
**功能:**该函数向指定链表的head节点后插入new节点
因为链表是循环的,而且通常没有首尾节点的概念,所以你可以把任何一个节点当成head。如果把“最后”一个节点当做head的话,那么该函数可以用来现一个栈
**例如:**我们创建一个新的struct fox,并将它加入fox_list中:
1
2 1list_add(&f->list,&fox_list);
2增加尾节点(list_add_tail)
1
2
3
4
5
6
7
8
9
10
11
12
13 1/**
2 * list_add_tail - add a new entry
3 * @new: new entry to be added
4 * @head: list head to add it before
5 *
6 * Insert a new entry before the specified head.
7 * This is useful for implementing queues.
8 */
9static inline void list_add_tail(struct list_head *new, struct list_head *head)
10{
11 __list_add(new, head->prev, head);
12}
13
- **功能:**该函数向指定链表的head令点前插入new节点
- 和list_add()函数类似,因为链表是环形的,所以可以把任何一个节点当做head。如果把“第一个”元素当做head的话,那么该函数可以用来实现一个队列
__list_add()函数
上面的函数都调用了__list_add,其原型如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 1/*
2 * Insert a new entry between two known consecutive entries.
3 *
4 * This is only for internal list manipulation where we know
5 * the prev/next entries already!
6 */
7#ifndef CONFIG_DEBUG_LIST
8static inline void __list_add(struct list_head *new,
9 struct list_head *prev,
10 struct list_head *next)
11{
12 next->prev = new;
13 new->next = next;
14 new->prev = prev;
15 prev->next = new;
16}
17#else
18extern void __list_add(struct list_head *new,
19 struct list_head *prev,
20 struct list_head *next);
21#endif
22删除节点(list_del)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 1/**
2 * list_del - deletes entry from list.
3 * @entry: the element to delete from the list.
4 * Note: list_empty() on entry does not return true after this, the entry is
5 * in an undefined state.
6 */
7#ifndef CONFIG_DEBUG_LIST
8static inline void list_del(struct list_head *entry)
9{
10 __list_del(entry->prev, entry->next);
11 entry->next = LIST_POISON1;
12 entry->prev = LIST_POISON2;
13}
14#else
15extern void list_del(struct list_head *entry);
16#endif
17
**功能:**从链表中删除一个结点
注意:该函数从链表中删除entry元素。注意,该操作并不会释放entry或释放包含entry的数据结构构体所占用的内存;该函数仅仅将entry元素从链表中移走,所以该函数被调用后,通常还需要再撤销包含entry的数据结构体和其中的entry项
**例如:**删除将f节点从fox_list中删除(注意,该函数并没有接受fox_list作为输入参数。它只是接受一个特定的节点,并修改其前后节点的指针,这样给定的节点就从链表中删除,详情见下面的__list_del源码)
1
2 1list_del(&f->list);
2删除一个节点并重新初始化(list_del_init)
1
2
3
4
5
6
7
8
9
10 1/**
2 * list_del_init - deletes entry from list and reinitialize it.
3 * @entry: the element to delete from the list.
4 */
5static inline void list_del_init(struct list_head *entry)
6{
7 __list_del(entry->prev, entry->next);
8 INIT_LIST_HEAD(entry);
9}
10
- 该函数除了还需要再次初始化entry以外,其他和list_del()函数类似。这样做是因为:虽然链表不再需要entry项,但是还可以再次使用包含entry的数据结构体
__list_del()函数
上面的函数都调用了__list_del函数,其源码如下
1
2
3
4
5
6
7
8
9
10
11
12
13 1/*
2 * Delete a list entry by making the prev/next entries
3 * point to each other.
4 *
5 * This is only for internal list manipulation where we know
6 * the prev/next entries already!
7 */
8static inline void __list_del(struct list_head * prev, struct list_head * next)
9{
10 next->prev = prev;
11 prev->next = next;
12}
13节点的移动(list_move)
从一个链表中移除list项,然后将其加入到另一个链表的head节点后面
1
2
3
4
5
6
7
8
9
10
11 1/**
2 * list_move - delete from one list and add as another's head
3 * @list: the entry to move
4 * @head: the head that will precede our entry
5 */
6static inline void list_move(struct list_head *list, struct list_head *head)
7{
8 __list_del(list->prev, list->next);
9 list_add(list, head);
10}
11节点的移动(list_move_tail)
从一个链表中移除list项,然后将其加入到另一个链表的head节点的前面
1
2
3
4
5
6
7
8
9
10
11
12 1/**
2 * list_move_tail - delete from one list and add as another's tail
3 * @list: the entry to move
4 * @head: the head that will follow our entry
5 */
6static inline void list_move_tail(struct list_head *list,
7 struct list_head *head)
8{
9 __list_del(list->prev, list->next);
10 list_add_tail(list, head);
11}
12链表的合并(list_splice)
该函数合并两个链表,将list所指的链表插入到指定链表的head元素后面
1
2
3
4
5
6
7
8
9
10
11 1/**
2 * list_splice - join two lists
3 * @list: the new list to add.
4 * @head: the place to add it in the first list.
5 */
6static inline void list_splice(struct list_head *list, struct list_head *head)
7{
8 if (!list_empty(list))
9 __list_splice(list, head);
10}
11链表的合并(list_splice_init)
并重新初始化原来的链表,并重新初始化list链表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 1/**
2 * list_splice_init - join two lists and reinitialise the emptied list.
3 * @list: the new list to add.
4 * @head: the place to add it in the first list.
5 *
6 * The list at @list is reinitialised
7 */
8static inline void list_splice_init(struct list_head *list,
9 struct list_head *head)
10{
11 if (!list_empty(list)) {
12 __list_splice(list, head);
13 INIT_LIST_HEAD(list);
14 }
15}
16__list_splice
上面的list_splice和list_splice_init都用到了此函数,其原型如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 1static inline void __list_splice(struct list_head *list,
2 struct list_head *head)
3{
4 struct list_head *first = list->next;
5 struct list_head *last = list->prev;
6 struct list_head *at = head->next;
7
8 first->prev = head;
9 head->next = first;
10
11 last->next = at;
12 at->prev = last;
13}
14
五、遍历链表
- 内核为我们提供了一组非常好的接口,可以用来遍历链表和引用链表中的数据结构体
- 和上面的链表操作函数不同,遍历链表的复杂度为O(n),n是链表所包含的元素数目
基本方法(list_for_each)
该宏使用2个参数:
参数1:一个临时变量,遍历时用来指向当前项
- 参数2:需要遍历的链表的以头节点形式存在的list_head
在遍历链表时,第一个参数在链表中不断移动指向下一个元素,直到链表中的所有元素都被访问为止
1
2
3
4
5
6
7
8
9 1/**
2 * list_for_each - iterate over a list
3 * @pos: the &struct list_head to use as a loop cursor.
4 * @head: the head for your list.
5 */
6#define list_for_each(pos, head) \
7 for (pos = (head)->next; prefetch(pos->next), pos != (head); \
8 pos = pos->next)
9
**演示案例:**例如我们遍历前面的fox_list链表,则可以定义以下的代码
1
2
3
4
5
6
7
8
9 1struct list_head *p;
2struct fox *f;
3
4list_for_each(p,&fox_list){
5 //每次返回一个struct fox结构体
6 //list_entry见上面介绍
7 f=list_entry(p,struct fox,list);
8}
9list_for_each_entry()
前面的方法虽然确实展示了list_head节点的功效,但并不优美,而且也不够灵活。所以多数内核代码采用list_for_each_entry()宏遍历链表。该宏内部也使用list_entry()宏,但简化了遍历过程
参数:
参数1:指向包含list_head节点对象的指针,可将它看做是list_entry宏的返回值
参数2:head是一个指向头节点的指针,即遍历开始的位置
参数3:list_head在结构中的名称
1
2
3
4
5
6
7
8
9
10
11 1/**
2 * list_for_each_entry - iterate over list of given type
3 * @pos: the type * to use as a loop cursor.
4 * @head: the head for your list.
5 * @member: the name of the list_struct within the struct.
6 */
7#define list_for_each_entry(pos, head, member) \
8 for (pos = list_entry((head)->next, typeof(*pos), member); \
9 prefetch(pos->member.next), &pos->member != (head); \
10 pos = list_entry(pos->member.next, typeof(*pos), member))
11
**演示案例:**使用list_for_each_entry遍历所有fox节点
**演示案例:**内核文件系统的更新通知机制(来自inotify)
该函数遍历了inode->inotify_watched链表中的所有项,每个项的类型都是struct inotify_watch,list_head在结构体中被命名为i_list。循环中的每一次遍历,watc都指向链表的新节点。该函数的目的在于:在inode结构串联起来的inotify_watches链表中,搜寻inotify_handle与所提供的句柄相匹配的inotify_watch项
反向遍历链表(list_for_each_entry_reverse)
其和list_for_each_entry类似,不同点在于它是反向遍历链表的。也就是说,不再是沿着next指针向前遍历,而是沿着prev指针向后遍历
很多原因会需要反向遍历链表。其中一个是性能原因——如果你知道你要寻找的节点最可能在你搜索的起始点的前面,那么反向搜索岂不更快。第二个原因是如果顺序很重要,比如,如果你使用链表实现堆栈,那么你需要从尾部向前遍历才能达到先进/先 出(LIFO)原则。如果你没有确切的反向遍历的原因,就老实点,用list_for_each_entry()宏吧
1
2
3
4
5
6
7
8
9
10
11 1/**
2 * list_for_each_entry_reverse - iterate backwards over list of given type.
3 * @pos: the type * to use as a loop cursor.
4 * @head: the head for your list.
5 * @member: the name of the list_struct within the struct.
6 */
7#define list_for_each_entry_reverse(pos, head, member) \
8 for (pos = list_entry((head)->prev, typeof(*pos), member); \
9 prefetch(pos->member.prev), &pos->member != (head); \
10 pos = list_entry(pos->member.prev, typeof(*pos), member))
11正向遍历的同时删除(list_for_each_entry_safe)
标准的链表遍历方法在你遍历链表的同时要想删除节点时是不行的。因为标准的链表方法建 立在你的操作不会改变链表项这一假设上,所以如果当前项在遍历循环中被刪除,那么接下来的遍历就无法获取next(或prev)指针了。这其实是循环处理中的一个常见范式,开发人员通过在潜在的删除操作之前存储next(或者previous)指针到一个临时变量中,以便能执行删除操作。好在Linux内核提供了例程处理这种情况:
1
2
3
4
5
6
7
8
9
10
11
12
13 1/**
2 * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
3 * @pos: the type * to use as a loop cursor.
4 * @n: another type * to use as temporary storage
5 * @head: the head for your list.
6 * @member: the name of the list_struct within the struct.
7 */
8#define list_for_each_entry_safe(pos, n, head, member) \
9 for (pos = list_entry((head)->next, typeof(*pos), member), \
10 n = list_entry(pos->member.next, typeof(*pos), member); \
11 &pos->member != (head); \
12 pos = n, n = list_entry(n->member.next, typeof(*n), member))
13
你可以按照list_for_each_entry()宏的方式使用上述例程,只是需要提供next指针,next指针和pos是同样的类型。list_for_each_entry_safe()启用next指针来将下一项存进表中,以使得能安全删除当前项
**演示案例:**再次看看inotify的例子
该函数遍历并删除inotify_watches链表中的所有项 。如果使用了标准的list_for_each_ entry(),那么上述代码会造成“使用一在一释放后”的错误,因为在移向链表中下一项时,需要访问watch,但这时它已经被撤销
反向遍历的同时删除(list_for_each_entry_safe_reverse)
该宏与list_for_each_entry_safe相反,是在反向遍历链表的同时删除它
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 1/**
2 * list_for_each_entry_safe_reverse
3 * @pos: the type * to use as a loop cursor.
4 * @n: another type * to use as temporary storage
5 * @head: the head for your list.
6 * @member: the name of the list_struct within the struct.
7 *
8 * Iterate backwards over list of given type, safe against removal
9 * of list entry.
10 */
11#define list_for_each_entry_safe_reverse(pos, n, head, member) \
12 for (pos = list_entry((head)->prev, typeof(*pos), member), \
13 n = list_entry(pos->member.prev, typeof(*pos), member); \
14 &pos->member != (head); \
15 pos = n, n = list_entry(n->member.prev, typeof(*n), member))
16
六、总结
- Linux还提供了很多链表操作的方法,可以在<include/linux/list.h>中找到
- 同步和锁:链表的操作有时会遇到并发的问题,同步和锁的概念我们会在后面文章介绍