# 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

```
enum cgroup_subsys_id {
	cpuset_cgrp_id,
	cpu_cgrp_id,
	cpuacct_cgrp_id,
	io_cgrp_id,
	memory_cgrp_id,
	devices_cgrp_id,
	freezer_cgrp_id,
	net_cls_cgrp_id,
	perf_event_cgrp_id,
	net_prio_cgrp_id,
	hugetlb_cgrp_id,
	pids_cgrp_id,
	rdma_cgrp_id,
	debug_cgrp_id,
	CGROUP_SUBSYS_COUNT,
};
```

### cgroup\_subsys\_name\[]

```
static const char *cgroup_subsys_name[] = {
	[cpuset_cgrp_id]       =      "cpuset",
	[cpu_cgrp_id]          =      "cpu",
	[cpuacct_cgrp_id]      =      "cpuacct",
	[io_cgrp_id]           =      "io",
	[memory_cgrp_id]       =      "memory",
	[devices_cgrp_id]      =      "devices",
	[freezer_cgrp_id]      =      "freezer",
	[net_cls_cgrp_id]      =      "net_cls",
	[perf_event_cgrp_id]   =      "perf_event",
	[net_prio_cgrp_id]     =      "net_prio",
	[hugetlb_cgrp_id]      =      "hugetlb",
	[pids_cgrp_id]         =      "pids",
	[rdma_cgrp_id]         =      "rdma",
	[debug_cgrp_id]        =      "debug",
};
```

### cgroup\_subsys\[]

```
struct cgroup_subsys *cgroup_subsys[] = {

	[cpuset_cgrp_id]       =      cpuset_cgrp_subsys,
	[cpu_cgrp_id]          =      cpu_cgrp_subsys,
	[cpuacct_cgrp_id]      =      cpuacct_cgrp_subsys,
	[io_cgrp_id]           =      io_cgrp_subsys,
	[memory_cgrp_id]       =      memory_cgrp_subsys,
	[devices_cgrp_id]      =      devices_cgrp_subsys,
	[freezer_cgrp_id]      =      freezer_cgrp_subsys,
	[net_cls_cgrp_id]      =      net_cls_cgrp_subsys,
	[perf_event_cgrp_id]   =      perf_event_cgrp_subsys,
	[net_prio_cgrp_id]     =      net_prio_cgrp_subsys,
	[hugetlb_cgrp_id]      =      hugetlb_cgrp_subsys,
	[pids_cgrp_id]         =      pids_cgrp_subsys,
	[rdma_cgrp_id]         =      rdma_cgrp_subsys,
	[debug_cgrp_id]        =      debug_cgrp_subsys,

};
```

ok,这样当内核中出现这些变量的时候，你就知道都对应到是谁，要去哪里找他们了吧。

## cgroup初始化概览

终于要揭开cgroup的面纱了，我们先来看cgroup是在什么地方初始化的，以及初始化大致都做了些什么工作。

```
start_kernel()
    cgroup_init_early()
        init_cgroup_root(&ctx)
            init_cgroup_housekeeping(cgrp)
        for_each_subsys(ss, i) {
            if (ss->early_init)
                cgroup_init_subsys(ss)
        }
    cgroup_init()
        cgroup_init_cftypes(NULL, cgroup_base_files)
        cgroup_init_cftypes(NULL, cgroup1_base_files)
        cgroup_setup_root(&cgrp_dfl_root, 0)
        for_each_subsys(ss, ssid) {
            if (!ss->early_init)
                cgroup_init_subsys(ss)
            cgroup_add_dfl_cftypes(ss, ss->dfl_cftypes)
            cgroup_add_legacy_cftypes(ss, ss->legacy_cftypes)
            if (ss->bind)
                ss->bind(init_css_set.subsys[ssid])
            css_populate_dir(init_css_set.subsys[ssid])
        }
        sysfs_create_mount_point(fs_kobj, "cgroup")
        register_filesystem(&cgroup_fs_type)
        register_filesystem(&cgroup2_fs_type)
```

从上面摘出的代码来看，初始化cgroup主要做了这么几件事：

