linux内核的链表机制

/*本篇由真胖子同志打造,原创辛苦,转载请标明出处http://blog.csdn.net/figtingforlove/article/details/20124267*/

(一)双链表

内核几乎每个稍大的数据结构都会包含双链表结构,用于管理内核的各个数据结构,内核为实现双链表提供了若干通用函数和结构,方便程序设计时使用。

1.双链表的定义

图2 双链表

linux/types.h

185 struct list_head {
186         struct list_head *next, *prev;//前驱和后继节点指针
187 };

2.双链表的初始化(指向自己)

linux/list.h

/*静态初始化宏*/ 
19 #define LIST_HEAD_INIT(name) { &(name), &(name) } 
20 
21 #define LIST_HEAD(name) \ 
22         struct list_head name = LIST_HEAD_INIT(name) 
23 /*动态初始化函数*/  
24 static inline void INIT_LIST_HEAD(struct list_head *list) 
25 { 
26         list->next = list; 
27         list->prev = list; 
28 }

3.插入(add a new entry)

37 static inline void __list_add(struct list_head *new,
 38                               struct list_head *prev,
 39                               struct list_head *next)
 40 {
 41         next->prev = new;
 42         new->next = next;
 43         new->prev = prev;
 44         prev->next = new;
 45 }
 /* 插入到链表头*/
 52 /**
 53  * list_add - add a new entry
 54  * @new: new entry to be added
 55  * @head: list head to add it after
 56  *
 57  * Insert a new entry after the specified head.
 58  * This is good for implementing stacks.
 59  */
 60 static inline void list_add(struct list_head *new, struct list_head *head)
 61 {
 62         __list_add(new, head, head->next);//(新节点,新节点的前驱,新节点的后继
 63 }

 /*插入到链表尾*/

 66 /**
 67  * list_add_tail - add a new entry
 68  * @new: new entry to be added
 69  * @head: list head to add it before
 70  *
 71  * Insert a new entry before the specified head.
 72  * This is useful for implementing queues.
 73  */
 74 static inline void list_add_tail(struct list_head *new, struct list_head *head)
 75 {
 76         __list_add(new, head->prev, head);
 77 }

4.删除

80 /* Delete a list entry by making the prev/next entries
 81  * point to each other.
 82  *
 83  * This is only for internal list manipulation where we know
 84  * the prev/next entries already!
 85  */
 86 static inline void __list_del(struct list_head * prev, struct list_head * next)
 87 {
 88         next->prev = prev;
 89         prev->next = next;
 90 }
 91 
 92 /**
 93  * list_del - deletes entry from list.
 94  * @entry: the element to delete from the list.
 95  * Note: list_empty() on entry does not return true after this, the entry is
 96  * in an undefined state.
 97  */
 98 #ifndef CONFIG_DEBUG_LIST
 99 static inline void __list_del_entry(struct list_head *entry)
100 {
101         __list_del(entry->prev, entry->next);
102 }
103 
104 static inline void list_del(struct list_head *entry)
105 {
106         __list_del(entry->prev, entry->next);
107         entry->next = LIST_POISON1;
108         entry->prev = LIST_POISON2;
109 }
110 #else
/*没有上面的配置选项,__list_del_entry函数会有一些告警信息的输出,但函数的主体是一样的*/
111 extern void __list_del_entry(struct list_head *entry);
112 extern void list_del(struct list_head *entry);
113 #endif

上面有两个宏定义的常数值LIST_POISON1和LIST_POISON2,这是为了确保不能使用已经删除的节点,内核做了如下定义

7 /* Architectures might want to move the poison pointer offset
  8  * into some well-recognized area such as 0xdead000000000000,
  9  * that is also not mappable by user-space exploits:
 10  */
 11 #ifdef CONFIG_ILLEGAL_POINTER_VALUE
 12 # define POISON_POINTER_DELTA _AC(CONFIG_ILLEGAL_POINTER_VALUE, UL)
 13 #else
 14 # define POISON_POINTER_DELTA 0
 15 #endif
 16 
 17 /*
 18  * These are non-NULL pointers that will result in page faults
 19  * under normal circumstances, used to verify that nobody uses
 20  * non-initialized list entries.
 21  */
 22 #define LIST_POISON1  ((void *) 0x00100100 + POISON_POINTER_DELTA)
 23 #define LIST_POISON2  ((void *) 0x00200200 + POISON_POINTER_DELTA)

