内核数据结构之——链表

转载请注明出处:http://blog.csdn.net/hxcpp

看内核代码时经常会遇到一些数据结构,包括链表,散列表,树等。之前遇到,仅仅只是从原理上了解大概是干嘛用的,没有深入详细分析代码。这里我想逐步就一些常用的数据结构进行总结,一些代码设计颇为精妙,对于自己编程也会有很大帮助。

1链表

1.1基本链表结构

链表是Linux内核中最简单也是最常用的数据结构。简单的链表结构形如:

单链表


双链表


循环链表(分别是单向循环链表和双向循环链表):


尽管Linux中链表实现十分独特,但本质上是一个双向循环链表。

1.2Linux中链表的实现

自己之前写代码时,定义链表结点均类似上面所示,即将所需的数据结构包含在链表节点中。但是在Linux中,有很多数据结构需要使用链表将其串连,如,task_structsuper_block等等。这样如果按照传统的设计,对于每个链表,必须分别实现一组基本操作:初始化链表,插入删除元素,扫描链表等。这可能既浪费开发人员的精力,也因为对每个不同的链表都要重复相同的基本操作而造成存储空间的浪费。

为此,Linux将链表节点嵌入到需要形成链式结构的数据结构中。内核定义了list_head数据结构,如下所示:

228 "include/linux/types.h"
229 struct list_head { 
230     struct list_head *next, *prev; 
231 }; 

显然,nextprev分别指向下一个和前一个list_head的地址,而并非包含list_head整个数据结构的地址。如下图所示:


那么,这样的list_head是如何使用的呢,也就是如何通过指向list_headnext\prev指针,来获取包含list_head的整个数据结构的地址?

Linux为此提供了一个list_entry(ptr,type,member)宏,其中ptr是指向该数据中list_head成员的指针,也就是存储在链表中的地址值,type是数据项的类型,member则是数据项类型定义中list_head成员的变量名。

例如:定义了如下一个结构

struct fox{

struct list_head list;

}fox_a;

现有一个

struct list_head *p; //p指向fox_a中的list

struct fox *f;

f可以这样获取fox_a的地址:

f=list_entry(p,struct fox, list);

下面具体看看其实现:

343 "include/linux/list.h"
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) 

由于在C语言中,一个数据结构分量的偏移地址在编译时就已经确定,则通过container_of()宏,能够很容易获取包含任意成员变量的父数据结构:

672 "include/linux/kernel.h"
673 /** 
674  * container_of - cast a member of a structure out to the containing structure 
675  * @ptr:    the pointer to the member. 
676  * @type:   the type of the container struct this is embedded in. 
677  * @member: the name of the member within the struct. 
678  * 
679  */ 
680 #define container_of(ptr, type, member) ({          \ 
681     const typeof( ((type *)0)->member ) *__mptr = (ptr);    \ 
682     (type *)( (char *)__mptr - offsetof(type,member) );}) 

其中,offsetof定义如下("include/linux/stddef.h"):

20 #define offsetof(TYPE, MEMBER)   ((size_t) &((TYPE *)0)->MEMBER) 

size_t 最终定义为unsigned longi386)。

这里使用的是一个利用编译器技术的小技巧,即先求得结构成员在与结构中的偏移量,然后根据成员变量的地址反过来得出属主结构变量的地址。

container_of()offsetof()并不仅用于链表操作,这里最有趣的地方是

((type*)0)->member,它将0地址强制"转换"type结构的指针,再访问到type结构中的member成员。在container_of宏中,它用来给typeof()提供参数(typeof()gcc的扩展,和sizeof()类似),以获得member成员的数据类型;在offsetof()中,这个member成员的地址实际上就是type数据结构中member成员相对于结构变量的偏移量。(看,符号&用于取地址,这里由于((TYPE*)0)的作用,也就是结构变量起始地址为0,那么获得MEMBER的地址当然是偏移值了。size_t再将地址类型强制转换为整数类型,用于减法)