* 注册cgroup文件系统，及初始化对应的cft
* 初始化cgroup\_root
* 初始化每个配置了的subsystem

因为cgroup文件系统相关内容我们已经在上一节中详细阐述，所以接下来我们就看看cgroup\_root和subsystem的初始化。

## cgroup\_root的初始化

和cgroup\_root初始化相关的有两个地方

* init\_cgroup\_root()
* cgroup\_setup\_root()

在上一节中我们看到过cgroup\_root结构体，但我们着重看了和cgroup文件系统相关的部分。现在我们来看一下这个结构体的全貌。

```
cgroup_roots(struct list_head)
  |
  |       cgroup_root(cgrp_dfl_root)
  |       +-------------------------------+<----+
  +------>|root_list                      |     |
          |    (struct list_head)         |     |
          |name[]                         |     |
          |    (char)                     |     |
          |hierarchy_id                   |     |
          |    (int)                      |     |
          |                               |     |
          |cgrp                           |     |
          |    (struct cgroup)            |     |
          |    +--------------------------+<----|--------------------------+
          |    |root                   ---|-----+                          |
          |    |   (struct cgroup_root*)  |                                |
          |    |                          |                                |
          |    |kn                        |  = kf_root->kn                 |
          |    |    (struct kernfs_node *)|-----+                          |
          |    +--------------------------+     |                          |
          |kf_root                        |     |                          |
          |    (struct kernfs_root*)      |     |                          |
          |    +--------------------------+     |                          |
          |    |kn                        |     |                          |
          |    |  (struct kernfs_node*)   |     |                          |
          |    |  +-----------------------+<----+                          |
          |    |  |priv                   |--------------------------------+
          |    |  |  (void *)             |  = cgrp_dfl_root.cgrp
          |    +--+-----------------------+
          |    |syscall_ops               |  = cgroup_kf_syscall_ops | cgroup1_kf_syscall_ops
          |    |  (struct kernfs_syscall*)|
          |    |  +-----------------------+
          |    |  |mkdir                  |  = cgroup_mkdir
          |    |  |rmdir                  |  = cgroup_rmdir
          |    |  |show_path              |  = cgroup_show_path
          |    |  |show_options           |  = cgroup_show_options
          |    |  +-----------------------+
          |    +--------------------------+
          |                               |
          |subsys_mask                    |  bitmask of subsys attached
          |    (unsigned int)             |  set in cgroup_init() for cgrp_dfl_root
          |                               |      in rebind_subsystems() for others
          |nr_cgrps                       |
          |    (atomic_t)                 |
          +-------------------------------+
```

从结构体的定义上可以看出，cgroup\_root主要包含了：

* 一个根cgroup
* 一个kernfs文件系统的根结构

而cgroup\_root的初始化主要是准备好这两个结构体，也就是设置好了cgroup文件系统目录结构并关联到了某个cgroup树形结构的根上。

## subsystem的初始化

先来看一下subsystem初始化都做了哪些事儿。

```
cgroup_init_subsys(ss, early)
    ss->root = &cgrp_dfl_root
    css = ss->css_alloc()
    init_and_link_css(css, ss, &cgrp_dfl_root.cgrp)
        css->cgroup = cgrp
        css->ss = ss
    init_css_set.subsys[ss->id] = css
    online_css(css)
        ss->css_online(css)
        css->cgroup->subsys[ss->id] = css
```

嗯，看上去简直就是天书，完全不知道是在干啥。ok，让我来拆解一下。这段代码中涉及到四个数据结构：

* cgroup\_root
* cgroup
* cgroup\_subsys
* cgroup\_subsys\_state

而这个函数要做的事儿，就是把这四个结构体关联起来。那就让我们慢慢讲来：

### cgroup\_subsys的模样

