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 /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
的。