Linux基础数据结构——双向链表

来源:互联网 发布:出口产品责任险知乎 编辑:程序博客网 时间:2024/05/29 17:20

1、前言

数据结构中的链表在任何教程里面都是放在最前面讲解,这不仅仅体现它的重要性,而且说明了它的基础性。不说是地基,也能说是根葱,顶梁柱。而且在整个内核当中,无处不在。

记得在上大学的时候,学习数据结构链表,虽然用它写了很多程序,但是始终不晓得它的重要性在哪,更不知道它到底有何神通,用到何处。向左,向右,向前看,Linux要拐几个弯才来遇见(《遇见》),后来,终于在Linux中明白,有些地方一旦用了就忘不了。 双链表,吊炸天,落在我vim编辑code上。“爱你!”我轻声说。 如果当时我能看到你,现在也不会觉得那么相见恨晚(《后来》)^_^

言归正传,本博文Kernel3.10版本。揭开Linux双链表的面纱,领略大家的编码。

背景:kernel在2.1V后引入链表集合统一起来,目的就为了避免代码冗余。链表用途太广了,如果不统一起来,那么自己需要的时候,自己造一个,结果kernel内“百花齐放”,重复“造车轮”,因此现在要求,所有内核开发者应该使用如今的链表接口。


>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>声明<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

>>     知识要传播,劳动要尊重! 受益于开源,回馈于社会! 大家共参与,服务全人类!      

