Chinaunix首页 | 论坛 | 博客
  • 博客访问: 215060
  • 博文数量: 27
  • 博客积分: 527
  • 博客等级: 中士
  • 技术积分: 262
  • 用 户 组: 普通用户
  • 注册时间: 2012-05-07 19:52
文章分类
文章存档

2013年(6)

2012年(21)

分类: C/C++

2012-11-21 23:18:15

Normal 0 7.8 磅 0 2 false false false EN-US ZH-CN X-NONE /* Style Definitions */ table.MsoNormalTable {mso-style-name:普通表格; mso-tstyle-rowband-size:0; mso-tstyle-colband-size:0; mso-style-noshow:yes; mso-style-priority:99; mso-style-qformat:yes; mso-style-parent:""; mso-padding-alt:0cm 5.4pt 0cm 5.4pt; mso-para-margin:0cm; mso-para-margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:10.5pt; mso-bidi-font-size:11.0pt; font-family:"Calibri","sans-serif"; mso-ascii-font-family:Calibri; mso-ascii-theme-font:minor-latin; mso-hansi-font-family:Calibri; mso-hansi-theme-font:minor-latin; mso-bidi-font-family:"Times New Roman"; mso-bidi-theme-font:minor-bidi; mso-font-kerning:1.0pt;}

一、list_for_each

1.list_for_each原型
#define list_for_each(pos, head) \
    for (pos = (head)->next, prefetch(pos->next); pos != (head); \
    pos = pos->next, prefetch(pos->next))
它实际上是一个 for 循环,利用传入的pos 作为循环变量,从表头 head开始,逐项向后(next方向)移动 pos ,直至又回到 head prefetch() 可以不考虑,用于预取以提高遍历速度)。
注意:此宏必要把list_head放在数据结构第一项成员,至此,它的地址也就是结构变量的地址。如果不要list_head放在数据结构第一项成员,一般就要配合list_entry使用。

 

二、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))

list_for_each_entry遍历head 下面挂接的类型为typeof(*pos)childs结构体们,当然每个child结构体包含struct list_head node之类相似的双向链表list_head类型项,就这样通过循环pos将依次指向双向链表上的各个child.(member就是child类型中 被定义的变量名)

 

#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))

说白了define list_for_each_entry 就是一个for循环。。。。




[net/core/netfilter.c] nf_register_sockopt() 函数中有这么一段话:

…… struct list_head *i; …… list_for_each(i, &nf_sockopts) { struct nf_sockopt_ops *ops = (struct nf_sockopt_ops *)i; …… } ……


数首先定义一个 (struct list_head *) 指针变量i,然后调用 list_for_each(i,&nf_sockopts) 进行遍历。在 [include/linux/list.h] 中, list_for_each() 宏是这么定义的:

#define list_for_each(pos, head) \ for (pos = (head)->next, prefetch(pos->next); pos != (head); \ pos = pos->next, prefetch(pos->next))

它实际上是一个 for 循环,利用传入的 pos 作为循环变量,从表头 head 开始,逐项向后(next 方向)移动 pos,直至又回到 headprefetch() 可以不考虑,用于预取以提高遍历速度 )。

么在 nf_register_sockopt() 中实际上就是遍历 nf_sockopts 链表。为什么能直接将获得的 list_head 成员变量地址当成 struct nf_sockopt_ops 数据项变量的地址呢?我们注意到在 struct nf_sockopt_ops 结构中,list是其中的第一项成员,因此,它的地址也就是结构变量的地址。更规范的获得数据变量地址的用法应该是:

struct nf_sockopt_ops *ops = list_entry(i, struct nf_sockopt_ops, list);


大多数情况下,遍历链表的时候都需要获得链表节点数据项,也就是说 list_for_each()list_entry() 总是同时使用。对此 Linux 给出了一个 list_for_each_entry() 宏:

#define list_for_each_entry(pos, head, member) ……


list_for_each() 不同,这里的pos是数据项结构指针类型,而不是 (struct list_head *)nf_register_sockopt() 函数可以利用这个宏而设计得更简单:

…… struct nf_sockopt_ops *ops; list_for_each_entry(ops,&nf_sockopts,list){ …… } ……


些应用需要反向遍历链表,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 参数,就可以满足这一要求。

4.
安全性考虑
在并发执行的环境下,链表操作通常都应该考虑同步安全性问题,为了方便,Linux 将这一操作留给应用自己处理。Linux 链表自己考虑的安全性主要有两个方面:

a) list_empty()
判断

本的 list_empty() 仅以头指针的 next 是否指向自己来判断链表是否为空,Linux 链表另行提供了一个 list_empty_careful() 宏,它同时判断头指针的 next prev,仅当两者都指向自己时才返回真。这主要是为了应付另一个 cpu 正在处理同一个链表而造成 nextprev 不一致的情况。但代码注释也承认,这一安全保障能力有限:除非其他 cpu 的链表操作只有 list_del_init(),否则仍然不能保证安全,也就是说,还是需要加锁保护。

b)
遍历时节点删除

前面介 绍了用于链表遍历的几个宏,它们都是通过移动 pos 指针来达到遍历的目的。但如果遍历的操作中包含删除 pos 指针所指向的节点,pos 指针的移动就会被中断,因为 list_del(pos) 将把 pos nextprev 置成 LIST_POSITION2 LIST_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 节点被释放而造成的断链。

 

参考资料:

http://blog.163.com/kmustchenb@126/blog/static/1109057652011112221829306/

Normal 0 7.8 磅 0 2 false false false EN-US ZH-CN X-NONE /* Style Definitions */ table.MsoNormalTable {mso-style-name:普通表格; mso-tstyle-rowband-size:0; mso-tstyle-colband-size:0; mso-style-noshow:yes; mso-style-priority:99; mso-style-qformat:yes; mso-style-parent:""; mso-padding-alt:0cm 5.4pt 0cm 5.4pt; mso-para-margin:0cm; mso-para-margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:10.5pt; mso-bidi-font-size:11.0pt; font-family:"Calibri","sans-serif"; mso-ascii-font-family:Calibri; mso-ascii-theme-font:minor-latin; mso-hansi-font-family:Calibri; mso-hansi-theme-font:minor-latin; mso-bidi-font-family:"Times New Roman"; mso-bidi-theme-font:minor-bidi; mso-font-kerning:1.0pt;}


阅读(2558) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~