源码基于:Linux 5.10
0.前言
container_of() 这个宏函数在Linux 内核中使用的频率还是很多的。网上关于 container_of 使用的优秀文章也很多,之所以笔者也写一篇,一是想更新下最新代码中的使用,二是融入些自己的拙见,方便自己回头查看,也希望能有助于后来读者。
1. 源码
include/linux/kernel.h
/**
* 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); \
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))); })
该宏函数主要用于通过一个结构体中成员的地址,转换获得该结构体的起始地址。
该宏函数有三个参数:
- ptr:该成员变量的地址;
- type:该成员是被包含在哪个结构体,也就是最终想要获取起始地址的结构体类型;
- member:该成员在结构体中的名称,也就是ptr 指向的成员名称,与 ptr 是对应的;
该宏函数共做了三件事情:
- 将第一个参数,即成员指针强转成 void*;
- 调用 BUILD_BUG_ON_MSG() 进行编译assert,要求 ptr 确实是参数 member 对应的结构体成员,且 ptr 已经转换成 void*;
- 调用 offsetof() 计算成员member 在结构体中的偏移量,进而计算出结构体的起始地址,并进行返回;
1.1 BUILD_BUG_ON_MSG()
include/linux/build_bug.h
/**
* BUILD_BUG_ON_MSG - break compile if a condition is true & emit supplied
* error message.
* @condition: the condition which the compiler should know is false.
*
* See BUILD_BUG_ON for description.
*/
#define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)
当condition 为true 时,会退出编译并报错。
扩展后,可以看到 BUILD_BUG_ON_MSG() 为:
#define BUILD_BUG_ON_MSG(cond, msg) \
do { \
extern void __compiletime_assert_2(void)__attribute__((__error__(msg))); \
if (!(condition)) \
__compiletime_assert_2(); \
} while (0)
其中 __compiletime_assert_N() 是gcc 定义断言函数,N 是通过 __COUNTER__ 计数得来的。
另外,想要调用这个 gcc 断言函数,需要定义 __OPTIMIZE__,即 gcc 编译的时候需要加上优化选项 -O<n>,n 是大于0的。
1.2 offsetof()
include/linux/stddef.h
#ifdef __compiler_offsetof
#define offsetof(TYPE, MEMBER) __compiler_offsetof(TYPE, MEMBER)
#else
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
#endif
其中 TYPE 是结构体的类型,MEMBER 是成员名称。
当定义 __compiler_offsetof 时,调用 __compiler_offsetof() 宏函数,其实就是调用 gcc 的内置函数 __builtin_offsetof()。
当没有定义 __compiler_offsetof 时,通过 &((TYPE *)0)->MEMBER 获取成员变量的地址,0 是起始地址,那得到的成员变量的地址就是偏移量。
1.3 返回值
当通过 offsetof() 获取到偏移量之后,使用 ptr - offset 就是结构体的起始地址了。
之所以利用这种方式,是因为结构体的内存,在内存空间中是连续的。
结构体起始地址(也是第一个成员的首地址) 与成员N 相差 offset,container_of() 就是利用成员的首地址与offset,进而求出该结构体的起始地址。
2. 实例
drivers/dma-buf/heaps/system_heap.c
static void system_heap_buf_free(struct deferred_freelist_item *item,
enum df_reason reason)
{
struct system_heap_buffer *buffer;
struct sg_table *table;
struct scatterlist *sg;
int i, j;
buffer = container_of(item, struct system_heap_buffer, deferred_free);
...
}
Linux 内核中使用 container_of() 的地方很多,这里用 dma-buf 中的一个函数为例。
通过上面源码分析,很容易理解此处:
通过结构体 system_heap_buffer 的成员变量 deffered_free 的地址 item,求得该结构体的起始地址。
结构体的源码如下:
drivers/dma-buf/heaps/system_heap.c
struct system_heap_buffer {
struct dma_heap *heap;
struct list_head attachments;
struct mutex lock;
unsigned long len;
struct sg_table sg_table;
int vmap_cnt;
void *vaddr;
struct deferred_freelist_item deferred_free;
bool uncached;
};