写的很全面的一篇文章,手贱了,转了。
http://blog.chinaunix.net/uid-14114479-id-2932024.html
本文详解了内核中面向对象的list结构的原理,以及如何以list为内嵌对象来构造自己的链表结构,如何从内嵌list对象获得自定义的对象指针;探讨了各种宏或者函数的详细使用方法及怎样以通用list结构来操作自定义对象。
1.双循环链表传统实现
typedef struct foo { … struct foo *prev; struct foo *next; … } foo_t;
list_head结构 -----------struct list_head{}及初始化宏--------- struct list_head { struct list_head *next, *prev; };
--LIST_HEAD_INIT()--LIST_HEAD()--INIT_LIST_HEAD()------ #define LIST_HEAD_INIT(name) { &(name), &(name) } #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name)
#define INIT_LIST_HEAD(ptr) do { \ (ptr)->next = (ptr); (ptr)->prev = (ptr); \ } while (0)
static inline void INIT_LIST_HEAD(struct list_head *list) { list->next = list; list->prev = list; }
------------__list_add()------list_add()------list_add_tail()------- 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; } 普通的在两个非空结点中插入一个结点,注意new、prev、next都不能是空值。 Prev可以等于next,此时在只含头节点的链表中插入新节点。 static inline void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); } 在head和head->next两指针所指向的结点之间插入new所指向的结点。 即:在head指针后面插入new所指向的结点。Head并非一定为头结点。 当现有链表只含有一个头节点时,上述__list_add(new, head, head->next)仍然成立。 static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); }
-----------__list_del()---list_del()-----list_del_init()---------- static inline void __list_del(struct list_head * prev, struct list_head * next) { next->prev = prev; prev->next = next; } 在prev和next指针所指向的结点之间,两者互相所指。在后面会看到:prev为待删除的结点的前面一个结点,next为待删除的结点的后面一个结点。 static inline void list_del(struct list_head *entry) { __list_del(entry->prev, entry->next); entry->next = LIST_POISON1; entry->prev = LIST_POISON2; } 删除entry所指的结点,同时将entry所指向的结点指针域封死。 对LIST_POISON1,LIST_POISON2的解释说明: Linux 内核中解释:These are non-NULL pointers that will result in page faults under normal circumstances, used to verify that nobody uses non-initialized list entries. #define LIST_POISON1 ((void *) 0x00100100) #define LIST_POISON2 ((void *) 0x00200200) 常规思想是:entry->next = NULL; entry->prev = NULL; 保证不可通过该节点进行访问。 ---------------list_del_init()-------------------- static inline void list_del_init(struct list_head *entry) { __list_del(entry->prev, entry->next); INIT_LIST_HEAD(entry); } 删除entry所指向的结点,同时调用LIST_INIT_HEAD()把被删除节点为作为链表头构建一个新的空双循环链表。
-----------list_move()--list_move_tail()---------- static inline void list_move(struct list_head *list, struct list_head *head) { __list_del(list->prev, list->next); list_add(list, head); } 将list结点前后两个结点互相指向彼此,删除list指针所指向的结点,再将此结点插入head,和head->next两个指针所指向的结点之间。 即:将list所指向的结点移动到head所指向的结点的后面。 static inline void list_move_tail(struct list_head *list, struct list_head *head) { __list_del(list->prev, list->next); list_add_tail(list, head); } 删除了list所指向的结点,将其插入到head所指向的结点的前面,如果head->prev指向链表的尾结点的话,就是将list所指向的结点插入到链表的结尾。
---------------------list_empty()------------- static inline int list_empty(const struct list_head *head) { return head->next == head; } 测试链表是否为空,如果是只有一个结点,head,head->next,head->prev都指向同一个结点,则这里会返回1,表示空;但这个空不是没有任何结点,而是只有一个头结点,因为头节点只是纯粹的list节点,没有有效信息,故认为为空。 --------------------list_empty_careful()--------- static inline int list_empty_careful(const struct list_head *head) { struct list_head *next = head->next; return (next == head) && (next == head->prev); }注意:这里empty list是指只有一个空的头结点,而不是毫无任何结点。并且该头结点必须其head->next==head->prev==head.
---------------__list_splice()------------------ static inline void __list_splice(struct list_head *list, struct list_head *head) { struct list_head *first = list->next; struct list_head *last = list->prev; struct list_head *at = head->next; first->prev = head; head->next = first; last->next = at; at->prev = last; } 将一个非空链表插入到另外一个链表中。不作链表是否为空的检查,由调用者默认保证。因为每个链表只有一个头节点,将空链表插入到另外一个链表中是没有意义的。但被插入的链表可以是空的。 --------------------list_splice()---------------- /** * list_splice - join two lists * @list: 被合并的链表的头节点. * @head: the place to add it in the first list. */ static inline void list_splice(struct list_head *list, struct list_head *head) { if (!list_empty(list)) __list_splice(list, head); } 这种情况会丢弃list所指向的头结点,这是特意设计的,因为两个链表有两个头结点,要去掉一个头结点。只要list非空链,head无任何限制,该程序都可以实现链表合并。 --------------------list_splice_init()----------------------------------- /** * list_splice_init - join two lists and reinitialise the emptied list. * @list: the new list to add. * @head: the place to add it in the first list. * * The list at @list is reinitialised */ static inline void list_splice_init(struct list_head *list, struct list_head *head) { if (!list_empty(list)) 0 { __list_splice(list, head); INIT_LIST_HEAD(list); } } 将一个链表的有效信息合并到另外一个链表后,重新初始化空的链表头。
-------\linux\stddef.h-----offsetof()----------- #define __compiler_offsetof(a,b) __builtin_offsetof(a,b) 而__builtin_offsetof()宏就是在编译器中已经设计好了的函数,直接调用即可。 ------------------------------- #undef offsetof //取消先前的任何定义,可以保证下面的定义生效 #ifdef __compiler_offsetof #define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER) #else #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) #endif一共4步 1. ( (TYPE *)0 ) 0地址强制 "转换" 为 TYPE结构的指针; 2. ((TYPE *)0)->MEMBER 访问结构中的数据成员; 3. &( ( (TYPE *)0 )->MEMBER)取出数据成员的地址; 4.(size_t)(&(((TYPE*)0)->MEMBER))结果转换类型.巧妙之处在于将0转换成(TYPE*),结构以内存空间首地址0作为起始地址,则成员地址自然为偏移地址; 举例说明: #include<stdio.h> typedef struct _test { char i; int j; char k; }Test; int main() { Test *p = 0; printf("%p\n", &(p->k)); }
---------------------typeof()-------------------- unsigned int i; typeof(i) x; x=100; printf("x:%d\n",x); typeof() 是 gcc 的扩展,和 sizeof() 类似。
---------------container_of()-------------------- container_of() 来自\linux\kernel.h 内核中的注释:container_of - cast a member of a structure out to the containing structure。 ptr: the pointer to the member. type: the type of the container struct this is embedded in. member:the name of the member within the struct. #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );})
#define list_entry(ptr, type, member) \ container_of(ptr, type, member) 扩展替换即为: #define list_entry(ptr, type, member) \ ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) 例如,我们要访问foo链表(链表头为head)中首个元素,则如此调用: list_entry(head->next, struct foo, list); 经过C预处理的文字替换,这一行的内容就成为: ((struct foo *)((char *)(head->next) - (unsigned long)(&((struct foo *)0)->list)))
----------------list_for_each()------------------ #define list_for_each(pos, head) \ for (pos = (head)->next; prefetch(pos->next), pos != (head); \ pos = pos->next) 为提高遍历速度,还使用了预取。 -----asm-x86_64\processor.h---prefetch()--------- static inline void prefetch(void *x) { asm volatile("prefetcht0 %0" :: "m" (*(unsigned long *)x)); } 将x指针作强制类型转换为unsigned long *型,然后取出该内存操作数,送入高速缓存。 ----------------__list_for_each()----------------- #define __list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); pos = pos->next) list_for_each()有prefetch()用于复杂的表的遍历,而__list_for_each()无prefetch()用于简单的表的遍历,此时表项比较少,无需缓存。 ----------------list_for_each_prev()------------- #define list_for_each_prev(pos, head) \ for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \ pos = pos->prev) 反向遍历节点 ----------------list_for_each_safe()-------------- 如果在遍历过程中,包含有删除或移动当前链接节点的操作,由于这些操作会修改遍历指针,这样会导致遍历的中断。这种情况下,必须使用list_for_each_safe宏,在操作之前将遍历指针缓存下来: 内核中解释的精华部分: /* * list_for_each_safe - iterate over a list safe against removal of list entry */ #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) 在for循环中n暂存pos下一个节点的地址,避免因pos节点被释放而造成的断链。也就是说你可以遍历完当前节点后将其删除,同时可以接着访问下一个节点,遍历完毕后就只剩下一个头节点。这就叫safe。十分精彩。典型用途是多个进程等待在同一个等待队列上,若事件发生时唤醒所有进程,则可以唤醒后将其依次从等待队列中删除。
-------------list_for_each_entry()--------------- #define list_for_each_entry(pos, head, member) \ for (pos = list_entry((head)->next, typeof(*pos), member); \ prefetch(pos->member.next), &pos->member != (head); \ pos = list_entry(pos->member.next, typeof(*pos), member)) 这是用于嵌套的结构体中的宏: struct example_struct { struct list_head list; int priority; ... //其他结构体成员 }; struct example_struct *node = list_entry(ptr,struct example_struct,list); 自己分析:对比list_entry(ptr,type,member)可知有以下结果: 其中list相当于member成员,struct example_struct相当于type成员,ptr相当于ptr成员。而list{}成员嵌套于example_struct{}里面。ptr指向example_struct{}中的list成员变量的。在list_entry()作用下,将ptr指针回转指向struct example_struct{}结构体的开始处。
-------------list_for_each_entry_reverse()------- #define list_for_each_entry_reverse(pos, head, member) \ for (pos = list_entry((head)->prev, typeof(*pos), m+ember); \ prefetch(pos->member.prev), &pos->member != (head); \ pos = list_entry(pos->member.prev, typeof(*pos), member)) 分析类似上面。 ---------------list_prepare_entry()--------------- 如果遍历不是从链表头开始,而是从已知的某个pos结点开始,则可以使用list_for_each_entry_continue(pos,head,member)。但为了确保pos的初始值有效,Linux专门提供了一个list_prepare_entry(pos,head,member)宏,如果pos有值,则其不变;如果没有,则从链表头强制扩展一个虚pos指针。将它的返回值作为list_for_each_entry_continue()的pos参数,就可以满足这一要求。 内核中的list_prepare_entry()的代码: #define list_prepare_entry(pos, head, member) \ ((pos) ? : list_entry(head, typeof(*pos), member)) 分析: :前面是个空值,即:若pos不为空,则pos为其自身。等效于: (pos)? (pos): list_entry(head,typeof(*pos),member) 注意内核格式::前后都加了空格。 ------------list_for_each_entry_continue()-------- 内核中的list_for_each_entry_continue()的代码: #define list_for_each_entry_continue(pos, head, member) \ for (pos = list_entry(pos->member.next, typeof(*pos), member); \ prefetch(pos->member.next), &pos->member != (head); \ pos = list_entry(pos->member.next, typeof(*pos), member)) 此时不是从头节点开始遍历的,但仍然是以头节点为结束点的,即没有遍历完整个链表。 要注意并不是从pos开始的,而是从其下一个节点开始的,因为第一个有效pos是从pos->member.next扩展得到的。
本文例子来自http://isis.poly.edu/kulesh/stuff/src/klist/,只是对其中注释部分作了翻译。 #include <stdio.h> #include <stdlib.h> #include "list.h" struct kool_list{ int to; struct list_head list; int from; }; int main(int argc, char **argv){ struct kool_list *tmp; struct list_head *pos, *q; unsigned int i; struct kool_list mylist; INIT_LIST_HEAD(&mylist.list); /* 您也可以使用宏LIST_HEAD(mylist)来声明并初始化这个链表 */ /*向链表中添加元素*/ for(i=5; i!=0; --i){ tmp= (struct kool_list *)malloc(sizeof(struct kool_list)); /*INIT_LIST_HEAD(&tmp->list); 调用这个函数将初始化一个动态分配的list_head。也可以不调用它,因为在后面调用的add_list()中将设置next和prev域。*/ printf("enter to and from:"); scanf("%d %d", &tmp->to, &tmp->from); /*将tmp添加到mylist链表中*/ list_add(&(tmp->list), &(mylist.list)); /*也可以使用list_add_tail()将新元素添加到链表的尾部。*/ } printf("\n"); /*现在我们得到了数据结构struct kool_list的一个循环链表,我们将遍历这个链表,并打印其中的元素。*/ /*list_for_each()定义了一个for循环宏,第一个参数用作for循环的计数器,换句话说,在整个循环过程中它指向了当前项的list_head。第二个参数是指向链表的指针,在宏中保持不变。*/ printf("traversing the list using list_for_each()\n"); list_for_each(pos, &mylist.list){ /*此刻:pos->next指向了下一项的list变量,而pos->prev指向上一项的list变量。而每项都是struct kool_list类型。但是,我们需要访问的是这些项,而不是项中的list变量。因此需要调用list_entry()宏。*/ tmp= list_entry(pos, struct kool_list, list); /*给定指向struct list_head的指针,它所属的宿主数据结构的类型,以及它在宿主数据结构中的名称,list_entry返回指向宿主数据结构的指针。例如,在上面一行, list_entry()返回指向pos所属struct kool_list项的指针。*/ printf("to= %d from= %d\n", tmp->to, tmp->from); } printf("\n"); /* 因为这是一个循环链表,我们也可以向前遍历。只需要将list_for_each替换为list_for_each_prev。我们也可以使用list_for_each_entry()遍历链表,在给定类型的项间进行循环。例如:*/ printf("traversing the list using list_for_each_entry()\n"); list_for_each_entry(tmp, &mylist.list, list) printf("to= %d from= %d\n", tmp->to, tmp->from); printf("\n"); /*下面将释放这些项。因为我们调用list_del()从链表中删除各项,因此需要使用list_for_each()宏的"安全"版本,即list_for_each_safe()。务必注意,如果在循环中有删除项(或把项从一个链表移动到另一个链表)的操作,必须使用这个宏。*/ printf("deleting the list using list_for_each_safe()\n"); list_for_each_safe(pos, q, &mylist.list){ tmp= list_entry(pos, struct kool_list, list); printf("freeing item to= %d from= %d\n", tmp->to, tmp->from); list_del(pos); free(tmp); } return 0; }注意:上述代码在使用gcc编译时需要加上__KERNEL__定义。