linux内核中的list详解

来源:互联网 发布:沃虎送货单软件 编辑:程序博客网 时间:2024/06/01 14:28

linux内核中的list详解


原因:
file_operation 结构中的open函数定义如下:


int (*open)(struct inode *inode, struct file* filp);


inode中含有i_cdev属性,它描述的是字符设备。在自己定义的字符设备中,一般会包含字符设备的指针,而open方法被调用时,通常需要获取特定的设备对象,这里就涉及到一个问题:如何通过结构中的某个变量获取结构本身的指针。Linux 内核中提供了container_of宏(WDM中也定义了相似功能的宏)。C99中定义了两个宏,typeof和offsetof,它们返回的是某个变量的类型和结构中某变量在结构中的偏移量。可以预想的是,没有编译器的支持,container_of的宏是很难实现的(至少我还没有想出能够不用 typeof宏实现container_of的方法)。


优点:
值得一提的是,offsetof宏的实现非常巧妙,它把0地址转化为TYPE结构的指针,然后获取该结构中MEMBER成员的指针,并将其强制类型转换为size_t类型。于是,由于结构从0地址开始定义,因此,cast后的MEMBER成员地址,实际上就是它在结构中的偏移量。这也显示出了C语言中指针的强大。因为,在某个体系结构下实现的libc,结构中各个成员的偏移总是可以预见的,这比C#那种以托管的方式管理内存的自由度要大的多。


实现:
container_of宏定义在include/linux/kernel.h中:
offsetof宏定义在include/linux/stddef.h中:
container_of宏,它的功能是得到包含某个结构成员的结构的指针:
其实现如下:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})
 
    分析可知__mptr指向的是一个type结构里typeof(((type *)0)->member)类型member成员的指针,offsetof(type,member)是这个成员在结构中的偏移,单位是字节,所以为了计算type结构的起始地址,__mptr减去它自己的偏移。


说明:offsetof是求MEMBER在TYPE结构体中的偏移量,container_of是给定结构体某个成员的地址反推其结构体地址。两个宏实现的方法都很直观,无需多解释。倒是有一点:container_of是linux/kernel.h中定义的,其中用到的typeof 操作符和语句表达式(即“({ statements })”这种写法)都是gcc对C的扩展.


http://blog.csdn.net/hondrif82q/archive/2007/04/14/1564893.aspx
linux内核中的list详解
 
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中还有一个比较有趣的宏


#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


0 0
原创粉丝点击