本系列文章主要从源码入手,分析linux kernel中cgroup的实现。
本文基于3.10.0-862.el7.x86_64版本kernel进行分析。
基本概念
在进行源码分析之前, 我们有必要了解一下cgroup的基本概念。
cgroup功能
Cgroups是control groups的缩写,是Linux内核提供的一种可以限制、记录、隔离进程组(process groups)所使用的物理资源(如:cpu,memory,IO等等)的机制。cgroups也是LXC和Docker为实现虚拟化所使用的资源管理手段。
Cgroups 最初的目标是为资源管理提供的一个统一的框架,既整合现有的 cpuset 等子系统,也为未来开发新的子系统提供接口。现在的 cgroups 适用于多种应用场景,从单个进程的资源控制,到实现操作系统层次的虚拟化(OS Level Virtualization)。Cgroups 提供了一下功能:
- 限制进程组可以使用的资源数量(Resource limiting )。比如:memory子系统可以为进程组设定一个memory使用上限,一旦进程组使用的内存达到限额再申请内存,就会出发OOM(out of memory)。
- 进程组的优先级控制(Prioritization )。比如:可以使用cpu子系统为某个进程组分配特定cpu share。
- 记录进程组使用的资源数量(Accounting )。比如:可以使用cpuacct子系统记录某个进程组使用的cpu时间。
- 进程组隔离(Isolation)。比如:使用ns子系统可以使不同的进程组使用不同的namespace,以达到隔离的目的,不同的进程组有各自的进程、网络、文件系统挂载空间。
- 进程组控制(Control)。比如:使用freezer子系统可以将进程组挂起和恢复。
概念
- 任务(
task)。在cgroups中,任务就是系统的一个进程。
- 控制族群(
coontrol group)就是一组按照某种标准划分的进程。Cgroups中的资源控制都是以控制族群为单位实现。一个进程可以加入到某个控制族群,也从一个进程组迁移到另一个控制族群。一个进程组的进程可以使用cgroups以控制族群为单位分配的资源,同时受到cgroups以控制族群为单位设定的限制。
- 层级(
hierarchy)。控制族群可以组织成hierarchical的形式,既一颗控制族群树。控制族群树上的子节点控制族群是父节点控制族群的孩子,继承父控制族群的特定的属性。
- 子系统(
subsytem)。一个子系统就是一个资源控制器,比如cpu子系统就是控制cpu时间分配的一个控制器。子系统必须附加(attach)到一个层级上才能起作用,一个子系统附加到某个层级以后,这个层级上的所有控制族群都受到这个子系统的控制
css_set 和 cgroup 的多对多的关系
我们先从进程的角度出发,来剖析cgroups相关数据结构之间的关系。
task_struct 结构
在 Linux 中,管理进程的数据结构是 task_struct,其中与 cgroups 有关的是如下两个成员:
1
2
3
4
5
6
|
#ifdef CONFIG_CGROUPS
/* Control Group info protected by css_set_lock */
struct css_set __rcu *cgroups;
/* cg_list protected by css_set_lock and tsk->alloc_lock */
struct list_head cg_list;
#endif
|
其中cgroups指向了一个css_set结构,而css_set存储了与进程相关的cgroups信息。cg_list将使用同一个css_set的进程链接在一起。
css_set 结构
我们来看css_set结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
/*
* A css_set is a structure holding pointers to a set of
* cgroup_subsys_state objects. This saves space in the task struct
* object and speeds up fork()/exit(), since a single inc/dec and a
* list_add()/del() can bump the reference count on the entire cgroup
* set for a task.
*/
struct css_set {
/* Reference count */
atomic_t refcount;
/*
* List running through all cgroup groups in the same hash
* slot. Protected by css_set_lock
*/
struct hlist_node hlist;
/*
* List running through all tasks using this cgroup
* group. Protected by css_set_lock
*/
struct list_head tasks;
/*
* List of cg_cgroup_link objects on link chains from
* cgroups referenced from this css_set. Protected by
* css_set_lock
*/
struct list_head cg_links;
/*
* Set of subsystem states, one for each subsystem. This array
* is immutable after creation apart from the init_css_set
* during subsystem registration (at boot time) and modular subsystem
* loading/unloading.
*/
struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
/* For RCU-protected deletion */
struct rcu_head rcu_head;
};
|
- 其中
refcount是该css_set的引用计数,因为一个css_set可以被多个进程共用,只要这些进程的cgroups信息相同。比如:在所有已经创建的层级里面都在同一个cgroup里的进程。
hlist是嵌入的hlist_node,用于把所有的css_set组成一个hash表,这样内核可以快速查找特定的css_set(后续单独有文字分析这个)。
tasks是将所有引用此css_set的进程连接成链表。
cg_links指向一个由struct cg_cgroup_link组成的链表。
subsys是一个指针数组,存储一组指向cgroup_subsys_state的指针。一个cgroup_subsys_state就是进程与一个特定的子系统相关的信息。通过这个指针,进程就可以获得相应的cgroups控制信息了。
我们可以通过实验,观察一下css_set中的tasks链表。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# 启动两个sleep进程
~ # sleep 72000 &
[1] 1968
~ # sleep 72000 &
[2] 1973
~ # # 创建一个新的memory cgroup
~ # mkdir /sys/fs/cgroup/memory/test1
~ # # 将这两个进程加入到新创建的cgroup中
~ # echo 1968 > /sys/fs/cgroup/memory/test1/cgroup.procs
~ # echo 1973 > /sys/fs/cgroup/memory/test1/cgroup.procs
~ # cat /sys/fs/cgroup/memory/test1/cgroup.procs
1968
1973
|
通过crash,我们可以看到这两个进程使用同一个css_set, 而使用同一个css_set的进程被cg_list链接到了一起,其链表头为css_set中的成员tasks。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
crash> task -R cgroups 1968
PID: 1968 TASK: ffff904710fdc000 CPU: 0 COMMAND: "sleep"
cgroups = 0xffff90460d67b9c0,
crash> task -R cgroups 1973
PID: 1973 TASK: ffff90463b61c000 CPU: 0 COMMAND: "sleep"
cgroups = 0xffff90460d67b9c0,
crash> struct -o css_set 0xffff90460d67b9c0
struct css_set {
[ffff90460d67b9c0] atomic_t refcount;
[ffff90460d67b9c8] struct hlist_node hlist;
[ffff90460d67b9d8] struct list_head tasks;
[ffff90460d67b9e8] struct list_head cg_links;
[ffff90460d67b9f8] struct cgroup_subsys_state *subsys[13];
[ffff90460d67ba60] struct callback_head callback_head;
}
SIZE: 176
crash> list -l task_struct.cg_list -s task_struct.comm -H ffff90460d67b9d8
ffff90463b61d508
comm = "sleep\000\000\000\060\000\000\000\000\000\000"
ffff904710fdd508
comm = "sleep\000\000\000\060\000\000\000\000\000\000"
|
cgroup_subsys_state 结构
接着,我们来看cgroup_subsys_state结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
/* Per-subsystem/per-cgroup state maintained by the system. */
struct cgroup_subsys_state {
/*
* The cgroup that this subsystem is attached to. Useful
* for subsystems that want to know about the cgroup
* hierarchy structure
*/
struct cgroup *cgroup;
/*
* State maintained by the cgroup system to allow subsystems
* to be "busy". Should be accessed via css_get(),
* css_tryget() and css_put().
*/
atomic_t refcnt;
unsigned long flags;
/* ID for this css, if possible */
struct css_id __rcu *id;
/* Used to put @cgroup->dentry on the last css_put() */
struct work_struct dput_work;
};
|
cgroup指针指向了一个cgroup结构,也就是进程属于的cgroup。进程受到子系统的控制,实际上是通过加入到特定的cgroup实现的,因为cgroup在特定的层级上,而子系统又是附加到层级上的。
通过以上三个结构,task_struct就可以和cgroup 连接起来了 :task_struct->css_set->cgroup_subsys_state->cgroup
cgroup 结构
我们再来看cgroup结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
struct cgroup {
unsigned long flags; /* "unsigned long" so bitops work */
/*
* count users of this cgroup. >0 means busy, but doesn't
* necessarily indicate the number of tasks in the cgroup
*/
atomic_t count;
int id; /* ida allocated in-hierarchy ID */
/*
* We link our 'sibling' struct into our parent's 'children'.
* Our children link their 'sibling' into our 'children'.
*/
struct list_head sibling; /* my parent's children */
struct list_head children; /* my children */
struct list_head files; /* my files */
struct cgroup *parent; /* my parent */
struct dentry *dentry; /* cgroup fs entry, RCU protected */
/*
* This is a copy of dentry->d_name, and it's needed because
* we can't use dentry->d_name in cgroup_path().
*
* You must acquire rcu_read_lock() to access cgrp->name, and
* the only place that can change it is rename(), which is
* protected by parent dir's i_mutex.
*
* Normally you should use cgroup_name() wrapper rather than
* access it directly.
*/
struct cgroup_name __rcu *name;
/* Private pointers for each registered subsystem */
struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
struct cgroupfs_root *root;
/*
* List of cg_cgroup_links pointing at css_sets with
* tasks in this cgroup. Protected by css_set_lock
*/
struct list_head css_sets;
struct list_head allcg_node; /* cgroupfs_root->allcg_list */
struct list_head cft_q_node; /* used during cftype add/rm */
/*
* Linked list running through all cgroups that can
* potentially be reaped by the release agent. Protected by
* release_list_lock
*/
struct list_head release_list;
/*
* list of pidlists, up to two for each namespace (one for procs, one
* for tasks); created on demand.
*/
struct list_head pidlists;
struct mutex pidlist_mutex;
/* For RCU-protected deletion */
struct rcu_head rcu_head;
struct work_struct free_work;
/* List of events which userspace want to receive */
struct list_head event_list;
spinlock_t event_list_lock;
/* directory xattrs */
struct simple_xattrs xattrs;
};
|
sibling,children和parent三个嵌入的list_head负责将同一层级的cgroup连接成一颗cgroup树。
subsys是一个指针数组,存储一组指向cgroup_subsys_state的指针。这组指针指向了此cgroup跟各个子系统相关的信息,这个跟css_set中的道理是一样的。
root指向了一个cgroupfs_root的结构,就是cgroup所在的层级对应的结构体。
root->top_cgroup指向了所在层级的根cgroup,也就是创建层级时自动创建的那个cgroup。cgroup->root->top_cgroup 可以获取层级的根cgroup信息
css_sets指向一个由struct cg_cgroup_link连成的链表,跟css_set中cg_links一样
css_set和cgroup之间的关系
cg_cgroup_link 结构
下面我们来分析一个css_set和cgroup之间的关系。我们先看一下cg_cgroup_link的结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/* Link structure for associating css_set objects with cgroups */
struct cg_cgroup_link {
/*
* List running through cg_cgroup_links associated with a
* cgroup, anchored on cgroup->css_sets
*/
struct list_head cgrp_link_list;
struct cgroup *cgrp;
/*
* List running through cg_cgroup_links pointing at a
* single css_set object, anchored on css_set->cg_links
*/
struct list_head cg_link_list;
struct css_set *cg;
};
|
cgrp_link_list连入到cgroup->css_sets指向的链表
cgrp则指向此cg_cgroup_link相关的cgroup。
cg_link_list则连入到css_set->cg_links指向的链表
cg则指向此cg_cgroup_link相关的css_set。
那为什么要这样设计呢?
那是因为cgroup和css_set是一个多对多的关系,必须添加一个中间结构来将两者联系起来,这跟数据库模式设计是一个道理。
cg_cgroup_link中的cgrp和cg就是此结构体的联合主键,而cgrp_link_list和cg_link_list分别连入到cgroup和css_set相应的链表,使得能从cgroup或css_set都可以进行遍历查询。
那为什么cgroup和css_set是多对多的关系呢?
一个进程对应css_set,一个css_set就存储了一组进程跟各个子系统相关的信息,但是这些信息有可能不是从一个cgroup那里获得的,因为一个进程可以同时属于几个cgroup,只要这些cgroup不在同一个层级。
举个例子:我们创建一个层级A,A上面附加了cpu和memory两个子系统,进程x属于A的根cgroup; 然后我们再创建一个层级B,B上面附加了pids和blkio两个子系统,进程x同样属于B的根cgroup; 那么进程x对应的cpu和memory的信息是从A的根cgroup获得的,pids和blkio信息则是从B的根cgroup获得的。
因此,一个css_set存储的cgroup_subsys_state可以对应多个cgroup。
另一方面,cgroup也存储了一组cgroup_subsys_state,这一组cgroup_subsys_state则是cgroup从所在的层级附加的子系统获得的。一个cgroup中可以有多个进程,而这些进程的css_set不一定都相同,因为有些进程可能还加入了其他cgroup。但是同一个cgroup中的进程与该cgroup关联的cgroup_subsys_state都受到该cgroup的管理(cgroups中进程控制是以cgroup为单位的)的,所以一个cgroup也可以对应多个css_set。
经过前面的分析,我们可以看出从task_struct到cgroup是很容易定位的(task_struct->css_set->cgroup_subsys_state->cgroup),当然我们也可以通过cg_cgroup_link定位到进程对应的所有cgroup,示意图如下:

