list操作
来源:互联网 发布:mac ndk环境变量 编辑:程序博客网 时间:2024/06/15 23:36
在 [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,直至又回到 head(prefetch() 可以不考虑,用于预取以提高遍历速度 )。
那么在 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 正在处理同一个链表而造成 next、prev 不一致的情况。但代码注释也承认,这一安全保障能力有限:除非其他 cpu 的链表操作只有 list_del_init(),否则仍然不能保证安全,也就是说,还是需要加锁保护。
b) 遍历时节点删除
前面介绍了用于链表遍历的几个宏,它们都是通过移动 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 节点被释放而造成的断链。
…… 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,直至又回到 head(prefetch() 可以不考虑,用于预取以提高遍历速度 )。
那么在 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 正在处理同一个链表而造成 next、prev 不一致的情况。但代码注释也承认,这一安全保障能力有限:除非其他 cpu 的链表操作只有 list_del_init(),否则仍然不能保证安全,也就是说,还是需要加锁保护。
b) 遍历时节点删除
前面介绍了用于链表遍历的几个宏,它们都是通过移动 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 节点被释放而造成的断链。
复制搜索
复制搜索
- list操作
- list操作
- list操作
- list操作
- list操作
- list操作
- List操作
- List操作
- Spring RedisTemplate操作-List操作
- list box 操作
- list基本操作
- 用ajaxPro操作List
- java.util.list操作
- Python 列表(list)操作
- 对List的操作
- List的特殊操作
- list添加删除操作
- python list操作
- drop 一张3.7T的含lob的分区表
- Quartz 2D Programming Guide
- 图形图像处理网文
- IOS 四种保存数据的方式!
- 我喜欢的些东西收藏
- list操作
- 两个排序数组求中值
- 数据库分页
- Hash函数
- 视频监控存储空间大小与传输带宽计算方法
- JAVA double 类型相加
- .C# 操作SQLite数据库
- android如何获得手机屏幕大小
- 树状数组简单运用 hdu1166