Linux 内核的
读 Linux 内核源码的时候,你会在每一个角落碰到一个宏——
其实原理特别朴素。C 结构体在内存里是连续布局的,每个成员的偏移量在编译期就确定了。所以如果你知道某个成员的地址,再减去它相对于结构体起始位置的偏移,就回到了结构体的头部。
这个设计反过来想更有意思:不是"结构体包含链表节点",而是"链表节点嵌入结构体"。链表的实现完全不知道外面挂的是什么,它只管自己那几个
宏的定义本身也有值得看的细节。第一步算偏移用的是
下次读内核代码看到
#CS
container_of 宏:从指针倒推回结构体读 Linux 内核源码的时候,你会在每一个角落碰到一个宏——
container_of。它做的事情乍看像黑魔法:给你一个成员变量的指针,你就能拿回整个结构体的指针。听起来像是 C 语言在偷偷搞反射?其实原理特别朴素。C 结构体在内存里是连续布局的,每个成员的偏移量在编译期就确定了。所以如果你知道某个成员的地址,再减去它相对于结构体起始位置的偏移,就回到了结构体的头部。
container_of 就是把这个减法封装成了一句宏。内核里到处是链表嵌入在各自的结构体中——list_head 自己不承载数据,它被嵌进 task_struct、inode、sk_buff 等各种结构体里。遍历链表时你拿到的全是 list_head *,要从它找回外层的 task_struct,只能靠 container_of。这个设计反过来想更有意思:不是"结构体包含链表节点",而是"链表节点嵌入结构体"。链表的实现完全不知道外面挂的是什么,它只管自己那几个
next 和 prev 指针。泛型不是靠模板、不是靠虚函数,而是靠把数据结构的所有权翻转过来实现的。这种内嵌式链表在内核里无处不在,从进程调度到内存管理到文件系统,全靠这一套。宏的定义本身也有值得看的细节。第一步算偏移用的是
((size_t)&((type *)0)->member)——把零地址强转成结构体指针再取成员地址,得到的值恰好就是偏移量,因为这个"结构体"从地址零开始。offsetofoffsetofoffsetof。下次读内核代码看到
container_of(ptr, struct task_struct, run_list) 这种写法,就知道那不过是一次指针减法,背后藏着的是内核对数据结构关系的根本态度:谁包含谁,决定了代码怎么组织。#CS