1、前言
数据结构中的链表在任何教程里面都是放在最前面讲解,这不仅仅体现它的重要性,而且说明了它的基础性。不说是地基,也能说是根葱,顶梁柱。而且在整个内核当中,无处不在。
记得在上大学的时候,学习数据结构链表,虽然用它写了很多程序,但是始终不晓得它的重要性在哪,更不知道它到底有何神通,用到何处。向左,向右,向前看,Linux要拐几个弯才来遇见(《遇见》),后来,终于在Linux中明白,有些地方一旦用了就忘不了。 双链表,吊炸天,落在我vim编辑code上。“爱你!”我轻声说。 如果当时我能看到你,现在也不会觉得那么相见恨晚(《后来》)^_^
言归正传,本博文Kernel3.10版本。揭开Linux双链表的面纱,领略大家的编码。
背景:kernel在2.1V后引入链表集合统一起来,目的就为了避免代码冗余。链表用途太广了,如果不统一起来,那么自己需要的时候,自己造一个,结果kernel内“百花齐放”,重复“造车轮”,因此现在要求,所有内核开发者应该使用如今的链表接口。
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>声明<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
>> 知识要传播,劳动要尊重! 受益于开源,回馈于社会! 大家共参与,服务全人类!
>> 本博文由my_live_123原创(http://blog.csdn.net/cwcmcw),转载请注明出处!
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>^_^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
2、双链表结点数据类型定义
<linux/types.h>
- struct list_head {
- struct list_head *next, *prev;
- };
其中,next域指向前驱结点,prev域指向后驱结点
2.1、不严格区分头尾结点
这里不像数据结构教程里那样严格区分头结点,尾结点和表结点。
解析:事实上,内核不过分区分头结点还是尾结点,整个链表是一个完整的环形,完全可以抛开头尾这些概念;只有有序链表,才加以区分;当对访问链表的顺序没有要求时,遍历链表仅仅需要从链表中任意一个结点开始,沿着指针逐个访问下一个(或者前一个,不区分头尾,其实没有方向@_@,因此下一个可以是next也可以是prev,千万别晕哦),直到重新回到起始的结点即可!因此,不需要特别的头或者尾结点,每个进程仅仅需要指向链表中某个结点的指针,就可以操纵链表了,岂不是操作链表变得简单多了?!这些思想很重要,因为链表在内核中使用十分频繁,必须高效!据说黑客们对此设计十分自豪。~_~
2.2、为什么没有数据域
可能你会好奇,为什么没有数据域?数据存储到哪里?
解析:链表仅仅是一个链接你数据的工具,它不依赖与任何数据类型,具有通用性,自身不包含数据域。数据域由你的结构体而定,而你仅仅需要在自己的结构体内,嵌入struct list_head实例,就给自己数据插上了链接的“双臂”。
3、双链表的定义
<linux/list.h>
3.1、静态声明和初始化
- #define LIST_HEAD_INIT(name) { &(name), &(name) }
-
- #define LIST_HEAD(name) \
- struct list_head name = LIST_HEAD_INIT(name)
LIST_HEAD_INIT(name)负责初始化,使得表结点的指针域指向自身,达到初始化为空的目的。
LIST_HEAD(name)定义一个名位name的双链表,并初始化位为空
3.2、动态初始化
- static inline void INIT_LIST_HEAD(struct list_head *list)
- {
- list->next = list;
- list->prev = list;
- }
4、添加结点
4.1、内部添加结点函数
- 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;
- prev->next = new;
- }
此方法只使用于内部链表操作,并且已知prev、next结点
由于三个结点都有指针指向,因此这4步操作可以任意互换,而不影响操作的成功性。
4.2、指定结点之后插入新结点
-
-
-
-
-
-
-
-
-
- static inline void list_add(struct list_head *new, struct list_head *head)
- {
- __list_add(new, head, head->next);
- }
4.3、指定结点之前插入新结点
-
-
-
-
-
-
-
-
- static inline void list_add_tail(struct list_head *new, struct list_head *head)
- {
- __list_add(new, head->prev, head);
- }
注意和4.2的区分,差别就在于参数传递
__list_add(new, head, head->next);
__list_add(new, head->prev, head);
5、删除结点
5.1、内部删除结点函数
-
-
-
-
-
-
-
- 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);
- }
注意这个操作是内部函数,并没有对entry的指针域做任何处理
5.2、删除结点——特殊处理结点指针
- static inline void list_del(struct list_head *entry)
- {
- __list_del(entry->prev, entry->next);
- entry->next = LIST_POISON1;
- entry->prev = LIST_POISON2;
- }
LIST_POISON1,2在<linux/position.h>中,它们是个常量,有内核配置时决定
- #ifdef CONFIG_ILLEGAL_POINTER_VALUE
- #define POISON_POINTER_DELTA _AC(CONFIG_ILLEGAL_POINTER_VALUE,UL)
- #else
- #define POISON_POINTER_DELTA 0
- #endif
-
-
-
-
-
- #define LIST_POISON1 ((void *) 0x00100100 + POISON_POINTER_DELTA)
- #define LIST_POISON2 ((void *) 0x00200200 + POISON_POINTER_DELTA)
其中_AC在<linux/const.h>,是一条件编译,如果定义了__ASSEMBLY__则_AC等于什么都没有做;否则,把_AC的两个参数拼接在一起成为一个整体标识符
5.3、删除结点——初始化被删除结点
-
-
-
-
- static inline void list_del_init(struct list_head *entry)
- {
- __list_del_entry(entry);
- INIT_LIST_HEAD(entry);
- }
这样确保删除后结点与链表的完全脱离,而且使得entry能成为一个独立的链表且已经初始化,可以执行进一步的操作。
6、结点的替换
6.1、结点替换操作
-
-
-
-
-
-
-
- static inline void list_replace(struct list_head *old,
- struct list_head *new)
- {
- new->next = old->next;
- new->next->prev = new;
- new->prev = old->prev;
- new->prev->next = new;
- }
6.2、替换结点
- static inline void list_replace_init(struct list_head *old,
- struct list_head *new)
- {
- list_replace(old, new);
- INIT_LIST_HEAD(old);
- }
7、结点的移动
-
-
-
-
-
- static inline void list_move(struct list_head *list, struct list_head *head)
- {
- __list_del_entry(list);
- list_add(list, head);
- }
-
-
-
-
-
- static inline void list_move_tail(struct list_head *list,
- struct list_head *head)
- {
- __list_del_entry(list);
- list_add_tail(list, head);
- }