可见这两个值代表内存位置 0x00100100 和 0x00200200,其实相当于一个内存位置标记。
在内核中0xC0000000以下(内存为4G,高地址1G作为内核空间使用,低地址3G作为用户空间使用,此3G内核空间申请不到)的地址内核是不能申请到的。为了防止有的节点申请内存的时候使用不可能被初始化的指针来当作标记比如NULL,而0x00200200 这样的低地址是内核空间申请内存不会出现的地址可以拿来当标记。因为0x00200200不能被内核申请,所以当我们在内核空间引用该指针时,就会出现BUG。
把entry->prev 设置为 LIST_POISON2,这样做有一个好处,就是:当你一旦不小心错误的引用了 entry->prev 这个指针的时候,就会打印如下的出错信息:Unable to handle kernel paging request at virtual address 00200200这样由出错信息中的“00200200”,你就可以知道是错误的引用了已经被删除了的链表节点了!相反,如果把entry->prev 设置为 NULL,当你一旦不小心错误的引用了 entry->prev 这个指针的时候,打印的出错信息如下:Unable to handle kernel paging request at virtual address 00000000这样我们从出错地址“00000000”看不出出错的原因是错误的引用了已经被删除了的链表节点,因为NULL在很多地方都会被用到。
此外,内核还定义了另一个删除节点的函数

138 /**
139  * list_del_init - deletes entry from list and reinitialize it.
140  * @entry: the element to delete from the list.
141  */
142 static inline void list_del_init(struct list_head *entry)
143 {
144         __list_del_entry(entry);
145         INIT_LIST_HEAD(entry);
146 }

这个函数在删除了链表节点后,对她又进行了初始化。这样就不会访问到未初始化的地址而出现错误,一般我们用到的大多是list_del 而不是 list_del_init。

5.判空

182 /**
183  * list_empty - tests whether a list is empty
184  * @head: the list to test.
185  */
186 static inline int list_empty(const struct list_head *head)
187 {
188         return head->next == head;
189 }
190 
/*
不安全方式(并行时)判断链表是否为空
*/
191 /**
192  * list_empty_careful - tests whether a list is empty and not being modified
193  * @head: the list to test
194  *
195  * Description:
196  * tests whether a list is empty _and_ checks that no other CPU might be
197  * in the process of modifying either member (next or prev)
198  *
199  * NOTE: using list_empty_careful() without synchronization
200  * can only be safe if the only activity that can happen
201  * to the list entry is list_del_init(). Eg. it cannot be used
202  * if another CPU could re-list_add() it.
203  */
204 static inline int list_empty_careful(const struct list_head *head)
205 {
206         struct list_head *next = head->next;
207         return (next == head) && (next == head->prev);
208 }

6.遍历查找

内核提供了许多函数和宏用于链表的遍历,非常方便我们安全的使用/*取链表中的一项*/

344 /**
345  * list_entry - get the struct for this entry
346  * @ptr:        the &struct list_head pointer.指向结构体中成员的指针
347  * @type:       the type of the struct this is embedded in.结构体的类型  
348  * @member:     the name of the list_struct within the struct.这个成员在结构体中的名字
349  */
350 #define list_entry(ptr, type, member) \
351         container_of(ptr, type, member)
/*从结构体的成员来获得包含成员的结构体实例,这个宏在内核中的应用非常广泛*/

791 #define container_of(ptr, type, member) ({                      \
792         const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
793         (type *)( (char *)__mptr - offsetof(type,member) );})

((type *)0)表示将空指针转换为type类型的指针,也就是该结构体类型的指针,这是允许的因为我们并不反引用该指针。

lixnux/kernel.h中这样定义offset宏

14 #ifndef offsetof
15 #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
16 #endif

实际就是取MEMBER成员的相对地址,因为当TYPE类型的结构体地址为0时,取其地址&((TYPE *)0)->MEMBER就是它的相对偏移量。

