本文面向Linux初学者,通过实例解析container_of
宏的工作原理与应用场景
什么是container_of
宏?
container_of
是Linux内核中最重要且最常用的宏之一,它被称为"Linux内核第一宏"。这个宏的神奇之处在于:通过结构体成员的地址,反向获取包含该成员的完整结构体地址。
为什么需要这个宏?
在内核开发中,我们经常遇到这种情况:
-
某个结构体包含多种数据成员
-
我们只能访问到其中某个特定成员的指针
-
但实际需要操作的是整个结构体
这时container_of
就派上用场了!它在链表实现、设备驱动、内核对象管理等场景中广泛应用。
container_of
宏的定义
在Linux内核源码中(通常位于include/linux/kernel.h
或include/linux/stddef.h
),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)); })
注:不同内核版本可能有细微差异,但核心思想相同
关键概念解析
1. 宏参数说明
参数 |
说明 |
|
指向结构体成员的指针 |
|
包含该成员的结构体类型 |
|
结构体中该成员的名称 |
2. offsetof
宏
offsetof
是另一个关键宏,用于计算结构体成员在结构体中的偏移量:
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
这个宏的巧妙之处在于:
-
将地址
0
强制转换为(TYPE *)
类型 -
获取成员
MEMBER
的地址 -
这个地址值就是成员相对于结构体起始位置的偏移量
图解container_of
工作原理
计算公式:
结构体地址 = 成员地址 - 成员在结构体中的偏移量
实际代码示例
基础示例
#include <stdio.h>
#include <stddef.h> // 包含offsetof定义
// 定义结构体
struct person {
int age;
char *name;
double height;
};
// 简化版container_of实现
#define my_container_of(ptr, type, member) ({ \
(type *)((char *)(ptr) - offsetof(type, member)); })
int main() {
struct person john = {25, "John Doe", 180.0};
// 获取name成员的指针
char **name_ptr = &john.name;
// 通过container_of获取完整结构体指针
struct person *p = my_container_of(name_ptr, struct person, name);
printf("Original struct address: %p\n", &john);
printf("Calculated struct address: %p\n", p);
printf("Name: %s\n", p->name);
return 0;
}
内核链表应用实例
#include <stdio.h>
#include <stddef.h>
// 链表节点结构
struct list_head {
struct list_head *next, *prev;
};
// 包含链表节点的用户结构
struct my_data {
int id;
char data[32];
struct list_head list; // 内嵌链表节点
};
// container_of实现
#define container_of(ptr, type, member) ({ \
(type *)((char *)(ptr) - offsetof(type, member)); })
int main() {
struct my_data item1 = {1, "Sample Data", {NULL, NULL}};
// 获取list成员的指针(模拟在链表遍历中得到的情况)
struct list_head *list_ptr = &item1.list;
// 通过container_of获取包含list的完整结构体
struct my_data *parent = container_of(list_ptr, struct my_data, list);
printf("ID: %d, Data: %s\n", parent->id, parent->data);
return 0;
}
为什么container_of
如此重要?
-
高效的数据组织:允许在结构体中嵌入通用结构(如链表节点)
-
代码复用:通用链表操作不依赖具体数据结构
-
类型安全:在编译时进行类型检查
-
内存效率:避免额外的指针存储空间
常见问题解答
Q1: 为什么使用char *
指针运算?
因为char *
的指针运算以字节为单位,能精确计算偏移量。
Q2: ((TYPE *)0)->MEMBER
访问0地址不会崩溃吗?
不会!因为这是编译时计算,不会真正访问0地址内存。
Q3: 这个宏可以用于用户空间程序吗?
可以!但需要自己实现offsetof
和container_of
(如上例所示)。
学习建议
-
通过GDB调试观察地址变化
-
在内核源码中搜索
container_of
查看实际应用 -
尝试实现自己的链表结构
-
阅读《Linux内核设计与实现》相关章节
总结
container_of
宏展示了Linux内核的精妙设计:
-
小技巧解决大问题:通过地址运算实现反向查找
-
类型安全的泛型编程:避免void*带来的类型不安全
-
高效内存利用:减少冗余指针存储
掌握container_of
是理解Linux内核数据结构的基石,也是成为内核开发者的重要一步!