目录
cgroup 简介
cgroup 与 namespace 类似,也是对进程进行分组控制,但与 namespace 不一样,namespace 是为了隔离进程之间的资源,而 cgroup 是为了对一组进程统一的资源进行控制和限制。在 Linux 世界里,一致以来就有对进程进行分组的概念和需求,比如 session group, process group 等,后来随着这方面的需求越来越多,比如需要跟踪一组进程的内存和 IO 使用情况等,于是出现了 cgroup, 用来统一将进程进行分组,并在分组的基础上对进程进程监控和资源控制管理等。cgroup 是 Linux 下的一种将进程按组进行管理的机制,在用户层看来,cgroup 技术就是把系统中的所有进程组织成一颗一颗独立的树,每棵树都包含系统的所有进程,树的每个节点是一个进程组,而每颗树又和一个或者多个 subsystem 关联,树的作用是将进程分组,而 subsystem 的作用就是对这些组进行操作。cgroup 主要包括下面两部分:
subsystem
一个 subsystem 就是一个内核模块,他被关联到一颗 cgroup 树之后,就会在树的每个节点(进程组)上做具体的操作。subsystem 经常被称作 “resource controller”,因为它主要被用来调度或者限制每个进程组的资源,但是这个说法不完全准确,因为有时我们将进程分组只是为了做一些监控,观察一下他们的状态,比如 perf_event subsystem。到目前为止,Linux 支持 12 种 subsystem,比如限制 CPU 的使用时间,限制使用的内存,统计 CPU 的使用情况,冻结和恢复一组进程等.
hierarchy
一个 hierarchy 可以理解为一棵 cgroup 树,树的每个节点就是一个进程组,每棵树都会与零到多个 subsystem 关联。在一颗树里面,会包含 Linux 系统中的所有进程,但每个进程只能属于一个节点(进程组)。系统中可以有很多颗 cgroup 树,每棵树都和不同的 subsystem 关联,一个进程可以属于多颗树,即一个进程可以属于多个进程组,只是这些进程组和不同的 subsystem 关联。目前 Linux 支持 12 种 subsystem,如果不考虑不与任何 subsystem 关联的情况 (systemd 就属于这种情况),Linux 里面最多可以建 12 颗 cgroup 树,每棵树关联一个 subsystem,当然也可以只建一棵树,然后让这棵树关联所有的 subsystem。当一颗 cgroup 树不和任何 subsystem 关联的时候,意味着这棵树只是将进程进行分组,至于要在分组的基础上做些什么,将由应用程序自己决定.
cgroup memory subsystem
代码总会有 bug,有时会有内存泄漏,或者有意想不到的内存分配情况,或者这是个恶意程序,运行起来就是为了榨干系统内存,让其它进程无法分配到足够的内存而出现异常,如果系统配置了交换分区,会导致系统大量使用交换分区,从而系统运行很慢。cgroup 对内存控制能做的如下:
- 限制 cgroup 中所有进程所能使用的物理内存总量
- 限制 cgroup 中所有进程所能使用的物理内存与交换空间总量(CONFIG_MEMCG_SWAP)
- 限制 cgroup 中所有进程所能使用的内核内存总量及其它一些内核资源.
cgroup 内核配置
cgroup 使用 memory subsystem 对进程组的内存资源进行控制,具体的资源与内核配置有关,因此在使用 cgroup 控制内存之前,请确保内核相应的宏已经打开,其在 BiscuitOS 的配置如下(以 BiscuitOS Linux 5.0 X86_64 为例)
重新编译内核之后,可以在系统 “/proc/cgroup” 中查看 Cgroup subsystem 的支持信息:
由于当前系统只启用了 memory subsystem, 因此只看到 memory susbsystem.
CMDLINE
内核 CMDLINE 提供了多个字段用于控制 Cgroup memory subsystem, 具体如下:
- cgroup_disable=memory 用于禁用 memory subsystem
- swapaccount=0 禁用 memory subsystem 的 Swap Extension
- swapaccount=1 启用 memory subsystem 的 Swap Extension
CONFIG_CGROUPS
CONFIG_CGROUPS 宏用于控制内核的 CGROUP 子系统,在使用 cgroup 的系统上必须打开该宏.
CONFIG_MEMCG
CONFIG_MEMCG 宏用于打开 Cgroupp 的 memory subsystem. 如果要使用 Cgroup 控制进程组的内存资源,那么该宏一定要启用. 当启用该宏之后创建一颗 Cgroup 树的时候,其挂载点下的布局如下:
从上图可以看出,当系统启用 CONFIG_MEMCG 宏之后,挂载一颗 cgroup 树之后,在树的目录下会自动生成多个与内存相关的文件节点,系统可以通过提供的文件节点对进程组的内存资源进行控制。
CONFIG_MEMCG_KMEM
CONFIG_MEMCG_KMEM 宏用于 Cgroup memory subsystem 对 KMEM 内存资源的控制,该宏由 CONFIG_MEMCG 自动选择,因此 KMEM 内存资源称为 Cgroup memory 默认管理的资源,当挂载一颗 Cgroup 树的时候,其挂载点布局如下:
从上图可以看出,当系统挂载一颗 Cgroup 树之后,在树的目录下就存在多个与 KMEM 内存相关的文件节点,系统可以通过控制 KMEM 文件节点来控制 KMEM 内存资源.
CONFIG_MEMCG_SWAP
CONFIG_MEMCG_SWAP_ENABLED
CONFIG_MEMCG_SWAP_ENABLED (3.6 以后的内核新加的参数) 控制默认情况下是否使用 Swap Extension,由于 Swap Extension 比较耗资源,所以很多发行版 (比如 Ubuntu) 默认情况下会禁用该功能,当然用户也可以根据实际情况,通过设置内核参数 “swapaccount=0” 或者 1 来手动禁用和启用 Swap Extension. 当挂载一颗 Cgroup 树的时候,其挂载点布局如下:
正如上面的布局,当挂载一颗 Cgroup 树之后,在树目录下多了 “memory.memsw” 相关文件节点,系统可以通过控制这些文件节点来控制 SWAP 相关的内存资源.
cgroup memory 基础使用
Cgroup 相关的所有操作都是基于内核中的 cgroup virtual filesystem,因此通过 Cgroup 控制进程的内存可以按照下面的步骤进行操作:
[步骤 1] 挂载一颗 Cgroup 树
首先需要挂载一颗与 memory subsystem 关联的 Cgroup 树到指定的目录:
在挂载之前确认已经创建好挂载点,挂载点为一个目录,可以使用 mkdir 命令进行创建。使用 mount 命令进行挂载,将挂载的文件系统类型设置为 cgroup, 然后通过 “-o” 指定 subsystem 的类型,这里选择 subsystem, NAME 字段则是私有名字,可以任意,Mount_dir 字段则是挂载点的路径。在挂载完毕之后,可以查看 memory cgroup 提供的文件节点:
/proc/meminfo
“/proc/meminfo” 是了解 Linux 系统内存使用状态的主要接口,与常用的 “free”、”vmstat” 等命令通过它获得数据。”/proc/meminfo” 的源码来自 “fs/proc/meminfo.c” 的 “meminfo_proc_show()” 函数,那么各字段的含义如下:
MemTotal
系统从加电开始到引导完成,firmware/BIOS 要保留一些内存,kernel 本身要占用一些内存,最后剩下可供 kernel 支配的内存就是 MemTotal。这个值在系统运行期间一般是固定不变的。可参阅解读 DMESG 中的内存初始化信息.
MemFree
表示系统尚未使用的内存。”MemTotal - MemFree” 就是已被用掉的内存.
MemAvailable
有些应用程序会根据系统的可用内存大小自动调整内存申请的多少,所以需要一个记录当前可用内存数量的统计值,MemFree 并不适用,因为 MemFree 不能代表全部可用的内存,系统中有些内存虽然已被使用但是可以回收的,比如 cache/buffer、slab 都有一部分可以回收,所以这部分可回收的内存加上 MemFree 才是系统可用的内存,即 MemAvailable。/proc/meminfo 中的 MemAvailable 是内核使用特定的算法估算出来的,要注意这是一个估计值,并不精确.