ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

container_of() 宏的源码分析

2022-05-01 14:34:37  阅读:182  来源: 互联网

标签:分析 __ container assert member 源码 type ptr


简介

container_of(ptr, type, member)是内核中的经典函数之一。该函数的作用是:根据结构体中一个成员的地址,找到结构体的地址。这个函数是内核实现面向对象的基础设施,且最近在学习中经常见到这个函数,于是笔者在内核中查看了该函数的实现,故在此记录。本文原本是为了展示container_of的实现,但写着写着,发现有些内建函数与GNU C拓展的使用,所以就顺便查了资料,也一并记录于此,写得比较乱,请大家谅解。

基础知识

结构体在内存中的分布,是按照成员的顺序分配内存,同时保持内存对齐的要求

实现分析

源码

该函数在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

一个断言,用于检查ptrmember的类型一致性。这个断言函数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()

typeof()想必大家都比较熟悉了,它是一个GNU C的拓展,作用是获取变量的类型。文档地址:https://gcc.gnu.org/onlinedocs/gcc/Typeof.html
__builtin_types_compatible_p(type1, type2)是一个GNU C的内建函数,用于比较两个类型是否相等,若相等则返回1,不等则返回0。需要注意的是,这个函数的参数并不是表达式,而是变量类型,所以需要使用typeof()先取得变量类型后再传入。文档地址:https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html

最后我们再来看一看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宏定义中#符号的用法,可以总结为以下两点:

  1. 前加##,转换为合法标识符
#define to_symbol(x)  T_##x

// 下面这句等效于 int T_1 = 10;
int to_symbol(1) = 10;
  1. 前加#,转换为字符串
#define to_string(x) #x

// 下面这句等效于 "a+b+c"
to_string(a+b+c);

##__VA_ARGS__又是什么呢?它的功能有两个:

  1. 如果可变参数列表为空,使编译器忽略它以及它前面的逗号
  2. 如果可变参数列表不为空,编译器将其替换为可变参数列表

接着再来看一看_Static_assert(expr, msg, ...),这是一个C11特性,用来在编译时测试expr的正确性,如果正确则什么都不会发生,如果错误,则打印指定信息msg。文档地址:https://www.gnu.org/software/gnulib/manual/html_node/assert_002eh.html

综上所述,第二行的作用就是:判断传入的ptrmember(或者void)是否为同一类型,若否,则打印"pointer type mismatch in container_of()"

第三行:寻址

这一行真正用于获取结构体的地址。

((type *)(__mptr - offsetof(type, member)));

看上去很简单!就是用传进来的成员变量地址值减去它在结构体里的偏移值嘛!
逻辑上来讲确实很简单,但是如何实现呢?如何在不同的对齐下让这个函数均能成功运行呢?让我们带着这个疑问走进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中的检查和寻址操作。
在最后希望询问看到这篇文章的朋友们一个问题:为什么最新的版本还需要赋值给__mptr,能否在第三行中直接使用(void *)ptr代替__mptr

原创文章,如有错漏,敬请补充指正,如对于文章风格有建议,请在评论区直接提出,感谢。

标签:分析,__,container,assert,member,源码,type,ptr
来源: https://www.cnblogs.com/puhanzhou/p/16212798.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有