linux 内核中 container_of  宏 的详细分析及使用场景说明

container_of 是 Linux 内核中一个非常重要且广泛使用的宏。它允许你从一个指向结构体成员的指针反向获取整个结构体的指针。下面来详细解释它:

container_of 宏的定义 通常定义在内核源码的这个头文件 <linux/kernel.h>

#define container_of(ptr, type, member) ({				\
	void *__mptr = (void *)(ptr);					\
	BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) &&	\
			 !__same_type(*(ptr), void),			\
			 "pointer type mismatch in container_of()");	\
	((type *)(__mptr - offsetof(type, member))); })

让我们逐行解析这个宏:

  1. #define container_of(ptr, type, member) ({ ... })
    • 这是一个宏定义,它接受三个参数:
      • ptr: 指向结构体成员的指针。
      • type: 包含该成员的结构体的类型。
      • member: ptr 所指向的成员在结构体 type 中的名称。
    • ({...}):这是 GCC 的一个扩展,称为“语句表达式 (Statement Expression)”。它允许一个代码块像一个表达式一样返回一个值,这个值就是代码块中最后一条语句的值。这使得宏可以更安全地引入局部变量并返回计算结果。
  2. void *__mptr = (void *)(ptr);
    • ptr: 这是传入的指向成员的指针。
    • (void *)(ptr): 将 ptr 强制转换为 void * 类型。这样做是为了进行通用的指针运算,避免编译器因为类型不匹配而发出警告或错误。void * 是一种通用指针,可以指向任何类型的数据。
    • void *__mptr: 声明一个名为 __mptrvoid * 类型的临时指针变量,并用转换后的 ptr 初始化它。变量名前的 __ (双下划线) 是一种常见的约定,用于表示这是宏内部使用的临时变量,以避免与用户代码中的变量名冲突。
  3. BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && !__same_type(*(ptr), void), "pointer type mismatch in container_of()");
    • 这是一个编译时检查,用于确保类型的正确性,是 container_of 宏安全性的关键。
    • __same_type(A, B): 这通常是内核内部实现的另一个宏或编译器内置功能,用于检查类型 A 和类型 B 是否完全相同。如果相同,返回 1 (true),否则返回 0 (false)。
    • *(ptr): 对 ptr 指针进行解引用,获取该成员本身。这主要用于类型检查。
    • ((type *)0)->member: 这是一个巧妙的技巧,用于获取结构体 type 中成员 member 的类型,而无需一个实际的结构体实例。
      • (type *)0: 将整数 0 (空指针) 强制转换为指向 type 类型的指针。
      • ->member: 通过这个空指针访问成员 member。编译器在编译时知道 member 的类型。
    • !__same_type(*(ptr), ((type *)0)->member): 检查 ptr 指向的实际类型(通过 *(ptr) 获取)是否与 type 结构体中 member 成员的声明类型不同。
    • !__same_type(*(ptr), void): 允许 ptr 本身是一个 void*。如果 ptrvoid*,那么 *(ptr) 的类型也是 void,这个检查确保如果 ptrvoid*,则不会触发类型不匹配错误(因为 __mptr 已经将其转换为 void*)。主要目的是第一个类型检查。
    • &&: 逻辑与操作。
    • BUILD_BUG_ON_MSG(condition, message): 如果 condition 为真 (即类型不匹配,并且 ptr 指向的类型也不是 void),这个宏会在编译时产生一个错误,并显示 message (“pointer type mismatch in container_of()”)。这有助于在早期发现潜在的编程错误。
  4. ((type *)(__mptr - offsetof(type, member)));
    • 这是宏的核心计算部分,用于获取容器结构体的起始地址。
    • offsetof(type, member): 这是一个标准的 C 宏 (定义在 <stddef.h> 中,内核有自己的实现),它返回成员 member 在结构体 type 中的偏移量 (以字节为单位),即从结构体起始地址到该成员起始地址的字节数。
    • __mptr - offsetof(type, member): 进行指针运算。__mptr 指向成员的起始地址。从这个地址减去该成员在结构体中的偏移量,得到的就是整个结构体的起始地址。由于 __mptrvoid *,指针运算通常按字节 (char *) 进行。
    • (type *): 将计算得到的地址 (它实际上是一个 void *char * 类型) 强制转换为指向 type 类型的指针。
    • 这个表达式的值就是整个 container_of 宏的返回值,即指向包含 ptr 所指成员的 type 类型结构体的指针。

使用场景

container_of 在 Linux 内核中被广泛使用,尤其是在处理链表、回调函数和面向对象风格的编程时:

  1. 链表操作
    • 内核中的链表 (struct list_head) 通常是嵌入到其他结构体中的。当你遍历链表并获得一个 list_head 成员的指针时,你需要使用 container_of 来获取包含该 list_head 的整个数据结构。
    • 例如,list_for_each_entry(pos, head, member) 宏内部就依赖 container_of
  2. 回调函数
    • 当注册一个回调函数时,有时你可能会传递一个指向某个结构体成员的指针作为上下文。在回调函数被调用时,如果需要访问整个结构体,就可以使用 container_of
  3. 面向对象设计
    • 在 C 语言中模拟面向对象编程时,一个通用的“基类”结构体可能被嵌入到一个更具体的“派生类”结构体中。如果你有一个指向基类部分的指针,可以使用 container_of 来获取整个派生类结构体的指针。例如,内核中的 kobject 系统。
  4. 设备驱动程序
    • 驱动程序中常常会将一个通用的设备结构体 (如 struct device) 嵌入到特定设备的结构体中。通过通用结构体的指针,可以用 container_of 找到特定设备结构体的指针。

重要性和优势

  1. 代码复用和模块化
    • 允许编写通用的数据结构操作代码 (如链表处理函数),这些函数只需要知道成员的类型和名称,而不需要知道包含该成员的特定结构体类型。这极大地增强了代码的复用性。
  2. 类型安全
    • 通过 BUILD_BUG_ON_MSG__same_type 检查,container_of 在编译时就能捕捉到许多类型不匹配的错误。这比在运行时发生难以追踪的内存损坏要好得多。
  3. 代码清晰度和可读性
    • container_of(ptr, type, member) 的意图非常明确:从成员指针获取容器指针。这比手动进行指针运算和偏移量计算要清晰得多,也更不容易出错。
  4. 灵活性
    • 使得在结构体中嵌入通用组件 (如锁、引用计数器、链表头) 变得非常方便,同时还能轻松地从这些组件的指针回到主结构体。
  5. 效率
    • 由于它是一个宏,并且主要进行编译时检查和简单的指针运算,所以几乎没有运行时开销。计算结果在编译时就已确定或能高效计算。

本文版权归原作者zhaofujian所有,采用 CC BY-NC-ND 4.0 协议进行许可,转载请注明出处。

发表评论