在内核的代码中很多重要的数据结构之间的转化,都用到了一个统一的接口,那就是container_of(ptr, type, member),因此,很有必要对它做个详细的分析,以便我们能更好的理解内核的设计即c语言的使用技巧。
#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);
我们按照从内向外一层一层的分析:
((type *)0) 就是把0转换成指向type数据类型的指针,这是很常见的用法,即取type类型的这种数据结构的首地址。那么0就是type类型数据结构的首地址,这是个编译的时候确定的线性地址。
然后网外层看typeof( ((type *)0)->member ),((type *)0)->member 即取type数据结构内的member成员变量,然后在执行typeof运算,typeof()是取其参数的数据类型,并把它以字符串的形式返回。
举个例子:int a;那么typeof(a) b; <=> int b;那么这里就是定义了一个指向和member相同数据类型的指针*__mptr.
(2)(type *)( (char *)__mptr - offsetof(type,member) );
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
这儿的意思很明显了,取MEMBER数据成员的线性地址。
(char *)__mptr - offsetof(type,member)上面提到(char *)__mptr 是type数据结构的首址,那么二者相减就是member成员在数据结构内的偏移地址。
(3)还有一个知识点就是:多行宏的返回值是最后一个语句的值。那么这个宏container_of(ptr, type, member)的返回值就是member成员在type数据结构内的偏移地址了。
经过以上分析,我们再从内核中举几个例子来做一个更深入的例子(因为这段时间在研究组调度,就以组调度中的一个函数为例):
这个函数的功能就是cgroup转换到task_group结构,现在我们就来详细分析下他为什么要这么做以及他这么做的具体的过程是什么样的。
那我们就从linux CFS组调度说起。
a. cgroup_subsys_state(cgrp,cpu_cgroup_sunsys_id)返回指向cgroup_subsys_state的指针,即cgrp->subsys[cpu_cgroup_sunsys_id].
这样就可以确定container_of()的三个参数的具体值了:
ptr= *cgroup_subsys_state
type = struct task_group