【Linux API 揭秘】container_of函数详解

【Linux API 揭秘】container_of函数详解

Linux Version:6.6

Author:Donge

Github:linux-api-insides

1、container_of函数介绍

container_of可以说是内核中使用最为频繁的一个函数了,简单来说,它的主要作用就是根据我们结构体中的已知的成员变量的地址,来寻求该结构体的首地址,直接看图,更容易理解。

下面我们看看linux是如何实现的吧

2、container_of函数实现

复制代码
/**
 * 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.
 *
 * WARNING: any const qualifier of @ptr is lost.
 */
#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))); })
​

函数名称container_of

文件位置include/linux/container_of.h

该函数里面包括了一些封装好的宏定义以及函数,比如:static_assert__same_typeoffsetof,以及一些指针的特殊用法,比如:(type *)0),下面我们一一拆解来看。

2.1 static_assert

复制代码
/**
 * static_assert - check integer constant expression at build time
 *
 * static_assert() is a wrapper for the C11 _Static_assert, with a
 * little macro magic to make the message optional (defaulting to the
 * stringification of the tested expression).
 *
 * Contrary to BUILD_BUG_ON(), static_assert() can be used at global
 * scope, but requires the expression to be an integer constant
 * expression (i.e., it is not enough that __builtin_constant_p() is
 * true for expr).
 *
 * Also note that BUILD_BUG_ON() fails the build if the condition is
 * true, while static_assert() fails the build if the expression is
 * false.
 */
#define static_assert(expr, ...) __static_assert(expr, ##__VA_ARGS__, #expr)
#define __static_assert(expr, msg, ...) _Static_assert(expr, msg)

函数名称static_assert

文件位置include/linux/build_bug.h

函数解析 :该宏定义主要用来 在编译时检查常量表达式,如果表达式为假,编译将失败,并打印传入的报错信息

  • expr:该参数表示传入进来的常量表达式

  • ...:表示编译失败后,要打印的错误信息

  • _Static_assertC11中引入的关键字,用于判断表达式expr并打印错误信息msg

container_of函数中,主要用来断言判断

复制代码
    static_assert(
        __same_type(*(ptr), ((type *)0)->member)  ||   __same_type(*(ptr), void) ,
        "pointer type mismatch in container_of()"
 );

2.2 __same_type

复制代码
/* Are two types/vars the same type (ignoring qualifiers)? */
#ifndef __same_type
# define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
#endif

函数名称__same_type

文件位置include/linux/compiler.h

函数解析该宏定义用于检查两个变量是否是同种类型

  • __builtin_types_compatible_pgcc的内建函数,判断两个参数的类型是否一致,如果是则返回1

  • typeofgcc的关键字,用于获取变量的类型信息

了解完__same_type,想要理解__same_type(*(ptr), ((type *)0)->member),需要先弄明白(type *)0的含义。

更多干货可见:高级工程师聚集地,助力大家更上一层楼!

2.3 (type *)0

(type *)0,该如何理解这个表达式呢?

  • 首先,type是我们传入进来的结构体类型,比如上面讲到的struct test,而这里所做的可以理解为强制类型转换(struct test *)addr

  • addr可以表示内存空间的任意的地址,我们在强制转换后,默认后面一片的内存空间存储的是该数据结构。

  • (type *)0的作用,也就是默认将0地址处的内存空间,转换为该数据类型。
  • 我们就把0,当作我们正常的addr地址变量来操作,((type *)0)->member,就是获取我们结构体的成员对象。

  • ((type *)0)->member:是一种常见的技巧,用于直接获取结构体type的成员member的类型,而不需要定义一个type类型的对象

2.4 offsetof

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

函数名称offsetof

文件位置include/linux/stddef.h

函数解析该宏定义用于获取结构体中指定的成员,距离该结构体偏移量。

  • TYPE:表示结构体的类型

  • MEMBER:表示指定的结构体成员

  • __builtin_offsetofgcc内置函数,直接返回偏移量。

在新的linux源码中,直接引用了gcc内置的函数,而在老的内核源码中,该偏移量的实现方式如下:

复制代码
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

同样用到了((TYPE *)addr),上面我们知道

  • ((TYPE *)addr)->MEMBER:表示获取该结构体的成员

  • &((TYPE *)addr)->MEMBER):加了一个&,表示地址,取该成员的内存地址。

    • 比如我们addr=0x00000010,那么&((TYPE *)0x00000010)->MEMBER)就相当于0x00000010+size

    • 比如我们addr=0,那么&((TYPE *)0)->MEMBER)就相当于size

到这里,我们对container_of函数内部涉及的相关知识了然于胸,下面我们再来看container_of,简直容易到起飞。

2.5 container_of

复制代码
#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))); })
  • static_assert:断言信息,避免我们传入的参数类型不对,而做的编译检查处理,直接忽略。
复制代码
#define container_of(ptr, type, member) ({              \
 void *__mptr = (void *)(ptr);     \
 ((type *)(__mptr - offsetof(type, member))); })
  • offsetof(type, member):计算的是结构体中的成员的偏移量,这里称为size

  • (__mptr - offsetof(type, member)):也就是根据我们已知的成员变量地址,计算出来结构体的首地址

  • ((type *)(__mptr - offsetof(type, member))):最后强制转换为(type *),结构体指针。

比如,我们已知的结构体成员的地址为0xffff0000,计算之后如下:

3、总结

linux内核中,小小的一个函数,内部包括的技巧如此之多:static_assert__same_type(type *)0offsetof

了解完内部完整的实现手法之后,我们也可以手码一个container_of了 :)

相关推荐
枳实-叶2 小时前
【Linux驱动开发】第10天:设备树零基础入门——DTS/DTB/DTC全解+编译流程
linux·运维·驱动开发
枳实-叶2 小时前
【Linux驱动开发】第11天:设备树(Device Tree)超详细全解:从诞生背景到工作原理
linux·运维·驱动开发
沃普天科技4 小时前
USB显示器驱动板触摸收款机显示器收银机点菜机广告机
驱动开发·游戏·计算机外设·电脑·ar·音视频·硬件工程
小此方4 小时前
Re:Linux系统篇(十九)进程篇·四:内核托底与生死交错 ,深度硬核剖析“僵尸”与“孤儿”进程
linux·运维·驱动开发
charlie11451419114 小时前
嵌入式Linux驱动开发pinctrl篇(1)——从寄存器到子系统:驱动演进之路
linux·运维·驱动开发
猫猫的小茶馆19 小时前
【Python】函数与模块化编程
linux·开发语言·arm开发·驱动开发·python·stm32
高翔·权衡之境1 天前
主题9:DMA与零拷贝——让CPU从数据搬运中解放
驱动开发·安全·缓存·系统安全·信息与通信
小此方1 天前
Re:Linux系统篇(十七)进程篇·二:深入浅出 [进程概念与进程父子关系]:从底层原理到实战应用
linux·运维·驱动开发
weixin_408099671 天前
【实战】用易语言 + OCR API 打造自动文字识别工具(通用文字识别/截图识别,附完整源码)
ocr·文字识别·api调用·api开发·易语言·截图识别·石榴智能
小此方1 天前
Re: Linux系统篇(十八)进程篇·三:深度硬核!全面起底 Linux 进程状态变化与内核链表动态解绑
linux·驱动开发·链表