前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >13张图让你更进一步理解内核进程列表

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

作者头像
AshinZ
发布2023-11-01 17:02:48
2190
发布2023-11-01 17:02:48
举报

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

前文我们说到进程结构体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,相对而言会简短一些,理解实现原理比较重要:

小结

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-04-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程栩的性能优化笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • list_head与task_struct
  • 指针
  • 内核实现
  • 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档