```
cgroup_subsys
+-------------------------------+<----------------------------------------+
|name                           |                                         |
|legacy_name                    |                                         |
|    (char *)                   |                                         |
|id                             |                                         |
|    (int)                      |                                         |
|                               |                                         |
|root                           |  = &cgrp_dfl_root                       |
|    (struct cgroup_root*)      |    init in cgroup_init_subsys()         |
|                               |    rebind in rebind_subsystems()        |
|                               |                                         |
|dfl_cftypes                    |  be populated by css_populate_dir()     |
|legacy_cftypes                 |                                         |
|    (struct cftype*)           |                                         |
|                               |    dfl_cftypes       legacy_cftypes     |
|                               |    +------------+    +------------+     |
|cfts                        ---|--->|node     ---|--->|node        |     |
|    (struct list_head)         |    |ss          |    |ss       ---|-----+
|                               |    |kf_ops      |    |kf_ops      |
|                               |    +------------+    +------------+    
+-------------------------------+
```

从这个图上看，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
+--------------------------------------+<--------------------------+
|subsys[CGROUP_SUBSYS_COUNT]           |                           |
|    (struct cgroup_subsys_state*)     |                           |
|    [0]  +----------------------------+                           |
|         |cgroup                      |---------------------------+
|         |   (struct cgroup*)         |                           |
|         |ss                          |  = cgroup_subsys[0]       |
|         |   (struct cgroup_subsys*)  |                           |
|    [1]  +----------------------------+                           |
|         |cgroup                      |---------------------------+
|         |   (struct cgroup*)         |                           |
|         |ss                          |  = cgroup_subsys[1]       |
|         |   (struct cgroup_subsys*)  |                           |
|    [2]  +----------------------------+                           |
|         |                            |                           |
|         .                            .                           |
|         .                            .                           |
|         |                            |                           |
|    [15] +----------------------------+                           |
|         |cgroup                      |---------------------------+
|         |   (struct cgroup*)         |
|         |ss                          |  = cgroup_subsys[15]
|         |   (struct cgroup_subsys*)  |
|         |                            |
+---------+----------------------------+
```

cgroup上有一个长度为CGROUP\_SUBSYS\_COUNT的数组， subsys\[]。其中每一个成员都指向了一个cgroup\_subsys\_state结构。

从这个结构上可以看出：

* 每个cgroup可以关联所有的subsys
* 如果对应的成员为NULL，表示该cgroup没有和对应的subsys绑定
* 也可以看到每个subsys\_state都有指向自己是属于哪个subsys的指针

### cgroup\_subsys的层次结构

```
                     cgroup
                     +--------------------------------------+
                     |subsys[id]                        ^   |
                     |    (struct cgroup_subsys_state*) |   |
                     |    +---------------------------------+
                     |    |cgroup                    ---+   |
                     |    |    (struct cgroup*)             |
                     |    |ss                               |
                     |    |    (struct cgroup_subsys*)      |  = non-NULL
                     |    |                                 |
                     |    |parent                           |
                     |    |    (struct cgroup_subsys_state*)|
                     |    |                                 |
                     |    |children                ||       |
                     |    |    (struct list_head)  ||       |
                     +----+---------------------------------+ <--------------------------------------+
                                              ^    ||                                                |
       	                                      |    ||                                                |
                                              |    ||                                                |
                                              |    ||                                                |
                                              |    ||                                                |
    cgroup                                    |    ||      cgroup                                    |
    +--------------------------------------+  |    ||      +--------------------------------------+  |
    |subsys[id]                        ^   |  |    ||      |subsys[id]                        ^   |  |
    |    (struct cgroup_subsys_state*) |   |  |    ||      |    (struct cgroup_subsys_state*) |   |  |
    |    +---------------------------------+  |    ||      |    +---------------------------------+  |
    |    |cgroup                    ---+   |  |    ||      |    |cgroup                    ---+   |  |
    |    |    (struct cgroup*)             |  |    ||      |    |    (struct cgroup*)             |  |
    |    |                                 |  |    ||      |    |                                 |  |
    |    |parent                        ---|--+    ||      |    |parent                        ---|--+
    |    |    (struct cgroup_subsys_state*)|       ||      |    |    (struct cgroup_subsys_state*)|
    |    |                                 |       ||      |    |                                 |
    |    |sibling                      ====|=======++======|====|sibling                          |
    |    |    (struct list_head)           |               |    |    (struct list_head)           |
    +----+---------------------------------+               +----+---------------------------------+
