Bootstrap

linux驱动开发中常用函数--container_of的用法及分析

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xi_xix_i/article/details/134625972

1. 开发环境

linux 4.19

2. container_of用法简介

container_of是一个宏定义,可以根据传入的结构体中某一变量的地址,返回该变量所在的结构体。其源码位于include/linux/kernel.h中,在Linux内核中的定义如下(可能有不同,我看过4.1版本的内核是没有警告语句的):

/**
 * 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) ({				\
	void *__mptr = (void *)(ptr);					\
	BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) &&	\
			 !__same_type(*(ptr), void),			\
			 "pointer type mismatch in container_of()");	\
	((type *)(__mptr - offsetof(type, member))); })

传入三个参数:

  • ptr,要返回的结构体中的某一变量。
  • type,要返回的结构体的类型。
  • member,该变量在结构体中的命名。

3. container_of详解

首先要知道,({})是GNU C编译器的语法扩展,其结果为最后一个语句的值,实验如下:

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    int a = 1, b =1;
    int c =({
        a = 2;
        b = 3;
        b;
    });
    printf("a:%d, b:%d, c:%d\n", a, b, c);

    return 0;
}

结果如下图所示:
在这里插入图片描述
实验2:

int main(void)
{
    int a = 1, b =1;
    int c =({
        a = 2;
        b = 3;
        int d = 5;
        a + d;
    });
    printf("a:%d, b:%d, c:%d\n", a, b, c);

    return 0;
}

结果如下:
在这里插入图片描述
同时{}也起到了限制变量作用域的效果,在{}内定义的变量的作用域只能在{}内,如果在作用域外使用则会报错:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void)
{
    int a = 1, b =1;
    int c =({
        a = 2;
        b = 3;
        int d = 5;
        d;
    });
    printf("a:%d, b:%d, c:%d d:%d\n", a, b, c, d);

    return 0;
}

结果如下所示:
在这里插入图片描述
所以container_of(ptr, type, member)最后的返回值其实是:((type *)(__mptr - offsetof(type, member)))。那么这到底是什么意思呢?该宏定义下面有三个语句:

  • void *__mptr = (void *)(ptr);
  • BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type )0)->member) && !__same_type((ptr), void), “pointer type mismatch in container_of()”);
  • ((type *)(__mptr - offsetof(type, member)))

第一个语句是定义了一个名为__mptrvoid*类型指针,其值等于传入的结构体中的某一变量的地址。第二个语句是对传入的参数做检查,先不考虑这个。最后一个语句中调用了另外一个宏:offsetof,该宏定义位于include/linux/stddef.h中,该宏牵扯到的另外一个宏__compiler_offsetof定义在include/linux/compiler_typys.h中:

include/linux/stddef.h: 
#undef offsetof
#ifdef __compiler_offsetof
#define offsetof(TYPE, MEMBER)	__compiler_offsetof(TYPE, MEMBER)
#else
#define offsetof(TYPE, MEMBER)	((size_t)&((TYPE *)0)->MEMBER)
#endif

include/linux/compiler_typys.h:
#define __compiler_offsetof(a, b)	__builtin_offsetof(a, b)

可以看到,因为定义了__compiler_offsetof宏,所以offsetof(type, member)展开后即为__builtin_offsetof(type, member)

这篇文章中提到,这些__builtin_开头的符号其实是一些编译器内置的函数或者编译优化处理开关等,其作用类似于宏。宏是高级语言用于预编译时进行替换的源代码块,而内置函数则是用于在编译阶段进行替换的机器指令块。因此编译器的这些内置函数其实并不是真实的函数,而只是一段指令块,起到编译时的内联功能。

而这里的__builtin_offsetof的作用,其实就与没有定义__compiler_offsetof宏时的offsetof(TYPE, MEMBER)的表达式((size_t)&((TYPE *)0)->MEMBER)的作用是一样的。当取结构体中某一变量的地址时,获取到的是该变量的绝对地址。因为结构体的内存空间是连续的,所以如果知道该变量在结构体内的偏移值,则可知道结构体的首地址,即可得到该结构体。

那么如何来获取该变量在结构体内的偏移值呢?就是通过&((TYPE *)0)->MEMBER)来获取。若结构体的首地址为0的话,那么获取的变量地址既是绝对地址,也是在结构体内的偏移值。

然后根据我们传入的该变量的绝对地址__mptr,减去所获取的偏移值,即可得到该变量所在结构体的首地址,然后返回即可。

4. container_of测试

编写如下代码进行测试,因为有些__builtin_开头的一些编译器内置的函数无法使用,所以我对其进行了修改和替换,但是核心代码没有变化。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// #define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)
// #define __compiler_offsetof(a, b)	__builtin_offsetof(a, b)
// #define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))

// #define compiletime_assert(condition, msg) \
// 	_compiletime_assert(condition, msg, __compiletime_assert_, __LINE__)

#ifdef __compiler_offsetof
#define offsetof(TYPE, MEMBER)	__compiler_offsetof(TYPE, MEMBER)
#else
#define offsetof(TYPE, MEMBER)	((size_t)&((TYPE *)0)->MEMBER)
#endif

#define container_of(ptr, type, member) ({				\
	void *__mptr = (void *)(ptr);					\
	/* BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) &&	\
	 		 !__same_type(*(ptr), void),			\
	 		 "pointer type mismatch in container_of()");*/	\
	((type *)(__mptr - offsetof(type, member))); })

typedef struct test_s{
    int member_a;
    char member_b;
    int member_c;
}test_s;

int main(void)
{
    test_s test = {
        .member_a = 1,
        .member_b = 'b',
        .member_c = 3,
    };
    test_s* test_pointer = container_of(&test.member_b, test_s, member_b);
    printf("test.member_b address:0x%x,  offsetof(test_s, member_b):0x%x\n", &(test.member_b), offsetof(test_s, member_b));
    printf("test address:0x%x,  test_pointer:0x%x\n", &test, test_pointer);
    printf("test_pointer->member_c:%d\n", test_pointer->member_c);
    return 0;
}

测试结果如下:
在这里插入图片描述
通过加减法运算可以看到test.member_b的地址0xd8a0减offset即0x4(通过理论分析也可知,member_b的offset是经过一个int类型的member_a,一个int类型恰好为0x4)恰好等于0xd89c,正如前面分析的逻辑一样。通过理论分析也可以知道,最后也成功打印出了testmember_c的值。

;