CGroup 全称 Control Group 中文意思为 控制组,用于控制(限制)进程对系统各种资源的使用,比如 CPU、内存、网络 和 磁盘I/O 等资源的限制,著名的容器引擎 Docker 就是使用 CGroup 来对容器进行资源限制。
本文主要以 内存子系统(memory subsystem) 作为例子来阐述 CGroup 的原理,所以这里先介绍怎么通过 内存子系统 来限制进程对内存的使用。
子系统是CGroup用于控制某种资源(如内存或者CPU等)使用的逻辑或者算法
CGroup 使用了 虚拟文件系统 来进行管理限制的资源信息和被限制的进程列表等,例如要创建一个限制内存使用的 CGroup 可以使用下面命令:
$ mount -t cgroup -o memory memory /sys/fs/cgroup/memory上面的命令用于创建内存子系统的根 CGroup,如果系统已经存在可以跳过。然后我们使用下面命令在这个目录下面创建一个新的目录 test,
$ mkdir /sys/fs/cgroup/memory/test这样就在内存子系统的根 CGroup 下创建了一个子 CGroup,我们可以通过 ls 目录来查看这个目录下有哪些文件:
$ ls -l /sys/fs/cgroup/memory/test
cgroup.clone_children
memory.kmem.max_usage_in_bytes
memory.limit_in_bytes
memory.numa_stat
memory.use_hierarchy
cgroup.event_control
memory.kmem.slabinfo
memory.max_usage_in_bytes
memory.oom_control
notify_on_release
cgroup.procs
memory.kmem.tcp.failcnt
memory.memsw.failcnt
memory.pressure_level
tasks
memory.failcnt
memory.kmem.tcp.limit_in_bytes
memory.memsw.limit_in_bytes
memory.soft_limit_in_bytes
memory.force_empty
memory.kmem.tcp.max_usage_in_bytes
memory.memsw.max_usage_in_bytes
memory.stat
memory.kmem.failcnt
memory.kmem.tcp.usage_in_bytes
memory.memsw.usage_in_bytes
memory.swappiness
memory.kmem.limit_in_bytes
memory.kmem.usage_in_bytes
memory.move_charge_at_immigrate
memory.usage_in_bytes可以看到在目录下有很多文件,每个文件都是 CGroup 用于控制进程组的资源使用。我们可以向 memory.limit_in_bytes 文件写入限制进程(进程组)使用的内存大小,单位为字节(bytes)。例如可以使用以下命令写入限制使用的内存大小为 1MB:
$ echo 1048576 > /sys/fs/cgroup/memory/test/memory.limit_in_bytes然后我们可以通过以下命令把要限制的进程加入到 CGroup 中:
$ echo task_pid > /sys/fs/cgroup/memory/test/tasks上面的 task_pid 为进程的 PID,把进程PID添加到 tasks 文件后,进程对内存的使用就受到此 CGroup 的限制。
在介绍 CGroup 原理前,先介绍一下 CGroup 几个相关的概念,因为要理解 CGroup 就必须要理解他们:
任务(task)。任务指的是系统的一个进程,如上面介绍的 tasks 文件中的进程;控制组(control group)。控制组就是受相同资源限制的一组进程。CGroup 中的资源控制都是以控制组为单位实现。一个进程可以加入到某个控制组,也从一个进程组迁移到另一个控制组。一个进程组的进程可以使用 CGroup 以控制组为单位分配的资源,同时受到 CGroup 以控制组为单位设定的限制;层级(hierarchy)。由于控制组是以目录形式存在的,所以控制组可以组织成层级的形式,即一棵控制组组成的树。控制组树上的子节点控制组是父节点控制组的孩子,继承父控制组的特定的属性;子系统(subsystem)。一个子系统就是一个资源控制器,比如 CPU子系统 就是控制 CPU 时间分配的一个控制器。子系统必须附加(attach)到一个层级上才能起作用,一个子系统附加到某个层级以后,这个层级上的所有控制组都受到这个子系统的控制。他们之间的关系如下图:

我们可以把 层级 中的一个目录当成是一个 CGroup,那么目录里面的文件就是这个 CGroup 用于控制进程组使用各种资源的信息(比如 tasks 文件用于保存这个 CGroup 控制的进程组所有的进程PID,而 memory.limit_in_bytes 文件用于描述这个 CGroup 能够使用的内存字节数)。
而附加在 层级 上的 子系统 表示这个 层级 中的 CGroup 可以控制哪些资源,每当向 层级 附加 子系统 时,层级 中的所有 CGroup 都会产生很多与 子系统 资源控制相关的文件。
使用 CGroup 时,必须按照 CGroup 一些操作规则来进行操作,否则会出错。下面介绍一下关于 CGroup 的一些操作规则:
层级 可以附加多个 子系统,如下图:
子系统 只能被再次挂载在一个空的 层级 上,不能挂载到已经挂载了其他 子系统 的 层级,如下图:
任务 只能在同一个 层级 的唯一一个 CGroup 里,并且可以在多个不同层级的 CGroup 中,如下图:
fork 出时自动继承父进程所在 CGroup,但是 fork 之后就可以按需调整到其他 CGroup,如下图:
关于 CGroup 的介绍和使用就到这里,接下来我们来分析一下内核是怎么实现 CGroup 的。