在linux、xen系统内核源代码中大量链表都是通过struct list_head来实现的,其设计的巧妙,各种操作的便利,使得第一次使用就忍不住赞叹!~
在链表操作中,我们一般定义的数据结构可以分为两部分,数据域&&指针域,数据域是这一个节点中所应包含的数据,指针域用于处理各个节点之间的关系。
struct list_head的设计将指针操作单独剥离出来,进行了很好的封装实现,使得用户不必每次使用链表的时候都自己维护指针,重复的编写链表操作代码。struct list_head的定义如下:
30 struct list_head {
31 struct list_head *next, *prev;
32 };
先看一个xen内核中cpu调度使用struct list_head的一个例子:
155 /*
156 * Physical CPU
157 */
158 struct csched_pcpu {
159 struct list_head runq; /*对应物理cpu的运行队列头*/
......................
163 };
/*
* 在xen虚拟化的情况下,物理cpu上运行的是vcpu
* (要是觉得vcpu概念不懂,可以暂时就理解为进程)
*/
165 /*
166 * Virtual CPU
167 */
168 struct csched_vcpu {
169 struct list_head runq_elem; /* 运行队列元素 */
..............................
192 };
链表结构如图所示:
初始化:
物理CPU
844 static int
845 csched_pcpu_init(int cpu)
846 {
847 struct csched_pcpu *spc;
.........................................
849
850 /* Allocate per-PCPU info */
851 spc = xmalloc(struct csched_pcpu); /* 申请空间 */
852 if ( spc == NULL )
853 return -1;
..........................................
865 INIT_LIST_HEAD(&spc->runq); /* 初始化运行队列 */
..........................................
}
VCPU初始化
1158 static int
1159 csched_vcpu_init(struct vcpu *vc)
1160 {
..............................
1163 struct csched_vcpu *svc;
..............................
1168
1169 /* Allocate per-VCPU info */
1170 svc = xmalloc(struct csched_vcpu); /* 申请空间 */
1171 if ( svc == NULL )
1172 return -1;
1173
1174 INIT_LIST_HEAD(&svc->runq_elem); /* 初始化指针域 */
...............................
1190}
/*将VCPU插入到对应PCPU运行队列中*/
754 static inline void
755 __runq_insert(unsigned int cpu, struct csched_vcpu *svc)
756 {
757 const struct list_head * const runq = RUNQ(cpu); /*获取对应cpu的运行队列*/
758 struct list_head *iter;
759
760 BUG_ON( __vcpu_on_runq(svc) );
761 BUG_ON( cpu != svc->vcpu->processor );
762
763 list_for_each( iter, runq ) /*遍历runq,从runq->next开始,每次循环之后iter指向下一个,直到下一个是runq*/
764 {
765 const struct csched_vcpu * const iter_svc = __runq_elem(iter); /*获取runq中VCPU节点的地址*/
766 if ( svc->pri > iter_svc->pri ) /*判断svc的优先级是否比当前iter所在vcpu的优先级大*/
767 break;
768 }
769
770 list_add_tail(&svc->runq_elem, iter); /*将svc插入到第一个优先级比它小的vcpu的前面*/
771 }
/*根据list_head指针返回对应结构的指针*/
748 static inline struct csched_vcpu *
749 __runq_elem(struct list_head *elem)
750 {
/*
* elem为当前vcpu节点中用于链接前后两个节点的指针, struct csched_vcpu
* 为数据节点的type,runq_elem为结构体中对应的指针域
*/
751 return list_entry(elem, struct csched_vcpu, runq_elem);
752 }
/* 删除运行队列中该节点 */
773 static inline void
774 __runq_remove(struct csched_vcpu *svc)
775 {
776 BUG_ON( !__vcpu_on_runq(svc) );
777 list_del_init(&svc->runq_elem);
778}
/* 打印对应物理cpu的运行队列 */
2177 static void
2178 csched_dump_pcpu(int cpu)
2179 {
2180 struct list_head *runq, *iter;
2181 struct csched_pcpu *spc;
2182 struct csched_vcpu *svc;
2183 int loop;
..............................................
2186 spc = CSCHED_PCPU(cpu); /*获取物理cpu对应的结构指针*/
2187 runq = &spc->runq; /*获取物理cpu上的运行队列*/
...........................................
2193
2194 /* current VCPU */
2195 svc = CSCHED_VCPU(per_cpu(schedule_data, cpu).curr);
2196 if ( svc )
2197 {
2198 printk("\trun: ");
2199 csched_dump_vcpu(svc);
2200 }
2201
2202 loop = 0;
2203 list_for_each( iter, runq ) /*遍历runq*/
2204 {
2205 svc = __runq_elem(iter); /*获取对应数据节点指针*/
2206 if ( svc )
2207 {
2208 printk("\t%3d: ", ++loop);
2209 csched_dump_vcpu(svc);
2210 }
2211 }
2212 }
/* 自己修改内核写的一个函数,功能是移除当前运行队列中的所有vcpu */
587 static int
588 remove_cur_runq(int cpu)
589 {
590 struct list_head * const runq = RUNQ(cpu);
591 struct list_head * iter, *iter_next;
592 struct csched_vcpu *svc;
....................
/* 此处遍历链表用的是list_for_each_safe,不能用list_for_each来遍历,原因稍后解释 */
597 list_for_each_safe( iter, iter_next, runq )
598 {
599 svc = __runq_elem(iter);
610 __runq_remove(svc);
611 }
............................
615 return 0;
616 }
通过这个简单例子,可以体验到使用list_head单独将链表操作分离之后给我们所带来使用上的各种便利。下面总结下常用的涉及链表的操作及其实现:
/* 初始化list节点为表头,将前趋与后继都指向自己 */
39 static inline void INIT_LIST_HEAD(struct list_head *list)
40 {
41 list->next = list;
42 list->prev = list;
43 }
/* 将new节点插入到head的后一个位置 */
69 static inline void list_add(struct list_head *new, struct list_head *head)
70 {
71 __list_add(new, head, head->next);
72 }
/* 将new节点插入到head的前一个位置 */
82 static inline void list_add_tail(struct list_head *new, struct list_head *head)
83 {
84 __list_add(new, head->prev, head);
85 }
/* __list_add是一个非对外接口,用于将new节点插入到prev和next中间 */
51 static inline void __list_add(struct list_head *new,
52 struct list_head *prev,
53 struct list_head *next)
54 {
55 next->prev = new;
56 new->next = next;
57 new->prev = prev;
58 prev->next = new;
59 }
/* 从链表中移除entry节点,并重新初始化指针域指向节点自己 */
251 static inline void list_del_init(struct list_head *entry)
252 {
253 __list_del(entry->prev, entry->next);
254 INIT_LIST_HEAD(entry);
255 }
/* 非对外接口,用于将prev和next链接起来 */
154 static inline void __list_del(struct list_head *prev,
155 struct list_head *next)
156 {
157 next->prev = prev;
158 prev->next = next;
159 }
/* 使用new节点替换当前的old节点 */
212 static inline void list_replace(struct list_head *old,
213 struct list_head *new)
214 {
215 new->next = old->next;
216 new->next->prev = new;
217 new->prev = old->prev;
218 new->prev->next = new;
219 }
/* 判断当前链表是否为空 */
295 static inline int list_empty(const struct list_head *head)
296 {
297 return head->next == head;
298 }
/* 合并两个链表 */
333 /**
334 * list_splice - join two lists
335 * @list: the new list to add.
336 * @head: the place to add it in the first list.
337 */
338 static inline void list_splice(struct list_head *list, struct list_head *head)
339 {
340 if (!list_empty(list))
341 __list_splice(list, head);
342 }
/* 合并两个链表,并初始化被合并链表的头节点 */
344 /**
345 * list_splice_init - join two lists and reinitialise the emptied list.
346 * @list: the new list to add.
347 * @head: the place to add it in the first list.
348 *
349 * The list at @list is reinitialised
350 */
351 static inline void list_splice_init(struct list_head *list,
352 struct list_head *head)
353 {
354 if (!list_empty(list)) {
355 __list_splice(list, head);
356 INIT_LIST_HEAD(list);
357 }
358 }
/*
* 非对外接口,用于将list为头节点的链表合并到head为头结点的链表中,
* 插入位置为head->next 处
*/
319 static inline void __list_splice(struct list_head *list,
320 struct list_head *head)
321 {
322 struct list_head *first = list->next;
323 struct list_head *last = list->prev;
324 struct list_head *at = head->next;
325
326 first->prev = head;
327 head->next = first;
328
329 last->next = at;
330 at->prev = last;
331 }
/* 根据指针域指针获取对应结点指针 */
360 /**
361 * list_entry - get the struct for this entry
362 * @ptr: the &struct list_head pointer.
363 * @type: the type of the struct this is embedded in.
364 * @member: the name of the list_struct within the struct.
365 */
366 #define list_entry(ptr, type, member) \
367 container_of(ptr, type, member)
示例:__runq_elem函数中的 list_entry(elem, struct csched_vcpu, runq_elem)
struct csched_vcpu为返回的指针类型,runq_elem为指针域所在位置
container_of的实现如下:
36 /**
37 * container_of - cast a member of a structure out to the containing structu re
38 *
39 * @ptr: the pointer to the member.
40 * @type: the type of the container struct this is embedded in.
41 * @member: the name of the member within the struct.
42 *
43 */
44 #define container_of(ptr, type, member) ({ \
45 typeof( ((type *)0)->member ) *__mptr = (ptr); \
46 (type *)( (char *)__mptr - offsetof(type,member) );})
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
这个是整个中最巧妙的地方,如何根据结构类型以及结构中指针域得出指针域相对结构头的偏移呢?分析下上面这个宏,转换为示例就变成:
((size_t) &((struct csched_vcpu *)0)->runq_elem)
0首先被转换struct csched_vcpu的结构指针,相当于是伪造的一个节点头的地址为0,&((struct csched_vcpu *)0)->runq_elem)这个获取的是当前数据节点指针域所在的地址,由于节点头指针地址为0,因此此时数据节点指针域所在的地址就是指针域相对于节点头指针的偏移
/* 遍历链表 */
369 /**
370 * list_for_each - iterate over a list
371 * @pos: the &struct list_head to use as a loop cursor.
372 * @head: the head for your list.
373 */
374 #define list_for_each(pos, head) \
375 for (pos = (head)->next; prefetch(pos->next), pos != (head); \
376 pos = pos->next)
示例:
2203 list_for_each( iter, runq ) /*遍历runq*/
2204 {
........................
2211 }
宏扩展之后如下:
for (iter = (runq)->next; prefetch(iter->next), iter!=(runq); iter = iter->next )
{
..................
}
那么在含有节点删除操作时,这个宏为什么不安全呢? 在这个循环中我们没有保存下一个遍历位置的操作,如果删除当前iter指针所在节点并对指针域进行初始化,那么此时循环最后iter = iter->next操作就会出错,导致无法遍历链表。也因此出现了下面一个操作:
/* 在链表节点可能被删除的情况下,安全的遍历整个链表元素 */
400 /**
401 * list_for_each_safe - iterate over a list safe against removal of list entry
402 * @pos: the &struct list_head to use as a loop cursor.
403 * @n: another &struct list_head to use as temporary storage
404 * @head: the head for your list.
405 */
406 #define list_for_each_safe(pos, n, head) \
407 for (pos = (head)->next, n = pos->next; pos != (head); \
408 pos = n, n = pos->next)
ok,分析over~
转载于:https://blog.51cto.com/charlesxie/721274