源码基于: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.cstatic 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.cstruct 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;
};