container_of(ptr, type, member)
是内核中的经典函数之一。该函数的作用是:根据结构体中一个成员的地址,找到结构体的地址。这个函数是内核实现面向对象的基础设施,且最近在学习中经常见到这个函数,于是笔者在内核中查看了该函数的实现,故在此记录。本文原本是为了展示 container_of
的实现,但写着写着,发现有些内建函数与GNU C拓展的使用,所以就顺便查了资料,也一并记录于此,写得比较乱,请大家谅解。
结构在内存中的分布是在保持内存对齐的同时按成员顺序分配内存的要求。
[En]
The distribution of structures in memory is the requirement of allocating memory in the order of members while maintaining memory alignment.
该函数在5.17.5中的实现在 include/linux/container_of.h
中
5.16之前,这个宏都被放在
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) ({ \
void *__mptr = (void *)(ptr); \
static_assert(__same_type(*(ptr), ((type *)0)->member) || \
__same_type(*(ptr), void), \
"pointer type mismatch in container_of()"); \
((type *)(__mptr - offsetof(type, member))); })
- ptr:成员指针
- type:结构体类型
- mem:成员在结构体里的名称
第一行:赋值
将传入的成员变量的地址,转换为 void *
类型,并赋给另一个值。这个操作笔者没有理解,所以找了以前版本的源码来进行分析,在2.6.23里,他的实现是这样的:
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
这个版本中,第一行的作用其实相当于赋值+检查,考虑如果传进来的指针类型和member不一致,编译器会报warning。
在使用查看了相关的log之后,发现这个宏是在提交 c7acec713d14c
被改变的,改变的原因是:如果结构体内引入了一个非const数组成员,那么这个指针就会产生变量赋值给常量的问题,这会在gcc-4.9中产生一个 warning: initialization from incompatible pointer type
。这一笔改动抽离出了类型检查,但 __mptr
仍留在原处,笔者实在不清楚这个操作的深意,又或许只是历史遗留问题?(已更新,请查看尾部的”更新”章节)
第二行:检查
static_assert(__same_type(*(ptr), ((type *)0)->member) || \
__same_type(*(ptr), void), \
"pointer type mismatch in container_of()"); \
这个地方在5.16后修改成static_assert,之前使用的是
BUILD_BUG_ON()
这个宏,他和static_assert被定义在同一个文件里,感兴趣的朋友们可以去看一看相关实现,根据commit message显示,使用static_assert
可以给出更加直接的错误提示,并且在理论上可以提升一点点的build速度(commit message里写了a tiny bit faster
)
一个断言,用于检查 ptr
和 member
的类型一致性。这个断言函数 static_assert()
我们先放在一边,来分析一下这个断言的第一个参数:内部使用了 __same_type()
这个宏,来看看这个宏的实现:
/* Are two types/vars the same type (ignoring qualifiers)? */
#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
这个宏使用了两个函数: __builtin_types_compatible_p()
和 typeof()
。
最后我们再来看一看 static_assert()
,这个函数的实现位于 include/linux/build_log.h
,源码如下:
#define static_assert(expr, ...) __static_assert(expr, ##__VA_ARGS__, #expr)
#define __static_assert(expr, msg, ...) _Static_assert(expr, msg)
关于C宏定义中 #
符号的用法,可以总结为以下两点:
#define to_symbol(x) T_##x
// 下面这句等效于 int T_1 = 10;
int to_symbol(1) = 10;
#define to_string(x) #x
// 下面这句等效于 "a+b+c"
to_string(a+b+c);
那 ##__VA_ARGS__
又是什么呢?它的功能有两个:
综上所述,第二行的作用就是:判断传入的 ptr
和 member
(或者 void
)是否为同一类型,若否,则打印 "pointer type mismatch in container_of()"
第三行:寻址
这行实际上是用来获取结构的地址的。
[En]
This line is really used to get the address of the structure.
((type *)(__mptr - offsetof(type, member)));
看起来很简单!它是从传递的成员变量的地址值中减去结构中的偏移量。
[En]
It looks simple! It is to subtract the offset value in the structure from the address value of the passed member variable.
逻辑上来讲确实很简单,但是如何实现呢?如何在不同的对齐下让这个函数均能成功运行呢?让我们带着这个疑问走进 offsetof()
这个宏:
// At include/linux/stddef.h
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
也很简单对吧?把0地址转换成结构体类型指针,然后利用这个特殊的结构体指针获取 member
,然后再对 member
取地址,得到的这个值就是 member
相对于0地址的偏移值,这个偏移值不就是member相对于结构体首地址的偏移值嘛!
看到这里,如果你和笔者一样是内核初学者,你可能会和笔者一样惊讶:0地址还能这么用?!!笔者也是在发出了这样的感叹之后,才决定记录下这篇随笔。
offsetof
这个宏还有另一个实现,即调用GNU C的内建函数 __builtin_offsetof
,本质上和上面的定义是一致的。
该宏由三个步骤组成:赋值、检查和寻址。作者分析了2.6.23中赋值操作的目的和最新版本5.17.5中的检查和寻址操作。
[En]
This macro consists of three steps: assignment, check, and addressing. The author analyzes the purpose of the assignment operation in 2.6.23 and the check and addressing operation in the latest 5.17.5.
在最后希望询问看到这篇文章的朋友们一个问题:为什么最新的版本还需要赋值给 __mptr
,能否在第三行中直接使用 (void *)ptr
代替 __mptr
?
#define container_of(ptr, type, member) ({ \
static_assert(__same_type(*(ptr), ((type *)0)->member) || \
__same_type(*(ptr), void), \
"pointer type mismatch in container_of()"); \
((type *)((void *)(ptr) - offsetof(type, member))); })
原文如有错误或遗漏,请补充更正。如果你对文章的风格有任何建议,请直接提交到评论区。谢谢。
[En]
If there are any mistakes or omissions in the original articles, please add and correct them. If you have any suggestions on the style of the articles, please submit them directly in the comments section. Thank you.
Original: https://www.cnblogs.com/puhanzhou/p/16212798.html
Author: PuhanZhou
Title: container_of() 宏的源码分析
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/523722/
转载文章受原作者版权保护。转载请注明原作者出处!