linux内核中的list详解

来源:互联网 发布:sql查询多列重复数据 编辑:程序博客网 时间:2024/06/01 17:30
 

1 list_entry作用就是通过list_head型指针ptr换算成其宿主结构的起始地址,该宿主结构是type型的,

而ptr在其宿主结构中定义为member成员。定义在内核源文件include/linux/list.h中,

对比list_entry(ptr,type,member)可知有以下结果:
其中list相当于member成员,struct example_struct相当于type成员,ptr相当于ptr成员。而list{}成员嵌套于example_struct{}里面。ptr指向example_struct{}中的list成员变量的。在
list_entry()作用下,将ptr指针回转指向struct example_struct{}结构体的开始处

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

 container_of是个宏,定义如下:

#define container_of(ptr, type, member) ({   /
        const typeof( ((type *)0)->member ) *__mptr = (ptr); /      // typeof是gcc扩展,用于得知数据类型的,这句话作用大

               //概是看ptr是否是结构体里成员变量member的类型,不是编译时将报错,类型检测的


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

offsetof是计算成员member在结构体的type里面的偏移数,用__mptr的地址减去偏移地址就可以得到type结构体的开始地址

 

container_of向上面这样定义展开后可以直接赋值如下面这样

#include <stdio.h>

int main(int argc,char argv[])
{
 int x = 1,y = 2;
 int m =
 ({
  x++;
  x + y;
 });
 
 fprintf(stdout,"----%d/n",m);
}

这种用法应该是gcc的扩展

2

在linux内核中,list是无处不在,好多代码都include了list.h。在linux源代码中,由于list的运用过于频繁,所以就把 list 单独定义了,如果有哪个结构要成为列表,就把结构的第一个成员定义成list_head,然后初始化就可以了

    list.h应该是在include/linux/list.h,里面的代码很易懂,大家翻翻吧。当然数据结构要过关先,起码要知道list是双向链表还有双向链表的结构是如何的。
    一部分代码:
typedef struct list_head {
        struct list_head *next, *prev;         //从定义中可以看出是个双向队列
} list_t;

#define LIST_HEAD_INIT(name) { &(name), &(name) }      

#define LIST_HEAD(name) /
        struct list_head name = LIST_HEAD_INIT(name)     //定义一个空的列表

#define INIT_LIST_HEAD(ptr) do { /               //初始化一个已定义的列表
        (ptr)->next = (ptr); (ptr)->prev = (ptr); /
} while (0)
.
.
前三个宏用来初始化一个next和prev指针皆指向自身的空链表,宏可以用到的地方明显受限于c语法约束。例如,LIST_HEAD_INIT()用来初始化结构元素,第二个宏用来初始化静态变量,第三个用于函数内部。
static inline void __list_add(struct list_head *new,
                              struct list_head *prev,
                              struct list_head *next)
{
        next->prev = new;
        new->next = next;
        new->prev = prev;
        prev->next = new;
}
static inline void list_add(struct list_head *new, struct list_head *head)     //加入一个新节点
{
        __list_add(new, head, head->next);
}
在list.h中还有一个比较有趣的宏

/**
* list_entry - get the struct for this entry
* @ptr:        the &struct list_head pointer.
* @type:       the type of the struct this is embedded in.
* @member:     the name of the list_struct within the struct.
*/
#define list_entry(ptr, type, member) /
        ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

从上面我们知道list_entry的作用是返回类型type的数据结构地址,但是具体怎么实现的呢?看代码怪怪的。其实我们只要知道 &((type *)0)->member 的意思就明白了。它是取结构成员偏移量的一种方法,将常数0强制为结构指针,取其成员的地址,结果就是成员的偏移量。
list_entry宏根据list_head型指针ptr换算成其宿主结构的起始地址,该宿主结构是 type型的,而ptr在其宿主结构中定义为member成员。如下图:

req-->|type型对象起始地址
|
|... ...
ptr-->|ptr指针所指的member成员地址
|
|... ...

ptr指向图中所示的位置,通过(unsigned long)(&((type*)0)->member)得到ptr 和req之间的差值,ptr减去这个差值就得到了type型宿主结构的指针req,返回 类型为(type*)。
    了解上面那个宏,你就知道了 list_head 是如何工作的了。首先,我们先定义并初始化一个 list_head,然后声明一个结构,如下

struct demo {
struct list_head list;
int i;
}
struct demo demo1;
list_add(&demo1->list, list_head)        //把demo1加入列表,list_head仅仅就是两个指针

这样的列表是非常灵活的,不同类型的变量都可以加到列表中去。如下图所示:
_________________________________________________
|  list    |         1          |         2         |          3          |
|_head |____________|___________|____________ |____
                     ||                  ||                    ||
                     ||                  ||                    ||
                 demo1             demo2       struct dev dev1

所以,我们只要知道了,list_head的地址,里面的东西就好找了。
注意:队列头list_head不能被嵌入到结构中
 
list_add(n,p):把n指向的元素插入p所指向的特定元素之后
list_add_tail(n,h):把n所指向的元素插到第一个元素的地址h所指定的链表尾
list_empty(p):检查由第一个元素的地址指定的链表是否为空
list_for_each(p,h):对第一个元素的地址h指定的链表进行扫描
//摘自http://answes.spaces.live.com/blog/cns!76AAAD406604238A!179.entry?_c=BlogPart
3 上面对list所讲的东西,都是适用于内核的(module),在用户态下好像不可以
原创粉丝点击