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) ({
说明:offsetof是求MEMBER在TYPE结构体中的偏移量,container_of是给定结构体某个成员的地址反推其结构体地址。两个宏实现的方法都很直观,无需多解释。倒是有一点:container_of是linux/kernel.h中定义的,其中用到的typeof 操作符和语句表达式(即“({ statements })”这种写法)都是gcc对C的扩展.
http://blog.youkuaiyun.com/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) \
#define container_of(ptr, type, member) ({
offsetof是计算成员member在结构体的type里面的偏移数,用__mptr的地址减去偏移地址就可以得到type结构体的开始地址
container_of向上面这样定义展开后可以直接赋值如下面这样
#include <stdio.h>
int main(int argc,char argv[])
{
}
这种用法应该是gcc的扩展
2
在linux内核中,list是无处不在,好多代码都include了list.h。在linux源代码中,由于list的运用过于频繁,所以就把 list 单独定义了,如果有哪个结构要成为列表,就把结构的第一个成员定义成list_head,然后初始化就可以了。
typedef struct list_head {
} list_t;
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
#define INIT_LIST_HEAD(ptr) do { \
} while (0)
.
.
前三个宏用来初始化一个next和prev指针皆指向自身的空链表,宏可以用到的地方明显受限于c语法约束。例如,LIST_HEAD_INIT()用来初始化结构元素,第二个宏用来初始化静态变量,第三个用于函数内部。
static inline void __list_add(struct list_head *new,
{
}
static inline void list_add(struct list_head *new, struct list_head *head)
{
}
在list.h中还有一个比较有趣的宏
#define list_entry(ptr, type, 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*)。
struct demo {
struct list_head list;
int i;
}
struct demo demo1;
list_add(&demo1->list, list_head)
这样的列表是非常灵活的,不同类型的变量都可以加到列表中去。如下图所示:
_________________________________________________
|
|_head |____________|___________|____________ |____
所以,我们只要知道了,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