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_head结构的遍历操作是远远不够的,我们希望实现的是对宿主结构的遍历,即在遍历时直接获得当前链表节点所在的宿主结构项,而不是每次要同时调用list_for_each和list_entry。对此,Linux提供了list_for_each_entry()宏,第一个参数为传入的遍历指针,指向宿主数据结构,第二个参数为链表头,为list_head结构,第三个参数为list_head结构在宿主结构中的成员名。 -------------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{}结构体的开始处。
pos当指向外层结构体,比如指向struct example_struct{}的结点,最开始时候,head指向链表结构体struct list_head{}的头结点,头节点不含有有效信息,(head)->next则指向第一个外层结点的内嵌的链表结点struct list_head{} list,由此得出的pos当指向第一个有效结点。member即是指出该 list为其内嵌的结点。 思路:用pos指向外层结构体的结点,用head指向内层嵌入的结构体的结点。用(head)->next,pos->member.next(即:ptr->list.next)来在内嵌的结构体结点链表中遍历。每遍历一个结点,就用list_entry()将内嵌的pos->member.next指针回转为指向该结点外层结构体起始处的指针,并将指针进行指针类型转换为外层结构体型pos。&pos->member! = (head)用pos外层指针引用member即:list成员,与内层嵌入的链表之头结点比较来为循环结束条件。 当遍历到头节点时,此时并没有pos这样一个type类型数据指针,而是以member域强制扩展了一个type类型的pos指针,此时其member域的地址就是head指针所指向的头节点,遍历结束,头节点的信息没有被访问。
-------------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扩展得到的。
-------------list_for_each_entry_safe()-----------
它们要求调用者另外提供一个与pos同类型的指针n,在for循环中暂存pos下一个节点的地址,避免因pos节点被释放而造成的断链。
内核中的注释与源代码: /**
* list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
* @pos: the type * to use as a loop counter.
* @n: another type * to use as temporary storage * @head: the head for your list.
* @member: the name of the list_struct within the struct. */
#define list_for_each_entry_safe(pos, n, head, member) \\ for (pos = list_entry((head)->next, typeof(*pos), member), \\ n = list_entry(pos->member.next, typeof(*pos), member); \\ &pos->member != (head); \\
pos = n, n = list_entry(n->member.next, typeof(*n), member)) 分析类似上面。容易明白。
--------list_for_each_entry_safe_continue()-------
#define list_for_each_entry_safe_continue(pos, n, head, member) \\ for (pos = list_entry(pos->member.next, typeof(*pos), member), \\ n = list_entry(pos->member.next, typeof(*pos), member); \\ &pos->member != (head); \\
pos = n, n = list_entry(n->member.next, typeof(*n), member))
、如何使用Linux中的双循环链表
本文例子来自http://isis.poly.edu/kulesh/stuff/src/klist/,只是对其中注释部分作了翻译。
#include
#include
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(\
scanf(\ /*将tmp添加到mylist链表中*/ list_add(&(tmp->list), &(mylist.list));
/*也可以使用list_add_tail()将新元素添加到链表的尾部。*/ }
printf(\
/*现在我们得到了数据结构struct kool_list的一个循环链表,我们将遍历这个链表,并打印其中的元素。*/
/*list_for_each()定义了一个for循环宏,第一个参数用作for循环的计数器,换句话说,在整个循环过程中它指向了当前项的list_head。第二个参数是指向链表的指针,在宏中保持不变。*/
printf(\ 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(\ }
printf(\
/* 因为这是一个循环链表,我们也可以向前遍历。只需要将list_for_each替换为list_for_each_prev。我们也可以使用list_for_each_entry()遍历链表,在给定类型的项间进行循环。例如:*/
printf(\ list_for_each_entry(tmp, &mylist.list, list)
printf(\ printf(\
/*下面将释放这些项。因为我们调用list_del()从链表中删除各项,因此需要使用list_for_each()宏的\安全\版本,即list_for_each_safe()。务必注意,如果在循环中有删除项(或把项从一个链表移动到另一个链表)的操作,必须使用这个宏。*/
printf(\ list_for_each_safe(pos, q, &mylist.list){ tmp= list_entry(pos, struct kool_list, list);
printf(\ list_del(pos); free(tmp); }
return 0; }
注意:上述代码在使用gcc编译时需要加上__KERNEL__定义。