还是从头说起吧,也给自己记一下。
你是怎么定义链表的?如果昨天你问我这个问题,我大概会写出以下的代码:
struct dog{
unsigned char name[10];
struct dog*prev;
struct dog *next;
}
但是内核可不能这么做,(以前确实是这么做的,从2.1开始就使用了官方的实现方法)因为这样的链表只适用于这一个结构体,你以后要想实现一个struct cat就得写另一个链表。内核里需要链表的结构体多如牛毛,要全都写上那该多费事啊……聪明的开发团队想到了下面的实现方法:
struct list_head{
struct list_head *next;
struct list_head *prev;
};
struct dog{
unsigned char name[10];
struct list_head list;
}
也就是说,以后想要实现链表只需在用户结构体上增加一项内核定义的结构体struct list_head。从名字就可以看出,next指向了下一个元素,prev指向了上一个。
对,这样是可以把相应的list_head连接起来,所有的程序都可以使用同样的方法,确实简便了,可是似乎有点问题?我无法获得list_head所在的父结构体啊!我只能操作list_head有什么用!
所以,为了获取list_head所在父结构体,接下来就需要主角出场了:container_of(或者list_entry)。实际上:
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
我们在list.h文件中找到container_of的宏定义:
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
这个宏接受三个参数,一般说来,ptr的类型是struct list_head*,它就是你提供的list_head,它存在于你想要找到的父结构体中,是三个参数里唯一一个在运行时才有确切值的;而type是父结构体的类型;member是struct list_head在父结构体中的名字。
使用方法如下:
struct dog mdog;
struct dog * ptr_dog=container_of(&mdog.list,struct dog,list);
接下来好好看看它是怎么实现的吧。
1.typeof
typeof(int) a;将a定义为int型
typeof(a) b;将b定义为和a相同的类型
typeof('b') a;将a定义为int 型,'b'自动提升为int
typeof(*a) b;将b定义为a指向的数据的类型
更详细的说明请看gcc编译器说明:http://gcc.gnu.org/onlinedocs/gcc/Typeof.html
2.offsetof
不要认为offsetof是C函数库提供的函数或者宏,我因为这个调了很久的程序……我们也可以在list.h里找到它的定义:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
((TYPE*)0)->MEMBER是说:将0强制转换成TYPE型的,然后指向了它的成员MEMBER。所以&((TYPE*)0)->MEMBER就可以得到MEMBER在TYPE结构体中的偏移量。比如offset(struct dog,list)得到的结果是12.(为什么不是10呢?这是字节对齐的问题了,可以看看这里: http://blog.sina.com.cn/s/blog_5d9a116e0100r2wh.html)
知道了以上两点,理解container_of应该就不成问题了。
container_of里是一个代码块,里面包含两句话。
第一句话定义了一个指向输入参数ptr的指针,typeof(((type*)0)->member)即获得了ptr的类型。为了防止接下来__mptr对源数据的改变,所以定义成了const类型。
第二句话就计算了父结构体的地址。__mptr现在已经存着ptr的地址了,它只要减去list到struct dog的距离就行了。而这个距离已经用offset算好了。
到这里,我们可以写一个test demo了吧:
#include<stdio.h>
#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) );})
struct list_head
{
struct list_head *prev,*next;
};
struct dog{
char name[10];
struct list_head list;
};
void main()
{
struct dog mdog;
scanf("%s",mdog.name);
printf("%s\n",container_of(&mdog.list,struct dog,list)->name);
}
下面,我们进入更深一层的讨论:
讨论1.
关于为什么要转换__mptr成(char*),我们可以这样看看:
#include<stdio.h>
#define offsetof(type,member) ((size_t)&((type*)0)->member)
#define char_container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
#define int_container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (int *)__mptr - offsetof(type,member) );})
struct list_head
{
struct list_head *prev,*next;
};
struct dog{
char name[10];
struct list_head list;
};
void main()
{
struct dog mdog;
printf("&mdog:%u\n",&mdog);
printf("&mdog.list:%u\n",&mdog.list);
printf("sizeof(char):%u\n",sizeof(char));
printf("sizeof(int):%u\n",sizeof(int));
printf("char_container:%u\n",char_container_of(&mdog.list,struct dog,list));
printf("int_container:%u\n",int_container_of(&mdog.list,struct dog,list));
}
结果如下:
如果是int指针的话是这样计算的:52-4*12=4;如果是char指针的话就是这样52-1*12=40。
讨论2.
container_of的第一行代码有什么意义?这样实现不是更方便吗:
#define new_container_of(ptr,type,member) ({ \
(type*)((char*)ptr-offsetof(type,member));})
原因是这样的实现方法不能够检查参数的传递是否正确,测试程序如下:
#include<stdio.h>
#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) );})
#define new_container_of(ptr,type,member) ({ \
(type*)((char*)ptr-offsetof(type,member));})
struct list_head
{
struct list_head *prev,*next;
};
struct dog{
char name[10];
struct list_head list;
};
void main()
{
struct dog mdog;
int b;
scanf("%s",mdog.name);
printf("%s\n",new_container_of(b,struct dog,list)->name);
printf("%s\n",container_of(b,struct dog,list)->name);
}
这个程序里new_container_of能顺利编译通过,而container_of会提示不正确的赋值。
讨论3.
受第2个问题的启发,为什么要是
const typeof( ((type *)0)->member ) *__mptr = (ptr);
而不是
const typeof(*ptr)*__mptr=(ptr);
呢?
这样也能检测上述参数传递是否正确,而且似乎更容易理解。
经过更进一步的测试,我发现"const typeof(*ptr) *__mptr=(ptr);"检测的范围还是没有"const typeof(((type)0)->member)*__mptr=(ptr);"检测的范围广。
比如传进去的是type的二级指针,那么第一种方法将失效。
struct dog ** pdog;
printf("%s\n",container_of(&pdog,struct dog,list)->name);
这样第一种方法编译通过,第二种方法(内核的方法)将得到有用的警告。
仔细想想,确实,"const typeof(*ptr)*__mptr=(ptr);"只能保证mptr和ptr是同类型的指针,至于是member类型的几级指针都没有关系。如果传了二级指针进来,下一条语句里的强制转换"char(*)"也不会检测到使用的是几级指针,这样的话就可能存在着巨大的风险。而内核的实现方法"const typeof( ((type *)0)->member ) *__mptr = (ptr); "一定能够保证传进来的参数是member类型的一级指针!
切记!