***这样看上面的式子还是有些怪异,比如就前面f=list_entry(p,struct fox, list); 这句话来说,宏展开后即为

 f=({  const typeof( (( struct fox *)0)->list)  * __mptr = (p);   
		( struct fox *)( (char *)__mptr – offsetof( struct fox,list) );
        });

上面这个赋值语句,其右值为一个复合语句,问题就是既然复合语句能够赋值给f,那么这个复合语句的值又是多少呢?复合语句能作为右值出现么?我查了一下资料:(http://blog.csdn.net/npy_lp/article/details/7015066GCCC的扩展,有机会需要详细看看语法,现在就不管这么多吧。

如下:

GNUC中,允许用小括号括起来的复合语句出现在一个表达式中

例如下面程序:

运行结果:

因此,可以知道f的值实际上为该复合语句最后一句话的值,该语句返回一个struct fox *类型值。这样,所有问题应该都弄清楚了吧。

能够从嵌入的list_head类型变量的地址获取包含它的数据结构的地址,剩下的问题就好办了,这样便可以根据list_head定义一组关于链表的基本操作(Armed with list_entry(), the kernel provides routines to create,manipulate, and otherwise manage linked lists—all without knowinganything about the structures that the list_head resides within.):

1.3list_head相关操作("include/linux/list.h"

(1)定义链表(声明和初始化)

实际上Linux只定义了链表节点,并没有专门定义链表头,那么一个链表结构是如何建立起来的呢?让我们来看看LIST_HEAD()这个宏:

 18 
 19 #define LIST_HEAD_INIT(name) { &(name), &(name) }
 20 
 21 #define LIST_HEAD(name) \
 22     struct list_head name = LIST_HEAD_INIT(name)

LIST_HEAD(name) 定义了类型为list_head的新变量name,该变量作为新链表头的占位符,是一个哑元素,同时初始化了prevnext字段,让它们指向name变量本身,见前面图3-3

对于很多包含list_head的数据结构,需要在动态运行时初始化,此时需要使用INIT_LIST_HEAD()内联函数进行初始化,如下:

24 static inline void INIT_LIST_HEAD(struct list_head *list)
 25 {
 26     list->next = list;
 27     list->prev = list;
 28 }

2)判空

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 }

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 }

3)插入/删除/移动/替换/合并/旋转

a)插入

对链表的插入操作有两种:在指定节点后插入和之前插入。Linux为此提供了两个接口:

60 static inline void list_add(struct list_head *new, struct list_head *head)
 61 {   
 62     __list_add(new, head, head->next);
 63 }

 74 static inline void list_add_tail(struct list_head *new, struct list_head *head)
 75 {
 76     __list_add(new, head->prev, head);
 77 }

因为Linux链表是双向循环表,所以,list_addlist_add_tail的区别并不大,实际上,Linux最后均调用__list_add()函数:

30 /*
 31  * Insert a new entry between two known consecutive entries.
 32  *
 33  * This is only for internal list manipulation where we know
 34  * the prev/next entries already!
 35  */
 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 }

b )删除

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 }


同时需要注意,list_del不会释放任何属于entry或者它所在数据结构的内存空间,仅仅是将entry元素从链表上摘下来:

设置LIST_POISON1LIST_POISON2是为了保证不在链表中的节点项不可访问——对LIST_POSITION1LIST_POSITION2的访问都将引起缺页。

86 static inline void __list_del(struct list_head * prev, struct list_head * next)
 87 {
 88     next->prev = prev;
 89     prev->next = next;
 90 }


同时,内核提供了这样一个函数:

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 }


这样的好处是可以重利用从链表上删除的元素。


c)移动

很简单,如下:
153 static inline void list_move(struct list_head *list, struct list_head *head)
154 {
155     __list_del_entry(list);
156     list_add(list, head);
157 }

164 static inline void list_move_tail(struct list_head *list,
165                   struct list_head *head)
166 {
167     __list_del_entry(list);
168     list_add_tail(list, head);
169 }

