详解Linux内核之双向循环链表(3)

2020-05-01 10:45

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 #include \ 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(\

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__定义。


详解Linux内核之双向循环链表(3).doc 将本文的Word文档下载到电脑 下载失败或者文档不完整,请联系客服人员解决!

下一篇:招投标与合同管理试卷一及答案[001]

相关阅读
本类排行
× 注册会员免费下载(下载后可以自由复制和排版)

马上注册会员

注:下载文档有可能“只有目录或者内容不全”等情况,请下载之前注意辨别,如果您已付费且无法下载或内容有问题,请联系我们协助你处理。
微信: QQ: