container_of是 Linux 内核中一个非常重要的宏,主要用于通过结构体的某个成员指针反向获取包含这个成员的整个结构体的指针。这种技巧在内核代码中被广泛使用,特别是在嵌套数据结构、回调函数、链表操作等场景。
我们逐行解析这个宏,并详细说明其工作原理、应用场景以及所涉及的机制。

1、相关宏定义

__same_type、offsetof、container_of 不在一个文件,分别在 linux-6.1.83/include/linux/compiler_types.h、linux-6.1.83/include/linux/stddef.h、linux-6.1.83/include/linux/container_of.h 中,下面详细介绍。

/* Are two types/vars the same type (ignoring qualifiers)? */
#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))

#define offsetof(TYPE, MEMBER)	__builtin_offsetof(TYPE, MEMBER)

/**
 * 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))); })

container_of 参数说明
ptr:

  • 指向结构体成员的指针。

  • 宏的输入是一个结构体成员的地址,通过它反向推导出包含这个成员的结构体的地址。

type:

  • 包含该成员的结构体类型。

  • 用于告诉宏目标结构体的类型,以便计算偏移量。

member:

  • 结构体中的成员名称。

  • 宏通过这个成员名称计算成员在结构体中的偏移量。

2、container_of逐行解析

2.1、void *__mptr = (void *)(ptr);

  • 将 ptr 转换为 void * 类型并存储在一个临时变量 __mptr 中。
  • 这样做的目的是避免编译器对指针类型进行不必要的类型检查,确保宏的通用性。
  • void * 是 C 中的通用指针类型,适用于任意类型的指针。

2.2、static_assert(__same_type(*(ptr), ((type )0)->member) || __same_type((ptr), void), “pointer type mismatch in container_of()”);

static_assert:

  • 编译时断言(静态断言),用于在编译阶段验证某些条件是否成立。
  • 如果条件不满足,编译器会报错并输出指定的错误消息。

__same_type(a, b):

  • __same_type 宏调用__builtin_types_compatible_p,它是 GCC(GNU Compiler Collection)提供的一个编译器内建函数(builtin function),并不是 Linux 内核内部定义的函数。它是 GCC
    的一个扩展,用于在编译时判断两个类型是否兼容。类型相同,返回true;类型不同,返回false。
  • __same_type(*(ptr), ((type )0)->member) :(ptr) 是对指针 ptr 解引用,得到指针所指向的类型。如果ptr是int 类型,则(ptr)是int类型。((type *)0)->member 是将
    0(即空指针)强制转换为type *类型,这样我们就得到了一个指向type类型的空指针。通过空指针访问 type 结构体的 member
    成员。这里不会真正访问内存地址,而是编译器通过类型信息推导出 member 的类型。
  • __same_type(*(ptr), void) :这个与上面的同理。
  • static_assert 就是判断 __same_type(*(ptr), ((type )0)->member) 与
    __same_type(
    (ptr), void) 只要有1个是true就可以继续执行的,当它们都是false的时候触发assert。

作用:

  • 验证 ptr 指向的类型是否与 type 结构体的 member 成员的类型一致。
  • 如果类型不匹配,编译器会报错,输出 “pointer type mismatch in container_of()”。

目的:

  • 提高代码的类型安全性,避免因指针类型错误导致的运行时错误。

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

offsetof(type, member):

  • offsetof中__builtin_offsetof是GCC提供的一个内建函数(builtin
    function),用于计算结构体成员的偏移量。它是 GCC 的扩展,并且也被 Clang 等一些兼容 GCC 的编译器支持。
  • 用于计算 member 在 type 结构体中的偏移量(以字节为单位)。
  • 例如,如果 member 是 type 结构体的第一个成员,偏移量为 0;如果是第二个成员,偏移量是第一个成员的大小。

__mptr - offsetof(type, member):

  • 通过 ptr 减去 member 的偏移量,得到结构体 type 的起始地址。

(type *):

  • 将结果强制转换为 type * 类型,即返回目标结构体的指针。

3、应用场景

3.1、从嵌套成员反向获取结构体指针

在内核中,许多数据结构是通过嵌套的方式组织的。例如,链表节点通常嵌套在用户定义的结构体中:

struct my_struct {
    int data;
    struct list_head list; // 链表节点
};

struct list_head *node = ...; // 某个链表节点的指针
struct my_struct *entry = container_of(node, struct my_struct, list);

这里的 container_of 用于从链表节点指针 node 反向获取包含它的 my_struct 结构体的指针。

3.2、回调函数中的上下文获取

在 C 语言中,回调函数通常只提供指向某个成员的指针,而需要通过 container_of 获取包含该成员的结构体。例如:

struct my_device {
    struct device dev;
    int id;
};

void my_callback(struct device *dev) {
    struct my_device *my_dev = container_of(dev, struct my_device, dev);
    // 通过 my_dev 获取整个 my_device 结构体
}

3.3、Linux 内核链表实现

Linux 内核的链表实现中,container_of 是核心宏。例如:

#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)

list_entry 是基于 container_of 的封装,用于从链表节点获取包含它的结构体。

3.4、总结

container_of是一个非常强大且通用的宏,用于通过结构体成员指针反向获取包含该成员的结构体指针。它的实现依赖于偏移量计算、指针运算和类型检查,广泛应用于嵌套数据结构、回调函数和链表操作等场景,特别是在 Linux 内核中,是一种高效、灵活的编程技巧。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