作者在 2011-04-28 10:13:14 发布以下内容
8 遍历
遍历是链表最经常的操作之一,为了方便核心应用遍历链表,Linux链表将遍历操作抽象成几
个宏。在介绍遍历宏之前,我们先看看如何从链表中访问到我们真正需要的数据项。
struct list_head *i;
list_for_each(i, &(nf_sockopts.list))
{
struct nf_sockopts *ops = list_entry(i, struct nf_sockopts, list);
}
list_for_each(i, &(nf_sockopts.list))
{
struct nf_sockopts *ops = list_entry(i, struct nf_sockopts, list);
}
函数首先定义一个(struct list_head *)指针变量i,然后调用
list_for_each(i,&(sockopt.list))进行遍历。在
[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, prefetch(pos->next); pos != (head); \
pos = pos->next, prefetch(pos->next))
它实际上是一个for循环,利用传入的pos作为循环变量,从表头head开始,逐项向后(next
方向)移动pos,直至又回到head(prefetch()可以不考虑,用于预取以提高遍历速度)。
大多数情况下,遍历链表的时候都需要获得链表节点数据项,也就是说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_sockopts *ops;
list_for_each_entry(ops, &sockopt, list)
{
}
list_for_each_entry(ops, &sockopt, 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参数,就可以满足这一要求。
9 安全性考虑
在并发执行的环境下,链表操作通常都应该考虑同步安全性问题,为了方便,Linux将这一操
作留给应用自己处理。Linux链表自己考虑的安全性主要有两个方面:
9.1 list_empty()判断
基本的list_empty()仅以头指针的next是否指向自己来判断链表是否为空,Linux链表另行提
供了一个list_empty_careful()宏,它同时判断头指针的next和prev,仅当两者都指向自己
时才返回真。这主要是为了应付另一个cpu正在处理同一个链表而造成next、prev不一致的情
况。但代码注释也承认,这一安全保障能力有限:除非其他cpu的链表操作只有
list_del_init(),否则仍然不能保证安全,也就是说,还是需要加锁保护。
9.2 遍历时节点删除
前面介绍了用于链表遍历的几个宏,它们都是通过移动pos指针来达到遍历的目的。但如果遍
历的操作中包含删除pos指针所指向的节点,pos指针的移动就会被中断,因为
list_del(pos)将把pos的next、prev置成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节点被释放而造成的断链。