13张图让你更进一步理解内核进程列表

2023-11-01 17:02:48 浏览数 (1)

大家好,我是程栩,一个专注于性能的大厂程序员,分享包括但不限于计算机体系结构、性能优化、云原生的知识。

前文我们说到进程结构体task_struct中有兄弟进程、子进程链表的指针,但是该指针类型是list_head,并不是我们平常见到的那种以一个同样的数据结构,然后用一个next指针记录后续指针的链表。那么,内核是如何基于这个指针寻找相关的数据结构的呢?

今天主要基于《Linux内核设计与实现》的6.1章及内核6.3源码编写。

list_head与task_struct

首先我们来看list_head类型,

代码语言:javascript复制
// include/linux/types.h L178
struct list_head {
    struct list_head *next, *prev;
};

可以看出这是一个双向链表,既可以往前寻找也可以向后寻找,这是一个环形双向链表:

list_head

list_headtask_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_headtask_struct的访问过程:

代码语言:javascript复制
// 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函数,这是一个宏定义:

代码语言:javascript复制
#define container_of(ptr, type, member)

简单来说,就是针对ptr指针,返回一个type类型的指针。在我们的例子中,就是传入list_head类型指针,list_head被嵌入到了task_struct指针中,也即ptrlist_headtypetask_struct,而member就是list_headtask_struct中的成员名,例如在这里就是children

首先计算出结构体成员membertask_struct的偏移,也即:

代码语言:javascript复制
 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_headtask_struct,相对而言会简短一些,理解实现原理比较重要:

小结

0 人点赞