>>     本博文由my_live_123原创(http://blog.csdn.net/cwcmcw),转载请注明出处!    

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>^_^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


2、双链表结点数据类型定义

<linux/types.h>

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. struct list_head {  
  2.     struct list_head *next, *prev;  
  3. };  
其中,next域指向前驱结点,prev域指向后驱结点

2.1、不严格区分头尾结点

这里不像数据结构教程里那样严格区分头结点,尾结点和表结点。

解析:事实上,内核不过分区分头结点还是尾结点,整个链表是一个完整的环形,完全可以抛开头尾这些概念;只有有序链表,才加以区分;当对访问链表的顺序没有要求时,遍历链表仅仅需要从链表中任意一个结点开始,沿着指针逐个访问下一个(或者前一个,不区分头尾,其实没有方向@_@,因此下一个可以是next也可以是prev,千万别晕哦),直到重新回到起始的结点即可!因此,不需要特别的头或者尾结点,每个进程仅仅需要指向链表中某个结点的指针,就可以操纵链表了,岂不是操作链表变得简单多了?!这些思想很重要,因为链表在内核中使用十分频繁,必须高效!据说黑客们对此设计十分自豪。~_~

2.2、为什么没有数据域

可能你会好奇,为什么没有数据域?数据存储到哪里?

解析:链表仅仅是一个链接你数据的工具,它不依赖与任何数据类型,具有通用性,自身不包含数据域。数据域由你的结构体而定,而你仅仅需要在自己的结构体内,嵌入struct list_head实例,就给自己数据插上了链接的“双臂”。

3、双链表的定义

<linux/list.h>

3.1、静态声明和初始化

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #define LIST_HEAD_INIT(name) { &(name), &(name) }  
  2.   
  3. #define LIST_HEAD(name) \  
  4.     struct list_head name = LIST_HEAD_INIT(name)  

LIST_HEAD_INIT(name)负责初始化,使得表结点的指针域指向自身,达到初始化为空的目的。

LIST_HEAD(name)定义一个名位name的双链表,并初始化位为空

3.2、动态初始化

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. static inline void INIT_LIST_HEAD(struct list_head *list)  
  2. {  
  3.     list->next = list;  
  4.     list->prev = list;  
  5. }  

4、添加结点

4.1、内部添加结点函数

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. static inline void __list_add(struct list_head *new,  
  2.                   struct list_head *prev,  
  3.                   struct list_head *next)  
  4. {  
  5.     next->prev = new;  
  6.     new->next = next;  
  7.     new->prev = prev;  
  8.     prev->next = new;  
  9. }  
此方法只使用于内部链表操作,并且已知prev、next结点

由于三个结点都有指针指向,因此这4步操作可以任意互换,而不影响操作的成功性。

4.2、指定结点之后插入新结点

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  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.   
  10. static inline void list_add(struct list_head *newstruct list_head *head)  
  11. {  
  12.     __list_add(new, head, head->next);//注意即使空表此操作也能成功,如下图  
  13. }  

4.3、指定结点之前插入新结点

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  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.  */  
  9. static inline void list_add_tail(struct list_head *newstruct list_head *head)  
  10. {  
  11.     __list_add(new, head->prev, head);//同上,空表也能正确执行  
  12. }  

注意和4.2的区分,差别就在于参数传递

__list_add(new, head, head->next);

__list_add(new, head->prev, head);

5、删除结点

5.1、内部删除结点函数

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  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.  */  
  8. static inline void __list_del(struct list_head * prev, struct list_head * next)  
  9. {  
  10.     next->prev = prev;  
  11.     prev->next = next;  
  12. }  
  13. static inline void __list_del_entry(struct list_head *entry)  
  14. {  
  15.     __list_del(entry->prev, entry->next);  
  16. }  

注意这个操作是内部函数,并没有对entry的指针域做任何处理

5.2、删除结点——特殊处理结点指针

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. static inline void list_del(struct list_head *entry)  
  2. {  
  3.     __list_del(entry->prev, entry->next);  
  4.     entry->next = LIST_POISON1;  
  5.     entry->prev = LIST_POISON2;  
  6. }  

 LIST_POISON1,2在<linux/position.h>中,它们是个常量,有内核配置时决定

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #ifdef CONFIG_ILLEGAL_POINTER_VALUE  
  2. #define POISON_POINTER_DELTA  _AC(CONFIG_ILLEGAL_POINTER_VALUE,UL)  
  3. #else  
  4. #define POISON_POINTER_DELTA 0  
  5. #endif  
  6. /* 
  7.  * These are non-NULL pointers that will result in page faults 
  8.  * under normal circumstances, used to verify that nobody uses 
  9.  * non-initialized list entries. 
  10.  */  
  11. #define LIST_POISON1  ((void *) 0x00100100 + POISON_POINTER_DELTA)  
  12. #define LIST_POISON2  ((void *) 0x00200200 + POISON_POINTER_DELTA)  

其中_AC在<linux/const.h>,是一条件编译,如果定义了__ASSEMBLY__则_AC等于什么都没有做;否则,把_AC的两个参数拼接在一起成为一个整体标识符

5.3、删除结点——初始化被删除结点

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * list_del_init - deletes entry from list and reinitialize it. 
  3.  * @entry: the element to delete from the list. 
  4.  */  
  5. static inline void list_del_init(struct list_head *entry)  
  6. {  
  7.     __list_del_entry(entry);  
  8.     INIT_LIST_HEAD(entry);  
  9. }  

这样确保删除后结点与链表的完全脱离,而且使得entry能成为一个独立的链表且已经初始化,可以执行进一步的操作。

6、结点的替换

6.1、结点替换操作

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * list_replace - replace old entry by new one 
  3.  * @old : the element to be replaced 
  4.  * @new : the new element to insert 
  5.  * 
  6.  * If @old was empty, it will be overwritten. 
  7.  */  
  8. static inline void list_replace(struct list_head *old,  
  9.                 struct list_head *new)  
  10. {  
  11.     new->next = old->next;  
  12.     new->next->prev = new;  
  13.     new->prev = old->prev;  
  14.     new->prev->next = new;  
  15. }  

6.2、替换结点

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. static inline void list_replace_init(struct list_head *old,  
  2.                     struct list_head *new)  
  3. {  
  4.     list_replace(old, new);  
  5.     INIT_LIST_HEAD(old);//它的作用是把上图中(4)操作结束后,撤销原来指针使之安全  
  6. }  

7、结点的移动

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  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.  */  
  6. static inline void list_move(struct list_head *list, struct list_head *head)  
  7. {  
  8.     __list_del_entry(list);  
  9.     list_add(list, head);  
  10. }  

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  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.  */  
  6. static inline void list_move_tail(struct list_head *list,  
  7.                   struct list_head *head)  
  8. {  
  9.     __list_del_entry(list);  
  10.     list_add_tail(list, head);  
  11. }  


原创粉丝点击