cgroup层次结构
cgroup形成的最重要的结构就是层次结构,也就是树形结构。其实你想一下,这是一个非常自然的结果。因为cgroup管理的对象就是进程树,那么自然自己也展现成一颗树的样子。
想到了这一层,那cgroup的树形结构就比较好理解,且理论上没有什么好讲的。但是有意思的是,cgroup的树形结构并不是以cgroup本身链接起来的,而是由subsystem的状态链接起来的。据说是为了更快捷的访问需要控制的状态数据。所以在介绍cgroup的树形结构中,我们要讲到
cgroup
subsystem
subsystem_state
subsystem的全局定义
在正式开始介绍cgroup前,我们先来看看几个全局变量的定义。因为cgroup所支持的subsystem不能像驱动那样随意添加,所以内核干脆把支持的subsystem定义成了全局变量。通过对这些全局变量的了解,我们也能知道当前内核究竟支持了多少subsystem。
内核中一共定义了三组全局变量:
cgroup_subsys_id
cgroup_subsys_name[]
cgroup_subsys[]
其实很简单,但是内核开发者用了一些很有意思的手法来定义,所以当你第一次找的时候不一定能找到。那就让我来为你慢慢揭开这个面纱。
首先这三个全局变量的定义形式都很类似:
#define SUBSYS(_x) _x ## _cgrp_id,
enum cgroup_subsys_id {
#include <linux/cgroup_subsys.h>
CGROUP_SUBSYS_COUNT,
};
#define SUBSYS(_x) [_x ## _cgrp_id] = #_x,
static const char *cgroup_subsys_name[] = {
#include <linux/cgroup_subsys.h>
};
#define SUBSYS(_x) [_x ## _cgrp_id] = &_x ## _cgrp_subsys,
struct cgroup_subsys *cgroup_subsys[] = {
#include <linux/cgroup_subsys.h>
};也就是都先定义了一个宏 SUBSYS,然后引用了头文件 linux/cgroup_subsys.h。其实头文件的内容很简单,就是一个个SUBSYS加上当前内核支持的subsystem的名字。
讲到这里可能还有点模糊,那我就干脆手工把这些定义展开,来看个明明白白。
cgroup_subsys_id
cgroup_subsys_name[]
cgroup_subsys[]
ok,这样当内核中出现这些变量的时候,你就知道都对应到是谁,要去哪里找他们了吧。
cgroup初始化概览
终于要揭开cgroup的面纱了,我们先来看cgroup是在什么地方初始化的,以及初始化大致都做了些什么工作。
从上面摘出的代码来看,初始化cgroup主要做了这么几件事:
注册cgroup文件系统,及初始化对应的cft
初始化cgroup_root
初始化每个配置了的subsystem
因为cgroup文件系统相关内容我们已经在上一节中详细阐述,所以接下来我们就看看cgroup_root和subsystem的初始化。
cgroup_root的初始化
和cgroup_root初始化相关的有两个地方
init_cgroup_root()
cgroup_setup_root()
在上一节中我们看到过cgroup_root结构体,但我们着重看了和cgroup文件系统相关的部分。现在我们来看一下这个结构体的全貌。
从结构体的定义上可以看出,cgroup_root主要包含了:
一个根cgroup
一个kernfs文件系统的根结构
而cgroup_root的初始化主要是准备好这两个结构体,也就是设置好了cgroup文件系统目录结构并关联到了某个cgroup树形结构的根上。
subsystem的初始化
先来看一下subsystem初始化都做了哪些事儿。
嗯,看上去简直就是天书,完全不知道是在干啥。ok,让我来拆解一下。这段代码中涉及到四个数据结构:
cgroup_root
cgroup
cgroup_subsys
cgroup_subsys_state
而这个函数要做的事儿,就是把这四个结构体关联起来。那就让我们慢慢讲来:
cgroup_subsys的模样
从这个图上看,subsys只和cgroup_root之前存在联系,而且只和一个cgroup_root有关。这也解释了系统中对某一个subsys的系统划分只能有一种。
另外我们也看到了,某个subsys自带的配置文件是关联在subsys上的。由函数cgroup_add_legacy_cftypes/cgroup_add_dfl_cftypes在cgroup_init()时添加到了ss->cfts链表。
cgroup和cgroup_subsys_state
cgroup上有一个长度为CGROUP_SUBSYS_COUNT的数组, subsys[]。其中每一个成员都指向了一个cgroup_subsys_state结构。
从这个结构上可以看出:
每个cgroup可以关联所有的subsys
如果对应的成员为NULL,表示该cgroup没有和对应的subsys绑定
也可以看到每个subsys_state都有指向自己是属于哪个subsys的指针
cgroup_subsys的层次结构
实际上这个工作主要是在css_create()函数中做到的,但是因为这个确实比较重要,我就把他放在了这里。
怎么样,现在是不是有点感觉了?我们已经在subsys_state的角度形成了一个层次结构。这是不是已经很像我们用mkdir创建cgroup时形成的层次结构?
隐藏的cgroup_subsys_state
最后要放一张神奇的图:
这张图是不是和上面那张很像?几乎一模一样?
是的,唯一的区别在于这次链接起来的是cgroup->self,而不是cgroup->subsys[]。
这个问题我也没有想明白,为啥还要单独用一个隐藏的subsys_state来链接?为啥不直接用cgroup来链接?我能想到的解释是遗留问题。可能最开始没有想到要加这么多subsys。
好了,cgroup的层次结构就这么突然的呈现在你的面前,没有什么明显的征兆。就好像生活中某些事儿突如其来又戛然而止。
相关函数
对整个结构有了全貌后,我们对一些常用的函数做一些了解。这样可以加深对整个结构的了解,同时也可以验证我们之前的认识是否正确。
我们将看一些比较常用的函数,如:
遍历层次结构
创建cgroup
创建css
遍历层次结构
既然我们构建了一个cgroup的层次结构,那么必然需要遍历这个结构。内核中提供了三个帮助我们遍历的函数:
cgroup_for_each_live_child()
cgroup_for_each_live_descendant_pre()
cgroup_for_each_live_descendant_post()
这三个都是宏定义,且不长。我们就都打开看一下。
可以看到,第一个遍历其实就是遍历了cgrp->self.children这个链表。也就这一个层次,遍历是比较简单的。
而后两者是遍历整个子树了,所以一眼看不清楚具体做了啥。但是都调用了一个非常像的函数css_for_each_descendant_[pre|post]。
让我们来看看其中一个的实现。
其中css_next_child()只是寻找parent下pos后面一个孩子,如果都找到了,则网上一层。这样配合起来就完成了子树的遍历。
创建cgroup
接下来一个重要的函数是创建cgroup的了。
这个函数里,创建了一个空的cgroup结构,然后把这个结构体收拾干净。备用。
那给谁用呢?还记得之前看过的cgroup_mkdir么?对了,就是给他用。
扫了一眼,大部分我们已经心中有数了。无非是创建cft文件,并显示。其中只有一个函数我们还不清楚。cgroup_apply_control_enable。
遍历cgroup子树的函数cgroup_for_each_live_descendant_pre()我们刚看过,因为这个cgroup是刚创建的,所以这里就遍历了一个节点。而整个函数的目标还是创建对应的cft文件并显示。只不过这里还隐含了一个动作css_create()。是的,之前我们看到的cgroup_subsystem_state就是在这里创建的。
创建css
每个cgroup上enable的每个subsys都有一个对应的subsys_state。这个结构由css_create()创建。
总的来说,创建一个css做了这么几件事:
把自己链接到父节点的孩子中
把自己赋值到cgroup的subsys[]中
好了,到这里,基本上就把cgroup层次结构所涉及到的内容都讲完了。
Last updated
Was this helpful?