定义了一个指向该成员类型的指针__mptr,将它转换为char*指针相当于指向字节类型的地址,再减去MEMBER的偏移量得到的就是TYPE类型结构体的首字节指针,然后把这指针转回TYPE类型,perfect!
6.1遍历一個链表的方法

402 /**
403  * list_for_each        -       iterate over a list
404  * @pos:        the &struct list_head to use as a loop cursor.
405  * @head:       the head for your list.
406  */
407 #define list_for_each(pos, head) \
408         for (pos = (head)->next; pos != (head); pos = pos->next)

还有另一个安全的方法

418 /**
419  * list_for_each_safe - iterate over a list safe against removal of list entry
420  * @pos:        the &struct list_head to use as a loop cursor.
421  * @n:          another &struct list_head to use as temporary storage
422  * @head:       the head for your list.
423  */
424 #define list_for_each_safe(pos, n, head) \
425         for (pos = (head)->next, n = pos->next; pos != (head); \
426                 pos = n, n = pos->next)

两者的区别在于,list_for_each_safe缓存了pos->next,这个优势可以体现在删除链表想的时候。
当在遍历的过程中需要删除结点时,会出现什么情况
list_for_each():list_del(pos)将pos的prev/next指向undefine stat(这里指的是前面提到的LIST_POSITION1),这样下一次迭代的时候

pos = pos->next
pos将指向一个不可用的位置,导致kernel panic,出错;

list_del_init(pos)将pos的prev/next指向指向自身,这样下一次迭代的时候

pos = pos->next
pos将指向自身,导致死循环。
list_for_each_safe():首先将pos的next指针缓存到n,处理一个流程后再赋回pos,避免了这种情况发生。
因此之遍历链表不删除结点时,可以使用list_for_each(),而当有删除结点操作时,则要使用list_for_each_safe()。其他带safe的处理也是基于这个原因。
6.2遍历由链表项链接结构体

439 /**
440  * list_for_each_entry  -       iterate over list of given type
441  * @pos:        the type * to use as a loop cursor.
442  * @head:       the head for your list.
443  * @member:     the name of the list_struct within the struct.
444  */
445 #define list_for_each_entry(pos, head, member)                          \
446         for (pos = list_first_entry(head, typeof(*pos), member);        \
447              &pos->member != (head);                                    \
448              pos = list_next_entry(pos, member))

list_first_entry and list_next_entry

353 /**
354  * list_first_entry - get the first element from a list
355  * @ptr:        the list head to take the element from.
356  * @type:       the type of the struct this is embedded in.
357  * @member:     the name of the list_struct within the struct.
358  *
359  * Note, that list is expected to be not empty.
360  */
361 #define list_first_entry(ptr, type, member) \
362         list_entry((ptr)->next, type, member)

386 /**
387  * list_next_entry - get the next element in list
388  * @pos:        the type * to cursor
389  * @member:     the name of the list_struct within the struct.
390  */
391 #define list_next_entry(pos, member) \
392         list_entry((pos)->member.next, typeof(*(pos)), member)

(二)哈希链表
linux还提供了一个双线链表的修改版,非常适合实现散列表中的益处链表
详细分析请看博客http://blog.csdn.net/cwcmcw/article/details/17124205


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Linux内核使用链表来组织数据。内核链表是通过在[include/linux/list.h]中实现的链表数据结构来实现的。它采用双循环链表机制,每个节点只包含指针域而不包含数据域,这样就可以灵活地扩展数据结构。在内核链表中,list_head结构起着整个链表的衔接作用,它有两个指针域,分别指向下一个节点和上一个节点。初始化链表时,可以使用list_head结构来创建一个空链表。具体的链表操作包括插入节点、删除节点和遍历节点等,这些操作可以在linux内核源码中的list.h文件中找到详细的注释。请注意,链表的源码可能会有一些变化,所以使用时最好参考与你使用的内核版本相对应的源码。如果对链表的使用有任何问题或不正确之处,你可以通过发送邮件到2253238252@qq.com来向我反馈。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [linux内核链表应用](https://blog.csdn.net/qq_18376583/article/details/127353571)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [linux内核链表提取与使用](https://download.csdn.net/download/jiangming7/9370159)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值