Linux内核中container_of的原理及其使用详解

前言

在进行内核驱动开发的时候,经常可以看到container_of的身影,其作用就是获取包含某个成员的结构体变量地址,函数原型如下所示;

#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})

宏定义

container_ofLinux内核源码/linux/kernel.h中,其宏定义如下所示;

#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif

/**
 * 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) );})

如何使用

#include <stdio.h>

#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif

/**
 * 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) );})


struct human_mod{

    int     head;
    char    eye;
    float   foot;
};


int main(){

    struct human_mod me;
    struct human_mod *p;
    me.head = 1;
    me.eye  = 2;
    me.foot = 2;

    p = (struct human_mod *)container_of(&me.head, struct human_mod, head);

    printf("head:%d\r\n",p->head);
    printf("eye :%d\r\n",p->eye );
    printf("foot:%f\r\n",p->foot);

    return 0;
}

测试结果如下所示;

简单分析

typeof

在标准C中是不支持typeof语法的,在gnu扩展语法中可以支持;

typeof可以得到表达式的类型;
具体参考:https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html#C-Extensions

offsetof

这条语句的目的就是获取TYPE类型中MEMBER成员的相对偏移地址;
在C标准库中(stddef.h)也可以找到offsetof;由于在内核空间中没有标准C库,因此就出现了下面的宏定义;

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

这样写会不会报错?
答案是不会,因为编译器只关心类型,只要不存在访问非法地址的内容就不会出现问题,那也是程序运行时的事情,而非编译时的事情;

扫描二维码关注公众号,回复: 11482845 查看本文章

也就是我们经常说的访问野指针,空指针,但是上述语句显然没有对指针指向的内存进行访问。

下面先简单测试一下:

#include <stdio.h>

#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif

int main(){

    printf("head:%d\r\n",offsetof(struct human_mod , head));
    printf("eye :%d\r\n",offsetof(struct human_mod , eye));
    printf("foot:%d\r\n",offsetof(struct human_mod , foot));
    return 0;
}

在这里面究竟发生了哪些事情;

简单解释一下,基本上是以下几个步骤;

    1.                         0
    2.                ((TYPE *)0)
    3.              ( ((TYPE *)0)->MEMBER )
    4.             &( ((TYPE *)0)->MEMBER )
    5.  ( (size_t) &( ((TYPE *)0)->MEMBER )
  • 先将地址指向0
  • 强制转换成TYPE类型(这里的类型是由我们来输入的);
  • 得到TYPE类型指针指向的MEMBER成员;
  • 取出MEMBER成员的地址;
  • 转换成size_t类型;

整体的过程,结合上面的代码,具体如下图所示;

最终的运行结果;
运行结果如下:

写在最后

当我们从这篇文章的开头开始查看这个宏定义的时候,甚至会怀疑第一行是否真的正确的。但事实证明,这样使用是正确的;

#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})

  • 第一行对于宏的结果并不是本质上重要的,但是它用于类型检查。
  • 第二行真正的作用是什么?它从成员地址减去结构体成员的偏移量,从而得出结构体变量的实际地址。如此而已;

在最终剥离这些眼花缭乱的运算符,结构和技巧之后,整体就相对显得比较简单了。

猜你喜欢

转载自blog.csdn.net/u010632165/article/details/107523477