首页 > 代码库 > 深入浅出实例解析linux内核container_of宏

深入浅出实例解析linux内核container_of宏

    做一件事情首先应该知道它的目的是什么。
    container_of的目的:如何通过结构中的某个变量获取结构本身的指针。
    总体思路:假想一下,你的结构体中有好几个成员,你如何通过里面的“任一成员”获取整个结构体的首地址呢。container_of的做法就是通过typeof定义一个与“任一成员”同类型的指针变量pvar_a(假设变量名就是pvar_a),并让指针变量pvar_a指向这个“任一成员”,然后用 “pvar_a的地址” 减去 “pvar_a相对于你的结构体的偏移量”,这个结果对应的地址其实就是你的结构体的首地址,最后将这个得到的结果强制转换为你的结构体对应的类型就OK了。
    于container_of见kernel.h中:
    /**
    * container_of - cast a member of a structure out to the containing structure
    * @ptr:     the pointer to the member.
    * @type:     the type of the container struct this is embedded in.
    * @member:     the name of the member within the struct.
    *
    */
    #define container_of(ptr, type, member) ({             /
        const typeof( ((type *)0)->member ) *__mptr = (ptr);     /
        (type *)( (char *)__mptr - offsetof(type,member) );})
    注:补充一点C语言关于宏定义的知识,在一个宏中,如果有多条语句,则最终宏的返回结果是最后一条语句的值,这有点像逗号表达式。	
    关于offsetof见stddef.h中:
    #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
    TYPE是某struct的类型,(TYPE *)0是将0地址处强制转换为TYPE类型struct,MEMBER是该struct中的一个成员(你完全可以理解成就是我上面说的“任一成员”). 由于该struct的基地址为0,所以MEMBER的地址本身毫无疑问之意就是代表它自身的地址,另外它还巧妙的有另外一层含义就是MEMBER的地址数值上等于MEMBER相对与struct头地址的偏移量,现在你该明白之前为什么要选(TYPE *)0来强制转换,而不选(TYPE *)100,或者其他。
    关于typeof,这是gcc的C语言扩展保留字,用于声明变量类型.
    “const typeof( ((type *)0->member ) *__mptr = (ptr);” 的意思是声明一个与member同一个类型的指针常量 *__mptr,并初始化为ptr.
    “(type *)( (char *)__mptr - offsetof(type,member) );” 的意思是__mptr的地址减去member在该struct中的偏移量得到的地址, 再转换成type型指针. 其实该指针就是member的入口地址了.
    下面是我结合以前在网上的一个例子修改后的一个新例子,希望对读者有帮助:
#include<stdio.h>
/*
 *在应用层不能引用内核提供的container_of和offsetof宏,故需自己定义	
 */
#define container_of(ptr, type, member) ({                      const typeof( ((type *)0)->member ) *__mptr = (ptr);             (type *)( (char *)__mptr - offsetof(type,member) );})
		 
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

/*
 *测试用的结构体
 */
struct student{
	char name[20];  
	char sex;
}stu={"liuhb569620660",‘M‘};

int main(int argc, char *argv[])
{
	struct student *stu_ptr; //存储container_of宏的返回值
	int offset;           	 //存储offsetof宏的返回值
	
	/*
	 *下面三行代码等同于 container_of(&stu.sex, struct student, sex )
	 */

	/*
	首先定义一个 _mptr指针, 类型为struct student结构体中sex成员的类型,typeof用于获取(((struct student*)0)->sex)的类型,此处该成员类型为char,((struct student*)0)在offsetof处讲解
	*/	
	const typeof(((struct student*)0)->sex) *_mptr = &stu.sex;

	/*
	((struct student*)0)是把0地址强制转化为指向student结构体类型的指针,该指针从地址 0 开始的 21个字节 用来存放name与sex,(注:char name[20]与char sex共21字节),sex存放在第20个字节出(从0字节开始),&((struct student *)0)->sex 取出sex地址(此处即为20),并强制转化为整形,所以offset为20,后面的printf结果将证明这一点
	*/
	offset = (int)(&((struct student *)0)->sex);

	/*
	((char*)_mptr - offset)此处先把_mptr指针转化为字符形指针,(为什么这么做呢? 如果_mptr为整形指针 _mptr - offset 相当于减去 sizeof(int)*offset个字节),减去 offset值 相当于 得到_mptr所在结构体的首地址(即stu的地址),然后我们把 该地址 强制转化为 struct student 类型即可正常使用了
	*/	
	stu_ptr = (struct student *)((char*)_mptr - offset);

	printf("\n### offsetof stu.sex = %d\n",offset);  
	printf("### stu_ptr->name = %s \n" 	"### stu_ptr->sex = %c\n\n", stu_ptr->name, stu_ptr->sex);
	
	/*
	下面增加验证直接调用container_of这个宏效果	
	*/
	/*通过sex成员找到结构体首地址*/	printf("\n### container_of(&stu.sex, struct student, sex)->name = %s\n", container_of(&stu.sex, struct student, sex)->name);  
	printf("### container_of(&stu.sex, struct student, sex)->sex = %c\n", container_of(&stu.sex, struct student, sex)->sex);  
	/*通过name成员找到结构体首地址*/
	printf("\n### container_of(&stu.sex, struct student, sex)->name = %s\n", container_of(stu.name, struct student, name)->name);  
	printf("### container_of(&stu.sex, struct student, sex)->sex = %c\n", container_of(stu.name, struct student, name)->sex);  
	
	return 0;
} 
    例子的演示效果如下: