什么是 cgroup
cgroup 的全称为 control group,中文翻译为 控制组。主要用于控制进程组对某种资源的使用,这些资源包括但不限于:内存、CPU、I/O 和 网络 等。 如下图所示,使用 cgroup 来限制进程组对内存的使用:- cpu子系统**:限制 CPU 的使用。**
- memory子系统**:限制内存使用。**
- cpuset子系统**:可以为进程组分配单独的 CPU 或者内存节点。**
- cpuacct子系统**:统计CPU group的使用情况。**
- blkio子系统**:限制I/O,一般用于磁盘。**
- devices子系统**:限制进程使用的设备。**
- freezer子系统**:可以挂起和恢复进程组。**
- net_cls子系统**:可以标记进程组的网络数据包,使用 tc 模块(traffic control)对数据包进行控制。**
cgroup 源码分析
cgroup 的设计还是比较复杂的,主要是因为 cgroup 涉及多种资源的控制,并且 cgroup 通过虚拟文件系统来组织进程控制组,所以导致 cgroup 的实现变得复杂难懂。 为了不会让大家陷入枯燥的概念和源码之中,本文主要通过以设计者的角度来分析 cgroup 的设计与实现。1、设计一个简单的 cgroup
如果让你来设计一个限制进程组对内存使用的方案,你会怎么设计呢? 最简单的方法就是,创建一个内存使用的计数器,然后将进程组中所有的进程都指向这个计数器。当进程组的进程申请内存时,就增加计数器的值,如果计数器超过限制就触发错误。如下图所示:2、控制组
有了上面的雏形,cgroup 的很多概念就比较容易理解了,下面主要介绍一下 控制组 这个概念。 控制组 说白了就是一组进程(进程组),cgroup 就是用来限制 控制组 的资源使用。为了能够方便地向一个 控制组 添加或者移除进程(在命令行也能操作),内核使用了 虚拟文件系统 来进行管理 控制组。 可以把一个 控制组 当成是一个目录,由于目录有层级关系,所以 控制组 也有层级关系,如下图所示:也可以通过读取 tasks 文件来查看某个 控制组 中的进程列表,例如:
$ cd /sys/fs/cgroup/memory/cgrp1 # 进入控制组cgrp1
$ echo 1029 > tasks # 将PID为1029的进程添加到cgrp1控制组中
在内核中,控制组使用 cgroup 结构来表示,其定义如下:
$ cat tasks
1
2
3
5
6
7
...
内核通过 cgroup 结构的 sibling、children 和 parent 这3个字段来将 控制组 组织成一棵树状结构。如下图所示:
struct cgroup {
...
// 下面3个字段把控制组连接成一个树结构
struct list_head sibling; // 兄弟节点
struct list_head children; // 子节点
struct cgroup *parent; // 父节点
struct dentry *dentry; // 当前控制组对应的目录对象
// 当前控制组关联的子系统资源统计对象
struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
...
};
在 Linux 内核中,有个名为 rootnode 的根层级,在系统启动后,由内核自动创建并且初始化的层级。系统启动后,所有的资源控制子系统都关联到此层级。rootnode 的定义如下:
struct cgroupfs_root {
struct super_block *sb; // 挂载点超级块对象(虚拟文件系统使用)
unsigned long subsys_bits; // 当前层级绑定的资源子系统位图(1表示已经绑定到当前层级)
...
struct list_head subsys_list; // 绑定到当前层级的资源子系统列表
struct cgroup top_cgroup; // 当前层级的根控制组
int number_of_cgroups; // 当前层级拥有的控制组数量
...
};
如果用户想把资源控制子系统关联到其他层级,那么可以使用 mount 命令来进行挂载,如下命令所示: $ mount -t cgroup -o memory memory /sys/fs/cgroup/memory
// 定义在文件 ./kernel/cgroup.c 中
static struct cgroupfs_root rootnode;
上面的命令用于将内存子系统重新关联到 /sys/fs/cgroup/memory 这个层级。
3、资源控制子系统
继续来介绍 资源控制子系统 (下面简称子系统) 这个重要的概念。 在 设计一个简单的 cgroup 例子中,主要以内存资源作为分析对象。但我们知道,计算机不单止只有内存资源,还有譬如 CPU、硬盘和网络等资源。所以,cgroup 不单止要控制内存资源的使用,还要控制 CPU、硬盘和网络等资源的使用。如下图所示:cgroup_subsys_state 结构看起来非常简单,这只是表面现象。内核为了将所有的 资源统计对象 抽象化(也就是都能用 cgroup_subsys_state 指针来指向所有类型的 资源统计对象),才定义出这个通用的部分,实际上的 资源统计对象 是比较复杂的。 例如内存的 资源统计对象 定义如下:
struct cgroup_subsys_state {
struct cgroup *cgroup; // 指向控制组对象
atomic_t refcnt; // 引用计数器
unsigned long flags; // 标志位
};
mem_cgroup 结构与 cgroup_subsys_state 结构的关系如下图所示:
struct mem_cgroup {
// 资源统计对象通用部分
struct cgroup_subsys_state css;
// 资源统计对象私有部分
struct res_counter res; // 用于统计进程组的内存使用情况
struct mem_cgroup_lru_info info;
int prev_priority;
struct mem_cgroup_stat stat;
};
可以看出,subsys 字段是一个 cgroup_subsys_state 结构的数组,数组的大小为系统支持的 资源控制子系统 数(也就是说,数组上的每个槽位对应着一个子系统资统计对象)。如下图所示:
struct cgroup {
...
// 当前控制组关联的资源统计对象
struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
...
};
- 进程A** 属于控制组 /sys/fs/cgroup/memory/cgrp1/cgrp3 和控制组 /sys/fs/cgroup/cpu/cgrp2/cgrp3,所以 进程A 就关联了 mem_group A 和 task_group A 这两个资源统计对象。**
- 进程B** 属于控制组 /sys/fs/cgroup/memory/cgrp1/cgrp4 和控制组 /sys/fs/cgroup/cpu/cgrp2/cgrp3,所以 进程B 就关联了 mem_group B 和 task_group A 这两个资源统计对象。**
在 进程描述符结构(task_struct) 中有个指向 css_set 结构的指针,如下所示:
struct css_set {
...
// 用于收集不同控制组的资源统计对象
struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
};
所以,当把一个进程添加到一个 控制组 时,将会把 控制组 关联的 资源统计对象 添加到进程的 cgroups 字段中,从而使进程受到这些 资源统计对象 的限制,结合图10就比较容易理解了。 另外,资源子系统必须关联到某个层级才能起到限制 控制组 使用的目的。每种资源子系统都由一个名为 cgroup_subsys 的结构来描述,其定义如下:
struct task_struct {
...
struct css_set *cgroups;
...
};
从 cgroup_subsys 结构的定义可以看出,其主要定义了一些方法和关联的层级。比如:create 方法主要用于当新建一个 控制组 时,创建一个新的 资源统计对象 与其关联;而 root 字段指向关联的层级根节点。 如内存子系统的定义如下:
struct cgroup_subsys {
struct cgroup_subsys_state *(*create)(struct cgroup_subsys *ss,
struct cgroup *cgrp);
...
void (*attach)(struct cgroup_subsys *ss, struct cgroup *cgrp,
struct cgroup *old_cgrp, struct task_struct *tsk);
void (*fork)(struct cgroup_subsys *ss, struct task_struct *task);
void (*exit)(struct cgroup_subsys *ss, struct task_struct *task);
...
int subsys_id;
int active;
int disabled;
int early_init;
const char *name; // 子系统名字
struct cgroupfs_root *root; // 关联的层级根节点
struct list_head sibling;
void *private;
};
// 定义在文件:./mm/memcontrol.c
struct cgroup_subsys mem_cgroup_subsys = {
.name = "memory",
.subsys_id = mem_cgroup_subsys_id,
.create = mem_cgroup_create,
.pre_destroy = mem_cgroup_pre_destroy,
.destroy = mem_cgroup_destroy,
.populate = mem_cgroup_populate,
.attach = mem_cgroup_move_task,
.early_init = 0,
};