```

实际上这个工作主要是在css\_create()函数中做到的，但是因为这个确实比较重要，我就把他放在了这里。

怎么样，现在是不是有点感觉了？我们已经在subsys\_state的角度形成了一个层次结构。这是不是已经很像我们用mkdir创建cgroup时形成的层次结构？

### 隐藏的cgroup\_subsys\_state

最后要放一张神奇的图：

```
                     cgroup
                     +--------------------------------------+
                     |self                              ^   |
                     |    (struct cgroup_subsys_state)  |   |
                     |    +---------------------------------+
                     |    |cgroup                    ---+   |
                     |    |    (struct cgroup*)             |
                     |    |ss                               |
                     |    |    (struct cgroup_subsys*)      |  = NULL
                     |    |parent                           |
                     |    |    (struct cgroup_subsys_state*)|
                     |    |                                 |
                     |    |children                ||       |
                     |    |    (struct list_head)  ||       |
                     +----+---------------------------------+ <--------------------------------------+
                                              ^    ||                                                |
       	                                      |    ||                                                |
                                              |    ||                                                |
                                              |    ||                                                |
                                              |    ||                                                |
    cgroup                                    |    ||      cgroup                                    |
    +--------------------------------------+  |    ||      +--------------------------------------+  |
    |self                              ^   |  |    ||      |self                              ^   |  |
    |    (struct cgroup_subsys_state)  |   |  |    ||      |    (struct cgroup_subsys_state)  |   |  |
    |    +---------------------------------+  |    ||      |    +---------------------------------+  |
    |    |cgroup                    ---+   |  |    ||      |    |cgroup                    ---+   |  |
    |    |    (struct cgroup*)             |  |    ||      |    |    (struct cgroup*)             |  |
    |    |                                 |  |    ||      |    |                                 |  |
    |    |parent                        ---|--+    ||      |    |parent                        ---|--+
    |    |    (struct cgroup_subsys_state*)|       ||      |    |    (struct cgroup_subsys_state*)|
    |    |                                 |       ||      |    |                                 |
    |    |sibling                      ====|=======++======|====|sibling                          |
    |    |    (struct list_head)           |               |    |    (struct list_head)           |
    +----+---------------------------------+               +----+---------------------------------+
```

这张图是不是和上面那张很像？几乎一模一样？

是的，唯一的区别在于这次链接起来的是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()

这三个都是宏定义，且不长。我们就都打开看一下。

```
/* iterate over child cgrps, lock should be held throughout iteration */
#define cgroup_for_each_live_child(child, cgrp)				\
	list_for_each_entry((child), &(cgrp)->self.children, self.sibling) \
		if (({ lockdep_assert_held(&cgroup_mutex);		\
		       cgroup_is_dead(child); }))			\
			;						\
		else

/* walk live descendants in pre order */
#define cgroup_for_each_live_descendant_pre(dsct, d_css, cgrp)		\
	css_for_each_descendant_pre((d_css), &(cgrp)->self)		\
		if (({ lockdep_assert_held(&cgroup_mutex);		\
		       (dsct) = (d_css)->cgroup;			\
		       cgroup_is_dead(dsct); }))			\
			;						\
		else

/* walk live descendants in postorder */
#define cgroup_for_each_live_descendant_post(dsct, d_css, cgrp)		\
	css_for_each_descendant_post((d_css), &(cgrp)->self)		\
		if (({ lockdep_assert_held(&cgroup_mutex);		\
		       (dsct) = (d_css)->cgroup;			\
		       cgroup_is_dead(dsct); }))			\
			;						\
		else
```

可以看到，第一个遍历其实就是遍历了cgrp->self.children这个链表。也就这一个层次，遍历是比较简单的。

而后两者是遍历整个子树了，所以一眼看不清楚具体做了啥。但是都调用了一个非常像的函数css\_for\_each\_descendant\_\[pre|post]。

让我们来看看其中一个的实现。

```
#define css_for_each_descendant_pre(pos, css)				\
	for ((pos) = css_next_descendant_pre(NULL, (css)); (pos);	\
	     (pos) = css_next_descendant_pre((pos), (css)))

