大家好,我是程栩,一个专注于性能的大厂程序员,分享包括但不限于计算机体系结构、性能优化、云原生的知识。
引
前文我们说到进程结构体task_struct
中有兄弟进程、子进程链表的指针,但是该指针类型是list_head
,并不是我们平常见到的那种以一个同样的数据结构,然后用一个next
指针记录后续指针的链表。那么,内核是如何基于这个指针寻找相关的数据结构的呢?
今天主要基于《Linux内核设计与实现》的6.1章及内核6.3源码编写。
list_head与task_struct
首先我们来看list_head
类型,
// include/linux/types.h L178
struct list_head {
struct list_head *next, *prev;
};
可以看出这是一个双向链表,既可以往前寻找也可以向后寻找,这是一个环形双向链表:
list_head
list_head
与task_struct
关系如下图所示:
task_struct与list_head
我们将这两者结合:
task_struct与list_struct
可以看到,我们可以很容易在获取到task_struct
指针后就通过children
指针访问到list_head
结构体:
从thread_info到head_list
那么,现在的问题就演变成,我们如何在访问到list_head
后回过头来访问task_struct
了:
如何从head_list访问到task_struct?
指针
当我们访问一个结构体指针的时候,其实我们只是访问的这个结构体的起始地址:
指针寻址
而如果我们有这个结构体的声明的话,那么每一个结构体成员对应的偏移在编译的时候就已经固定了:
偏移
当我们去访问结构体中成员的时候,我们通过偏移进行寻找:
结构体指针访问成员
那么反过来说,如果我们有20
偏移的位置对应的地址的话,那么,我们就可以反过来寻找:
反向访问
这样的话,我们就可以通过list_head
反向访问到task_struct
,从而可以遍历全部的task_struct
了:
list_head与task_struct
访问过程如下所示:
遍历task_struct
内核实现
在内核中,内核通过宏定义来实现从list_head
到task_struct
的访问过程:
// include/linux/container_of.h L10
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
* WARNING: any const qualifier of @ptr is lost.
*/
#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))); })
/**
* container_of_const - cast a member of a structure out to the containing
* structure and preserve the const-ness of the pointer
* @ptr: the pointer to the member
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*/
#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))
)
#endif /* _LINUX_CONTAINER_OF_H */
我们首先来看container_of
函数,这是一个宏定义:
#define container_of(ptr, type, member)
简单来说,就是针对ptr
指针,返回一个type
类型的指针。在我们的例子中,就是传入list_head
类型指针,list_head
被嵌入到了task_struct
指针中,也即ptr
是list_head
,type
是task_struct
,而member
就是list_head
在task_struct
中的成员名,例如在这里就是children
。
首先计算出结构体成员member
在task_struct
的偏移,也即:
offsetof(type, member)
基于结构体定义计算偏移
接着,将当前的指针地址减去偏移量:
代码语言:javascript复制__mptr - offsetof(type, member)
当前指针减去偏移
这样我们就找到了这个结构体的起始位置,但是我们要注意返回一个合适的类型,也即:
代码语言:javascript复制((type *)(__mptr - offsetof(type, member)))
这样,我们就从list_head
找到了task_struct
。
小结
相比于将数据结构放到链表中的常见实现,内核采用了将链表节点放到数据结构中的方式实现。这样的话,当我们需要一个新的链表的时候,我们只需要将list_head
嵌入到我们声明的结构体中即可,并且链表的相关操作,都可以复用当前的这一套,这些链表操作在include/linux/list.h
中。
今天的内容更多的在于介绍list_head
到task_struct
,相对而言会简短一些,理解实现原理比较重要:
小结