LinuxKernel-list.h 源码不完全分析
有一段时间没认真写博客了,没能一直坚持着,实在让 Y7n05h 感到惭愧,所以今天写出本文也算是补救一下吧.
info
License
本文引用了部分来自 Linux Kernel
的源码,源码取自 LinuxKernel v2.6.34 基于 GPLv2.
list.h 源码分析
1 |
/* |
这里是链表的核心结构,实现双向循环链表的初始化.
1 |
static inline void __list_add(struct list_head *new, |
1 |
/** |
关于插入也没什么需要过度解释的,唯一想说说的是 inline 的使用消除了函数调用的开销,当然代价是内核大小的增大,但我想这点代价是值得的.
当然,这里对 __list_add()
的复用和对两种不同的插入方式的抽象是十分精彩的.
1 |
/* |
关于这里,也没什么能产生太大疑惑的地方,唯一要好奇的可能是为什么要把被删除的链表节点的指针置为 LIST_POISON1
或 LIST_POISON2
?
在用户态编程的时候,开发者们常把无效的指针置为 NULL
防止出现 Use After Free(UAF)
等问题的出现,一旦访问置为 NULL
的指针就能通过 Segment fault
得知发生了错误.但别忘了,Segment fault
的检查是由内核完成的,在内核态编程时,自然是无法使用的.因此这里使用这两个特殊的地址触发分页保护告知开发者出现内存错误.
1 |
/* |
剩下的部分虽然也有很多内容,但都比较简单,相对来说也是易于理解的,Y7n05h 在这里就不赘述了.
这个宏函数还是很有趣的,能看到里面有很多 GNU
对 C 语言的扩展语法.直接从定义中看明白这个宏的用法是略有困难的,参考这个宏的用例将有助于理解.
1 |
/** |
1 |
/** |
用例:
1 |
static inline struct nfs_page * |
1 |
struct nfs_page { |
可以清晰的看到在 struct nfs_page
中,链表结点是 struct list_head wb_list
.
而 head
是指向 container_of
到底做了什么呢?那就是根据结构体中的成员的地址,计算出结构体的地址.首先,抛开代码考虑这件事情,在给定结构体在确定体系结构上使用确定对齐方式,那么结构体成员相对结构体的偏移量就是一个编译期能确定的常量.那么若有了结构体成员的地址,那么减去相应的偏移量即可得到结构体的地址.这一切在理论上都是可行的,剩下的事只是如何用代码实现.
其次,分析 container_of
的代码实现:
1 |
const typeof( ((type *)0)->member ) *__mptr = (ptr); |
这里使用 typeof
进行类型推断,实现范型编程,声明获得与 member
相同的类型,并添加 *
获得 member
的指针类型.通过这一行,获得了指向结构体成员的指针.同时利用 (type *)( (char *)__mptr - offsetof(type,member) )
根据 offsetof
关键字获得 member
在 type
中的偏移量,并使用指针运行将其从结构体成员的地址中减去.
最后则是使用 GNU
扩展的语句表达式语法,避免了需要将宏函数用 do{...}while(0)
包裹的麻烦事.
这些内容足够简单,但宏的运用与衔接十分精妙.
最后在谈谈
1 |
/** |
1 |
这里的遍历没什么好提及的,唯一想说说的地方只是 prefetch
.prefetch
也就是 __builtin_prefetch
看名字不难发现这是 GCC
的内置函数.查一下就能得知这是用来预读数据减少延迟的函数.大概就是防止后面用这个数据的时候出现缓存不命中吧.
好了,本文到此也就结束了.list.h
的别的部分 Y7n05h
认为也没有什么难以理解的内容了.
参考资料
1. LinuxKernel. ↩
LinuxKernel-list.h 源码不完全分析