记录一下学习 linux 源码过程中,遇到的一些神奇 coding 技巧
linux 版本为 v6.18
container_of
在 include/linux/types.h 里面定义了这样一个奇怪的东西:
1 | struct list_head { |
容易看出来这是定义了一个双向链表的节点,但是奇怪的点在于每一个节点除了前驱和后继之外没有存任何的信息,那这个双向链表有何作用呢?
查资料发现,内核中有一个叫做 container_of 的宏,这个宏的作用是从结构体的成员指针去访问对应的结构体,也即对于这样一个结构体:
1 | struct Foo { |
那利用 container_of, 就可以在知道成员变量 x 的地址 struct list_head * ptr 的情况下,得到 struct Foo 的地址,也就是实现了一个类型为 Foo 的双向链表!
由于 linux 内核是 C 而非 C++ 实现的,并没有原生的模板语法,因此这种设计的一种优势就是实现了泛型容器
具体来说,container_of 是利用了编译器提供的 __builtin_offsetof 函数,这个函数可以在编译器得知一个结构体成员相对于该结构体首地址的偏移量,于是就可以用成员的地址减去偏移量得到结构体的地址了
1 |
其中:
ptr是成员指针(例子中的ptr)type是容器结构体(例子中的struct Foo)member是容器结构体中,对应成员的名字(例子中的x)
这里引出了一个新问题,也即当 ptr 是 const type * 的时候,转换后 const 属性会丢失,这会导致潜在的 bug,所以在 C11 引入 _Generic 语法之后就开始使用 container_of_const 了
vm_flags 对应的 union
在 struct vm_area_struct 中有这样一个匿名 union:
1 | union { |
这两个变量的类型一致,并且使用的是同一片内存,这说明它们的含义其实是相同的;唯一不同的点就在于二者的权限,一个是 const 变量,一个有特殊的宏标记 __private,这个宏的定义是:
1 |
这里使用了 gcc/clang 提供的 __attribute__ 语法,而 noderef 的含义是指对应变量无法被解引用,在内核中,这种指针通常用来保护特殊地址(用户空间地址、MMIO 等非内核地址空间),这种地址的访问需要特殊处理,而不能直接解引用访问,如果有变量被添加了这个属性的话,那 Sparse 静态分析器就会在编译阶段检查语义,避免直接的解引用
但此处 __vm_flags 不是指针类型,vm_flags_t 是 typedef unsigned long,所以这个属性似乎并没有什么实际作用。我在自己的 Ubuntu 22.04 里面进行了测试,gcc 版本为 11.4.0,测试代码是:
1 |
|
这段代码编译的时候会有警告 warning: ‘noderef’ attribute directive ignored,并且可以正常运行退出,同时官网里面也没说 noderef 可以私有化变量,所以目前来看似乎这个 __private 的意义就是让 Sparse 知道这是结构体中的“私有变量”,从而禁止掉 vma_ptr->__vm_flags 这种行为
同时内核中还提供了 ACCESS_PRIVATE 宏,用于在特定情况下访问 __private 变量:
1 |
就是利用了 force 这个属性去强行访问