container_of() 宏的源码分析

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

一个断言,用于检查 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()

最后我们再来看一看 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__又是什么呢?它的功能有两个:

综上所述,第二行的作用就是:判断传入的 ptrmember(或者 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/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • docker-compose安装kafka集群并解决docker内kafka外界无法访问问题

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    大数据 2023年2月26日
    0107
  • Eureka高可用

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    大数据 2023年3月1日
    071
  • 大数据技术习题

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    大数据 2023年2月20日
    083
  • SQL Server 数据库创建与删除

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    大数据 2023年2月17日
    074
  • flink 应用场景(实时处理dataflow)

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    大数据 2023年4月22日
    058
  • hbase自带mapreduce计数表行数功能

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    大数据 2022年8月8日
    0286
  • 当我尝试问了chatGPT几个问题之后,我感到了危机……

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    大数据 2023年3月2日
    080
  • hive:常见日期函数

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    大数据 2023年2月16日
    066
  • MySQL EXISTS 关键字使用

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    大数据 2023年2月16日
    068
  • Burp Suite Professional 2022.7 (macOS, Linux, Windows)Web 应用安全、测试和扫描

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    大数据 2022年10月7日
    0250
  • ubuntu编译安装sqlite3

    2 代码编译 1 可以自己创建一个文件夹比如说 mkdir mysqlite32 解压压缩包tar zxvf sqlite-autoconf-3110000.tar.gz -C s…

    大数据 2023年11月12日
    038
  • kong管理界面konga的安装

    kong网关自身的管理界面属于付费的应用,而第三方界面又非常少,konga算是相对比较好的一款了,虽然也有一些问题,但整体的功能还比较全,github仓库为:https://git…

    大数据 2023年6月2日
    0117
  • Android JetPack简介

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    大数据 2023年2月6日
    084
  • ZooKeeper从源码到RPM包制作过程详解

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    大数据 2023年3月1日
    076
  • Flink单元测试用法讲解

    注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

    大数据 2023年4月22日
    069
  • ClickHouse监控及备份

    1. ClickHouse 监控概述 ClickHouse 运行时会将一些自身的运行状态记录到众多系统表中(system.*)。所以对于 ClickHouse 自身的一些运行指标的…

    大数据 2023年6月3日
    045
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球