d) 替换
115 /**
116  * list_replace - replace old entry by new one
117  * @old : the element to be replaced
118  * @new : the new element to insert
119  *
120  * If @old was empty, it will be overwritten.
121  */
122 static inline void list_replace(struct list_head *old,
123                 struct list_head *new)
124 {
125     new->next = old->next;
126     new->next->prev = new;
127     new->prev = old->prev;
128     new->prev->next = new;
129 }
130 
131 static inline void list_replace_init(struct list_head *old,
132                     struct list_head *new)
133 {
134     list_replace(old, new);
135     INIT_LIST_HEAD(old);
136 }

e )合并
287 /**
288  * list_splice - join two lists, this is designed for stacks
289  * @list: the new list to add.
290  * @head: the place to add it in the first list.
291  */
292 static inline void list_splice(const struct list_head *list,
293                 struct list_head *head)
294 {
295     if (!list_empty(list))
296         __list_splice(list, head, head->next);
297 }

299 /**
300  * list_splice_tail - join two lists, each list being a queue
301  * @list: the new list to add.
302  * @head: the place to add it in the first list.
303  */
304 static inline void list_splice_tail(struct list_head *list,
305                 struct list_head *head)
306 {
307     if (!list_empty(list))
308         __list_splice(list, head->prev, head);
309 }

273 static inline void __list_splice(const struct list_head *list,
274                  struct list_head *prev,
275                  struct list_head *next)
276 {
277     struct list_head *first = list->next;
278     struct list_head *last = list->prev;
279 
280     first->prev = prev;
281     prev->next = first;
282 
283     last->next = next;
284     next->prev = last;
285 }

类似的,还有
318 static inline void list_splice_init(struct list_head *list,
319                     struct list_head *head)
320 {
321     if (!list_empty(list)) {
322         __list_splice(list, head, head->next);
323         INIT_LIST_HEAD(list);
324     }
325 }

335 static inline void list_splice_tail_init(struct list_head *list,
336                      struct list_head *head)
337 {
338     if (!list_empty(list)) {
339         __list_splice(list, head->prev, head);
340         INIT_LIST_HEAD(list);
341     }
342 }

f )旋转
210 /**
211  * list_rotate_left - rotate the list to the left
212  * @head: the head of the list
213  */
214 static inline void list_rotate_left(struct list_head *head)
215 {
216     struct list_head *first;
217 
218     if (!list_empty(head)) {
219         first = head->next;
220         list_move_tail(first, head);
221     }
222 }

g )其他
224 /**
225  * list_is_singular - tests whether a list has just one entry.
226  * @head: the list to test.
227  */
228 static inline int list_is_singular(const struct list_head *head)
229 {
230     return !list_empty(head) && (head->next == head->prev);
231 }
232 
233 static inline void __list_cut_position(struct list_head *list,
234         struct list_head *head, struct list_head *entry)
235 {
236     struct list_head *new_first = entry->next;
237     list->next = head->next;
238     list->next->prev = list;
239     list->prev = entry;
240     entry->next = list;
241     head->next = new_first;
242     new_first->prev = head;
243 }

245 /**
246  * list_cut_position - cut a list into two
247  * @list: a new list to add all removed entries
248  * @head: a list with entries
249  * @entry: an entry within head, could be the head itself
250  *  and if so we won't cut the list
251  *
252  * This helper moves the initial part of @head, up to and
253  * including @entry, from @head to @list. You should
254  * pass on @entry an element you know is on @head. @list
255  * should be an empty list or a list you do not care about
256  * losing its data.
257  *
258  */
259 static inline void list_cut_position(struct list_head *list,
260         struct list_head *head, struct list_head *entry)
261 {
262     if (list_empty(head))
263         return;
264     if (list_is_singular(head) &&
265         (head->next != entry && head != entry))
266         return;
267     if (entry == head)
268         INIT_LIST_HEAD(list);
269     else
270         __list_cut_position(list, head, entry);
271 }