struct cgroup_subsys_state *
css_next_descendant_pre(struct cgroup_subsys_state *pos,
			struct cgroup_subsys_state *root)
{
	struct cgroup_subsys_state *next;

	cgroup_assert_mutex_or_rcu_locked();

	/* if first iteration, visit @root */
	if (!pos)
		return root;

	/* visit the first child if exists */
	next = css_next_child(NULL, pos);
	if (next)
		return next;

	/* no child, visit my or the closest ancestor's next sibling */
	while (pos != root) {
		next = css_next_child(pos, pos->parent);
		if (next)
			return next;
		pos = pos->parent;
	}

	return NULL;
}
```

其中css\_next\_child()只是寻找parent下pos后面一个孩子，如果都找到了，则网上一层。这样配合起来就完成了子树的遍历。

### 创建cgroup

接下来一个重要的函数是创建cgroup的了。

```
cgroup_create(parent)
    cgrp = kzalloc()
    percpu_ref_init(&cgrp->self.refcnt, css_release, 0, GFP_KERNEL);
    cgroup_rstat_init(cgrp);
    kn = kernfs_create_dir(parent->kn, name, mode, cgrp)
    init_cgroup_housekeeping(cgrp)
    list_add_tail_rcu(&cgrp->self.sibling, &cgroup_parent(cgrp)->self.children);
    cgroup_propagate_control(cgrp)
```

这个函数里，创建了一个空的cgroup结构，然后把这个结构体收拾干净。备用。

那给谁用呢？还记得之前看过的cgroup\_mkdir么？对了，就是给他用。

```
cgroup_mkdir()
  cgrp = cgroup_create(parent, name, mode)
  ret = css_populate_dir(&cgrp->self)
  ret = cgroup_apply_control_enable(cgrp)
  kernfs_activate(cgrp->kn)
```

扫了一眼，大部分我们已经心中有数了。无非是创建cft文件，并显示。其中只有一个函数我们还不清楚。cgroup\_apply\_control\_enable。

```
static int cgroup_apply_control_enable(struct cgroup *cgrp)
{
	struct cgroup *dsct;
	struct cgroup_subsys_state *d_css;
	struct cgroup_subsys *ss;
	int ssid, ret;

	cgroup_for_each_live_descendant_pre(dsct, d_css, cgrp) {
		for_each_subsys(ss, ssid) {
			struct cgroup_subsys_state *css = cgroup_css(dsct, ss);

			if (!(cgroup_ss_mask(dsct) & (1 << ss->id)))
				continue;

			if (!css) {
				css = css_create(dsct, ss);
				if (IS_ERR(css))
					return PTR_ERR(css);
			}

			WARN_ON_ONCE(percpu_ref_is_dying(&css->refcnt));

			if (css_visible(css)) {
				ret = css_populate_dir(css);
				if (ret)
					return ret;
			}
		}
	}

	return 0;
}
```

遍历cgroup子树的函数cgroup\_for\_each\_live\_descendant\_pre()我们刚看过，因为这个cgroup是刚创建的，所以这里就遍历了一个节点。而整个函数的目标还是创建对应的cft文件并显示。只不过这里还隐含了一个动作css\_create()。是的，之前我们看到的cgroup\_subsystem\_state就是在这里创建的。

### 创建css

每个cgroup上enable的每个subsys都有一个对应的subsys\_state。这个结构由css\_create()创建。

```
css_create(cgrp, ss)
    css = ss->css_alloc()
    init_and_link_css(css, ss, cgrp)
    list_add_tail_rcu(&css->sibling, &parent_css->children)
    online_css(css)
        ss->css_online(css)
        rcu_assign_pointer(css->cgroup->subsys[ss->id], css)
```

总的来说，创建一个css做了这么几件事：

* 把自己链接到父节点的孩子中
* 把自己赋值到cgroup的subsys\[]中

好了，到这里，基本上就把cgroup层次结构所涉及到的内容都讲完了。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://richardweiyang-2.gitbook.io/kernel-exploring/00-index/03-hierarchy.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
