在Linux 内核中,container_of 函数使用非常广,例如 Linux内核链表 list_head、工作队列work_struct中。
需要注意的是container_of不是一个函数而是一个宏。
该宏定义在include/linux/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) );})
container_of(ptr,type,member),这里面有ptr,type,member分别代表指针、类型、成员。
使用例子:
struct test
{
int i;
int j;
char k;
};
struct test temp;
如果想通过temp.j的地址找到temp的首地址就可以使用container_of(&temp.j,struct test,j);来实现。
container_of()的作用就是通过一个结构变量中一个成员的地址找到这个结构体变量的首地址。
下面分析这个这行过程:
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
将上面struct test结构体代入:
const typeof(((struct test *)0)->j) * __mptr = (&temp.j); //(sturct test *)0 表示数据段基址
其中,typeof是GNU C对标准C的扩展,它的作用是根据变量获取变量的类型。因此,上述代码的作用是首先使用typeof获取结构体成员j的类型为int,然后顶一个int指针类型的临时变量__mptr,并将结构体变量中的成员的地址赋给临时变量__mptr。
(char )__mptr转换为字节型指针。(char )__mptr - offsetof(type,member) )用来求出结构体起始地址(为char 型指针),然后(type )( (char )__mptr - offsetof(type,member) )在(type )作用下进行将字节型的结构体起始指针转换为type *型的结构体起始指针。
(struct test *)((char *)__mptr - offsetof(struct test,j));
这里执行了offsetof(struct test,j),offsetof也是一个定义宏,它定义在include/linux/stddef.h中
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
假设一个结构体变量demo,定义如下:
struct demo_struct {
type1 member1;
type2 member2;
type3 member3;
type4 member4;
};
struct demo_struct demo;
如果需要获取指向整个结构体变量的指针,而不仅仅只是其某一个域成员变量的指针,可以这么做:
struct demo_struct *demop = container_of(memp, struct demo_struct, member3);
container_of(memp, struct demo_struct, type3)根据宏的定义进行展开如下:
struct demo_struct *demop = ({ \
const typeof( ((struct demo_struct *)0)->member3 ) *__mptr = (memp); \
(struct demo_struct *)( (char *)__mptr - offsetof(struct demo_struct, member3) );})
假设结构体变量demo在实际内存中的位置如下图所示:
demo
+-------------+ 0xA000
| member1 |
+-------------+ 0xA004
| member2 |
+-------------+ 0xA010
| member3 |
+-------------+ 0xA018
| member4 |
+-------------+
则,在执行上面container_of宏中第一句后,__mptr的值即为0xA010
执行第二句分四步:
1. ( (TYPE *)0 ) 将零转型为TYPE类型指针;
2. ((TYPE *)0)->MEMBER 访问结构中的数据成员;
3. &( ( (TYPE *)0 )->MEMBER )取出数据成员的地址;
4. (size_t)(&(((TYPE*)0)->MEMBER))结果转换类型。巧妙之处在于将0转换成(TYPE*),结构以内存空间首地址0作为起始地址,则成员地址自然为偏移地址;
将上述的offsetof调用展开,即为:
(struct demo_struct *)( (char *)__mptr - ((size_t) &((struct demo_struct *)0)->member3) );
offsetof取结构体中的域成员相对于地址0的偏移地址,也就是域成员变量相对于结构体变量首地址的偏移。
因此,offsetof(struct demo_struct, member3)
调用返回的值就是member3相对于demo变量的偏移。结合上述给出的变量地址分布图可知,offsetof(struct demo_struct, member3)
将返回0x10。
此时可得,__mptr==0xA010,offsetof(struct demo_struct, member3)==0x10。
(char *)__mptr - ((size_t) &((struct demo_struct *)0)->member3) == 0xA010 - 0x10 == 0xA000
,也就是结构体变量demo的首地址
这就是从结构体某成员变量指针来求出该结构体的首指针。指针类型从结构体某成员变量类型转换为该结构体类型。
由此,container_of实现了根据一个结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针的功能。
同样的,根据以上思路套用在:
(struct test *)((char *)__mptr - offsetof(struct test,j));
可以得到struct test的首地址。