但是从cgroup获取此cgroup所有的task_struct就必须通过cg_cgroup_link这个结构了。
每个进程都会指向一个css_set,而与这个css_set关联的所有进程都会链入到css_set->tasks链表.而cgroup又通过一个中间结构cg_cgroup_link来寻找所有与之关联的所有css_set,从而可以得到与cgroup关联的所有进程。示意图如下:

实验
在后台启动两个睡眠进程(睡眠足够长,为了后续好观察):
1
2
3
4
|
~ # sleep 36000 &
[1] 28889
~ # sleep 36000 &
[2] 28894
|
新创建一个memory的cgroup和一个pids的cgroup。并分布将两个睡眠进程加入到新创建的cgroup中。
1
2
3
4
5
|
~ # cd /sys/fs/cgroup/
/sys/fs/cgroup # mkdir memory/test
/sys/fs/cgroup # echo 28889 > memory/test/cgroup.procs
/sys/fs/cgroup # mkdir pids/test
/sys/fs/cgroup # echo 28894 > pids/test/cgroup.procs
|
通过task_struct->css_set->cgroup_subsys_state->cgroup 查找cgroup
我们通过crash可以查看这两个进程,可以看出两个进程对应的css_set不同。
1
2
3
4
5
6
7
|
crash> task -R cgroups 28889
PID: 28889 TASK: ffff904653b10000 CPU: 3 COMMAND: "sleep"
cgroups = 0xffff9046d1ef1a80,
crash> task -R cgroups 28894
PID: 28894 TASK: ffff90470446c000 CPU: 1 COMMAND: "sleep"
cgroups = 0xffff9047076a8540,
|
我们输出css_set的详细信息:
1
2
3
4
5
6
7
8
9
10
11
12
|
crash> css_set.cg_links,subsys 0xffff9046d1ef1a80
cg_links = {
next = 0xffff904713f68958,
prev = 0xffff904713f68558
}
subsys = {0xffffffffa3ffb2a0, 0xffff90471e913a00, 0xffffffffa49ac1c0, 0xffffffffa3ff1560, 0xffff904688253000, 0xffff9046d6aa4180, 0xffff90471e919540, 0xffff90471e913a80, 0xffffffffa40990c0, 0xffff90471e913b00, 0xffff90471e919600, 0xffff9046db1d1e40, 0xffff90471e913b80}
crash> css_set.cg_links,subsys 0xffff9047076a8540
cg_links = {
next = 0xffff904646222958,
prev = 0xffff904646222098
}
subsys = {0xffffffffa3ffb2a0, 0xffff90471e913a00, 0xffffffffa49ac1c0, 0xffffffffa3ff1560, 0xffff90471e96d000, 0xffff9046d6aa4180, 0xffff90471e919540, 0xffff90471e913a80, 0xffffffffa40990c0, 0xffff90471e913b00, 0xffff90471e919600, 0xffff9046448959c0, 0xffff90471e913b80}
|
这两个进程除了memory cgroup 和pids cgroup 不同外,其他的cgroup都相同。这可以通过如上的css_set的subsys观察到。
通过cgroup_subsys_state可以找到cgroup,cgroup中也存在着cgroup_subsys_state。不同的控制器结构体种第一个字段都是cgroup_subsys_state。
他们的关系如下:
1
2
3
4
5
|
crash> cgroup_subsys_state.cgroup 0xffffffffa3ffb2a0
cgroup = 0xffff90471418e030
crash> cgroup.subsys 0xffff90471418e030
subsys = {0xffffffffa3ffb2a0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
crash>
|
通过css_set的cg_links找cgroup
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
crash> struct -o css_set.cg_links 0xffff9046d1ef1a80
struct css_set {
[ffff9046d1ef1aa8] struct list_head cg_links;
}
crash> struct -o css_set.cg_links 0xffff9047076a8540
struct css_set {
[ffff9047076a8568] struct list_head cg_links;
}
crash> list -l cg_cgroup_link.cg_link_list -s cg_cgroup_link.cgrp -H ffff9047076a8568
ffff904646222958
cgrp = 0xffffffffa57ff030
ffff904646222758
cgrp = 0xffff9046894d0c00
ffff9046462227d8
cgrp = 0xffff904714188030
ffff904646222b18
cgrp = 0xffff90471418e030
ffff904646222c98
cgrp = 0xffff90471418c030
ffff904646222698
cgrp = 0xffff9047143c6030
ffff904646222458
cgrp = 0xffff9047143c4030
ffff904646222498
cgrp = 0xffff9047143c2030
ffff904646222e98
cgrp = 0xffff9047143c0030
ffff904646222a18
cgrp = 0xffff9046881b5c00
ffff904646222618
cgrp = 0xffff9047143ba030
ffff904646222798
cgrp = 0xffff9046db3e7400
ffff904646222098
cgrp = 0xffff9047143be030
crash> list -l cg_cgroup_link.cg_link_list -s cg_cgroup_link.cgrp -H ffff9046d1ef1aa8
ffff904713f68958
cgrp = 0xffffffffa57ff030
ffff904713f68bd8
cgrp = 0xffff9046894d0c00
ffff904713f68fd8
cgrp = 0xffff90464b624000
ffff904713f68158
cgrp = 0xffff90471418e030
ffff904713f687d8
cgrp = 0xffff90471418c030
ffff904713f685d8
cgrp = 0xffff9047143c6030
ffff904713f68118
cgrp = 0xffff9047143c4030
ffff904713f68a98
cgrp = 0xffff9047143c2030
ffff904713f68658
cgrp = 0xffff9047143c0030
ffff904713f684d8
cgrp = 0xffff904634e32000
ffff904713f68418
cgrp = 0xffff9047143ba030
ffff904713f68998
cgrp = 0xffff9046db3e7400
ffff904713f68558
cgrp = 0xffff9047143be030
|
可以看出,我们的系统上有13个cgroup子系统
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# cat /proc/cgroups
#subsys_name hierarchy num_cgroups enabled
cpuset 3 2 1
debug 4 3 1
cpu 5 40 1
cpuacct 5 40 1
memory 2 44 1
devices 11 42 1
freezer 10 2 1
net_cls 12 2 1
blkio 8 42 1
perf_event 6 2 1
hugetlb 7 2 1
pids 9 109 1
net_prio 12 2 1
|
** 通过cgroup的css_sets找css_set, 进而找到task_struct**
我们以刚才新创建的cgroup为例,其应该只有一个对应的css_set
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
crash> css_set.cg_links,subsys 0xffff9046d1ef1a80
cg_links = {
next = 0xffff904713f68958,
prev = 0xffff904713f68558
}
subsys = {0xffffffffa3ffb2a0, 0xffff90471e913a00, 0xffffffffa49ac1c0, 0xffffffffa3ff1560, 0xffff904688253000, 0xffff9046d6aa4180, 0xffff90471e919540, 0xffff90471e913a80, 0xffffffffa40990c0, 0xffff90471e913b00, 0xffff90471e919600, 0xffff9046db1d1e40, 0xffff90471e913b80}
crash> cgroup_subsys_state.cgroup 0xffff904688253000
cgroup = 0xffff90464b624000
crash> struct -o cgroup.css_sets 0xffff90464b624000
struct cgroup {
[ffff90464b6240c8] struct list_head css_sets;
}
crash> list -l cg_cgroup_link.cgrp_link_list -s cg_cgroup_link.cg -H ffff90464b6240c8
ffff904713f68fc0
cg = 0xffff9046d1ef1a80
|
以第一个cgroup(cpuset)为例,可以看到其关联了系统上所有的css_set。
1
2
3
4
5
6
7
8
9
10
|
crash> cgroup_subsys_state.cgroup 0xffffffffa3ffb2a0
cgroup = 0xffff90471418e030
crash> struct -o cgroup.css_sets 0xffff90471418e030
struct cgroup {
[ffff90471418e0f8] struct list_head css_sets;
}
crash> list -H ffff90471418e0f8 | wc -l
48
crash> p css_set_count
css_set_count = $1 = 48
|
参考文档