记录一下学习 linux 源码过程中,遇到的一些神奇 coding 技巧

linux 版本为 v6.18

container_of

include/linux/types.h 里面定义了这样一个奇怪的东西:

1
2
3
struct list_head {
struct list_head *next, *prev;
};

容易看出来这是定义了一个双向链表的节点,但是奇怪的点在于每一个节点除了前驱和后继之外没有存任何的信息,那这个双向链表有何作用呢?

查资料发现,内核中有一个叫做 container_of 的宏,这个宏的作用是从结构体的成员指针去访问对应的结构体,也即对于这样一个结构体:

1
2
3
4
5
struct Foo {
...
struct list_head x;
...
}

那利用 container_of, 就可以在知道成员变量 x 的地址 struct list_head * ptr 的情况下,得到 struct Foo 的地址,也就是实现了一个类型为 Foo 的双向链表!

由于 linux 内核是 C 而非 C++ 实现的,并没有原生的模板语法,因此这种设计的一种优势就是实现了泛型容器

具体来说,container_of 是利用了编译器提供的 __builtin_offsetof 函数,这个函数可以在编译器得知一个结构体成员相对于该结构体首地址的偏移量,于是就可以用成员的地址减去偏移量得到结构体的地址了

内核实现
1
2
3
4
5
6
7
8
9
10
11
12
#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))); })

#define container_of_const(ptr, type, member) \
_Generic(ptr, \
const typeof(*(ptr)) *: ((const type *)container_of(ptr, type, member)),\
default: ((type *)container_of(ptr, type, member)) \
)

其中:

  • ptr 是成员指针(例子中的 ptr
  • type 是容器结构体(例子中的 struct Foo
  • member 是容器结构体中,对应成员的名字(例子中的 x

这里引出了一个新问题,也即当 ptrconst type * 的时候,转换后 const 属性会丢失,这会导致潜在的 bug,所以在 C11 引入 _Generic 语法之后就开始使用 container_of_const


© 2024 本网站由 Ywang22 使用 Stellar主题 创建
总访问 次 | 本页访问
共发表 81 篇 Blog(s) · 总计 188k 字