本文参考了http://my.oschina.net/vazor/blog/87344和http://blog.chinaunix.net/uid-21977330-id-3755194.html整理而成。
在学习数据结构课时,我们知道链表元素是个结构体,由数据项和指针项构成,正式里面的指针项是形成链表结构的核心,但数据项才是链表有意义的依托,如果一个链表元素只有指针项,没有数据项,这个链表是没有意义的。但这只是表面如此,在学习linux内核的双向循环链表中,我们不得不叹服内核设计者的匠心独具。List_head 结构定义在include/linux/types.h中
struct list_head {
struct list_head *next, *prev;
};
这个结构体没有数据项!也就是说,n多个这种元素可以首尾相连形成一个双向循环的环,但是在这个链表中没有信息的载体。那么一切链表的操作,比如遍历,在这里有什么意义呢?总不能就在一堆指针里闲逛吧。
我们要让链表有意义,必须使链表有信息的载体,即数据项,这是毋庸置疑的。那就让我们换个思路,既然一堆list_head已经构成一个链表,而且我们不能往list_head对象里面加入数据项,那么让这些list_head对象位于另外一个结构体的内部,作为指针域存在,这个外部结构体的其他成员作为数据项存在,是否就让链表有意义了呢。
答案是肯定的,但是问题又来了,N多外部结构体的对象通过包含list_head相互联系起来,可是在对链表进行操作时,我们的目的是要操作或访问与之相关的数据。而现在数据是存在于List_head对象外的,我们不能通过链表元素的指针来访问。事实上,我们只能通过同时包含这些数据项和链表元素的外部结构体对象的指针来访问,为什么呢?哈哈,绕晕了,因为这些数据本来就是这些外部结构体对象的内容之一啊。
现在问题归结为:已知一个结构体里某个成员的指针,怎么得到这个结构体的指针?
试想,如果知道了这个已知成员在结构体里的地址偏移,我们就能得到这个结构体的指针。如是List_head类型的链表的操作才显得有意义。
到现在所有的问题都指向了一个宏,container_of(),这个宏才是整个链表结构的最本质的地方。该宏定义在include/linux/kernel.h中。
/**
* container_of - 通过结构体的一个成员获取容器结构体的指针
* @ptr: 指向成员的指针。
* @type: 成员所嵌入的容器结构体类型。
* @member: 结构体中的成员名。
*
*/
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
这个宏的作用其实很简单,就是通过一个容器(结构体)中某个成员的指针得到指向这个容器(结构体)的指针,简单的说就是通过成员找容器。
这个宏的实现代码也就只有两行,下面仔细分析一下:
(1)const typeof( ((type *)0)->member ) *__mptr = (ptr);
定义一个中间变量__mptr,它等于提供给宏的参数ptr,也就是指向某个成员的指针。这个中间变量的命名意义是:"__"代表内部使用,内核编程中常常这么做;“m”代表middle。
(2)(type *)( (char *)__mptr - offsetof(type,member) );
这行代码的作用是通过中间变量__mptr(指向某个成员的指针)减去这个成员在容器(结构体)中的偏移来得到指向容器(结构体)的指针。这里注意偏移的获取offsetof宏的实现:
那么container_of的工作原理就相当简单了,就是用MEMBER的地址减去上一步中得到的偏移,即为TYPE类型的结构体对象的地址。
为什么__mptr要强制转换为char*呢?这是c语言的基础知识,指针的加减对应到地址是根据指针所指的数的类型来确定的。如果MEMBER是int,而不把它的指针强制为char*,那么减去的将是4倍的地址偏移,当然是不对的。如果不用(char*)强转,那么这个 - 操作意义就不同了。返回的地址也就不同了。
char *p = (char*)&n;
p-1;
int *pp = (int*)&n;
pp-1;
p-1与pp-1 是不同样的。
#ifdef __compiler_offsetof这个实现依赖编译器,如果编译器内建了__compiler_offsetof(TYPE,MEMBER)的实现,就使用编译器的实现,否则就是用((size_t) &((TYPE *)0)->MEMBER)。
#define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER)
#else
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif
在Linux-2.6.29(含)以上内核必须使用4.1.1(不含)以上的编译器,所以Linux-3.0内核肯定是使用了编译器内核内建的宏实现include/linux/compiler-gcc4.h
#define __compiler_offsetof(a,b) __builtin_offsetof(a,b)