(4) 遍历

遍历是链表最经常的操作之一,为了方便核心应用遍历链表,Linux链表将遍历操作抽象成几个宏。

最基本的当然是list_for_each()宏,如下:

364 /**
365  * list_for_each    -   iterate over a list
366  * @pos:    the &struct list_head to use as a loop cursor.
367  * @head:   the head for your list.
 头节点
368  */
369 #define list_for_each(pos, head) \
370     for (pos = (head)->next; pos != (head); pos = pos->next)

当然,遍历链表的目的是为了获取包含list_head结构的数据结构,那么此时可以这样使用,如下:
static LIST_HEAD(fox_list);
	….
struct list_head *p;
struct fox *f;

list_for_each(p, &fox_list){
	f=list_entry(p,struct fox,list);
}

大多数内核使用list_for_each_entry()宏来遍历链表
412 /**
413  * list_for_each_entry  -   iterate over list of given type
414  * @pos:    the type * to use as a loop cursor.
415  * @head:   the head for your list.
416  * @member: the name of the list_struct within the struct.
417  */
418 #define list_for_each_entry(pos, head, member)              \
419     for (pos = list_entry((head)->next, typeof(*pos), member);  \
420          &pos->member != (head);    \
421          pos = list_entry(pos->member.next, typeof(*pos), member))
422 

某些应用需要反向遍历链表,Linux提供了list_for_each_prev()list_for_each_entry_reverse()来完成这一操作,使用方法和上面介绍的list_for_each()list_for_each_entry()完全相同。

如果遍历不是从链表头开始,而是从已知的某个节点pos开始,则可以使用list_for_each_entry_continue(pos,head,member)。有时还会出现这种需求,即经过一系列计算后,如果pos有值,则从pos开始遍历,如果没有,则从链表头开始,为此,Linux专门提供了一个list_prepare_entry(pos,head,member)宏,将它的返回值作为list_for_each_entry_continue()pos参数,就可以满足这一要求。

遍历时节点删除

前面介绍了用于链表遍历的几个宏,它们都是通过移动pos指针来达到遍历的目的。但如果遍历的操作中包含删除pos指针所指向的节点,pos指针的移动就会被中断,因为list_del(pos)将把posnextprev置成LIST_POSITION2LIST_POSITION1的特殊值。

当然,调用者完全可以自己缓存next指针使遍历操作能够连贯起来,但为了编程的一致性,Linux链表仍然提供了两个对应于基本遍历操作的"_safe"接口:list_for_each_safe(pos,n, head)list_for_each_entry_safe(pos,n, head,member),它们要求调用者另外提供一个与pos同类型的指针n,在for循环中暂存pos下一个节点的地址,避免因pos节点被释放而造成的断链。

391 /**
392  * list_for_each_safe - iterate over a list safe against removal of list entry
393  * @pos:    the &struct list_head to use as a loop cursor.
394  * @n:      another &struct list_head to use as temporary storage
395  * @head:   the head for your list.
396  */
397 #define list_for_each_safe(pos, n, head) \
398     for (pos = (head)->next, n = pos->next; pos != (head); \
399         pos = n, n = pos->next)

485 /**
486  * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
487  * @pos:    the type * to use as a loop cursor.
488  * @n:      another type * to use as temporary storage
489  * @head:   the head for your list.
490  * @member: the name of the list_struct within the struct.
491  */
492 #define list_for_each_entry_safe(pos, n, head, member)          \
493     for (pos = list_entry((head)->next, typeof(*pos), member),  \
494         n = list_entry(pos->member.next, typeof(*pos), member); \
495          &pos->member != (head);                    \
496          pos = n, n = list_entry(n->member.next, typeof(*n), member))

对于其他一些定义的宏或者函数,功能和作用也都类似,这里就不再分析了,Linux链表这块,差不多就结束了吧,以后如果发现有意思的新内容再添加吧。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值