Chinaunix首页 | 论坛 | 博客
  • 博客访问: 327170
  • 博文数量: 161
  • 博客积分: 245
  • 博客等级: 二等列兵
  • 技术积分: 694
  • 用 户 组: 普通用户
  • 注册时间: 2012-09-08 13:19
文章分类

全部博文(161)

文章存档

2016年(3)

2015年(31)

2014年(11)

2013年(107)

2012年(9)

分类: LINUX

2013-04-16 16:13:36

linux 链表使用

 

3. 遍历

遍历是链表最经常的操作之一,为了方便核心应用遍历链表,Linux链表将遍历操作抽象成几个宏。在介绍遍历宏之前,我们先看看如何从链表中访问到我们真正需要的数据项。

a) 由链表节点到数据项变量

我们知道,Linux链表中仅保存了数据项结构中list_head成员变量的地址,那么我们如何通过这个list_head成员访问到作为它的所有者的节点数据呢?Linux为此提供了一个list_entry(ptr,type,member)宏,其中ptr是指向该数据中list_head成员的指针,也就是存储在链表中的地址值,type是数据项的类型,member则是数据项类型定义中list_head成员的变量名,例如,我们要访问nf_sockopts链表中首个nf_sockopt_ops变量,则如此调用:

list_entry(nf_sockopts->next, struct nf_sockopt_ops, list);



这里"list"正是nf_sockopt_ops结构中定义的用于链表操作的节点成员变量名。

list_entry的使用相当简单,相比之下,它的实现则有一些难懂:

#define list_entry(ptr, type, member) container_of(ptr, type, member)

container_of宏定义在[include/linux/kernel.h]中:

#define container_of(ptr, type, member) ({                               \

        const typeof( ((type *)0)->member ) *__mptr = (ptr);   \

        (type *)( (char *)__mptr - offsetof(type,member) );})

offsetof宏定义在[include/linux/stddef.h]中:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)



size_t最终定义为unsigned inti386)。

这里使用的是一个利用编译器技术的小技巧,即先求得结构成员在与结构中的偏移量,然后根据成员变量的地址反过来得出属主结构变量的地址。

container_of()offsetof()并不仅用于链表操作,这里最有趣的地方是((type *)0)->member,它将0地址强制"转换"type结构的指针,再访问到type结构中的member成员。在container_of宏中,它用来给typeof()提供参数(typeof()gcc的扩展,和sizeof()类似),以获得member成员的数据类型;在offsetof()中,这个member成员的地址实际上就是type数据结构中member成员相对于结构变量的偏移量。

如果这么说还不好理解的话,不妨看看下面这张图:


5 offsetof()宏的原理
图5 offsetof()宏的原理 

对于给定一个结构,offsetof(type,member)是一个常量,list_entry()正是利用这个不变的偏移量来求得链表数据项的变量地址。

b) 遍历宏

[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参数,就可以满足这一要求。



================================================================================

linux 链表我主要用到遍历和访问,涉及到 list_for_each, list_entry 或者 list_for_each_entry

附上例子,

 

#include
#include "list.h"
 
struct int_node {
int val;
    char *name;
struct list_head list;
};
 
int main()
{
struct list_head head, *plist;
struct int_node a, b, c, *p_node;
                         
a.val = 1;
    a.name = "golf";
b.val = 2;
    b.name = "tanson";
c.val = 3;
    c.name = "goldfighter";
                                         
INIT_LIST_HEAD(&head);
list_add_tail(&a.list, &head);
list_add_tail(&b.list, &head);
list_add_tail(&c.list, &head);
                                                                 
    printf("\nlist_for_each
list_entry 一起使用\n");
    // list_for_each
list_entry 一起使用
list_for_each(plist, &head) {
struct int_node *node = list_entry(plist, struct int_node, list);
printf("val = %d\n", node->val);
printf("name = %s\n", node->name);
}


    printf("\n
list_for_each_entry 代替 list_for_each list_entry\n");
    //
list_for_each_entry 代替 list_for_each list_entry
list_for_each_entry(p_node, &head, list) {
printf("val = %d\n", p_node->val);
printf("name = %s\n", p_node->name);
    }
                                                                         
return 0;
}

用两种方法遍历链表。

 


阅读(569) | 评论(0) | 转发(0) |
0

上一篇:cvs完全手册

下一篇:linux 内部同步机制

给主人留下些什么吧!~~