【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了 :)

相关推荐
gopher951116 小时前
linux驱动开发-设备树
linux·驱动开发
三菱-Liu1 天前
三菱变频器以模拟量电流进行频率设定(电流输入)
驱动开发·单片机·嵌入式硬件·硬件工程·制造
三菱-Liu2 天前
三菱FX5U CPU 内置以太网功能
网络·驱动开发·硬件工程·制造·mr
让开,我要吃人了2 天前
OpenHarmony鸿蒙( Beta5.0)摄像头实践开发详解
驱动开发·华为·移动开发·harmonyos·鸿蒙·鸿蒙系统·openharmony
OH五星上将3 天前
如何更换OpenHarmony SDK API 10
驱动开发·嵌入式硬件·sdk·harmonyos·openharmony·鸿蒙开发
OH五星上将4 天前
OpenHarmony(鸿蒙南向开发)——标准系统移植指南(二)Linux内核
linux·驱动开发·嵌入式硬件·移动开发·harmonyos·鸿蒙开发·鸿蒙内核
芊言芊语4 天前
蓝牙驱动开发详解
驱动开发
让开,我要吃人了5 天前
OpenHarmony鸿蒙( Beta5.0)RTSPServer实现播放视频详解
驱动开发·嵌入式硬件·华为·移动开发·harmonyos·鸿蒙·openharmony
OH五星上将5 天前
OpenHarmony(鸿蒙南向开发)——轻量和小型系统三方库移植指南(二)
驱动开发·移动开发·harmonyos·内存管理·openharmony·鸿蒙内核·鸿蒙移植
CS_素锦少年5 天前
Linux_kernel驱动开发11
linux·运维·驱动开发