Linux内核的container_of宏
简介
container_of宏是内核的常用宏, 通常用于通过结构体成员指针来获取结构体指针. 该宏依赖的一个基本原则是结构体在虚拟内存中是连续存储的. 还有铭记就是它是一个编译期就已经计算好的宏.
为什么要有container_of
根据内核考古学, container_of首次出现在Linux 2.5.28版本, 是由Neil Brown提交, 根据提交记录看container_of宏是为了替换list_entry宏, 其中加入了一些类型安全检查.
使用
1.实践中container_of扮演起了联系不同结构体的角色, 如下图所示 ib_qp_init_attr类型变量引用了结构体bnxt_re_cq中的一个成员ib_cq, 通过container_of我们可以间接的通过指针send_cq去找到包含了它的结构体变量指针.
2.当做获取成员所在结构体变量的工具, 这种情况就仅仅是为了获取成员所在的结构体指针
原理
container_of脱胎于list_entry, 其中 &((type *)0)->member 的作用是获取成员的实际偏移, 类似于用尺子量书本长度将书本和尺子靠墙对齐的道理[1].
之后在用 ptr 减去成员对齐后地址就是父结构体的地址. (unsigned long)用于将地址转换为指针类型, (char *)将 ptr 强转为 char * 类型是为了让指针以字节为单位进行运算
最后一步 (type *) 就是将指针转换为我们需要的类型.
以下是container_of的始祖, 第一版的container_of宏, 最外层的 ({}) 表示类似逗号表达式, 该表达式的值等于最后一个分号的语句所计算的值[1].
const typeof( ((type *)0)->member ) *__mptr = (ptr); 这一行初看感觉有些费解, 为什么要进行一次赋值操作? 答案是为了进行对指针 ptr 的类型检查, 设想下如果我们传入了一个和结构体成员类型并不匹配的指针, 代码是无法通过编译检查的.
offsetof()宏的作用等同于上面的获取成员实际偏移的功能.
下面是4.19.190版本的container_of宏, 跟始祖版本不同的在于:
1.类型检查通过BUILD_BUG_ON_MSG完成, 其中检查了ptr指向的类型是否不等于member && 不等于void类型, 两者都满足就直接报编译错误
2.直接使用 void * 类型进行指针运算, 在GNU C允许 void * 类型的指针运算, 并且运算单位是1.
结论
以上就是我对container_of宏的全部理解, GNU C和Linux内核在编译期的黑魔法还很多, 例如likely()\unlikely(), ARRAY_SIZE, __attribute__((packed))等. 让我们以后接着说, 嘻嘻嘻.