linux 链表使用

来源:互联网 发布:linux 显示驱动 编辑:程序博客网 时间:2024/05/14 20:42


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 int(i386)。

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

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,直至又回到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参数,就可以满足这一要求。


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

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

附上例子,


#include <stdio.h>
#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;
}

用两种方法遍历链表。


原创粉丝点击