简介
CGroups的全称叫做 control groups,是由Google首创,并被合入Linux内核的一种资源限制机制。使用CGroups可以将一系列任务极其子任务整个到资源划分等级不同的组内,从而管理系统资源。通俗讲,CGroups可以限制和记录任务组所使用的物理资源(包括CPU、Memory、IO等)。
CGroups对外以伪文件系统的方式提供API,用户态程序可以操作对应的文件以达到对CGroups的控制,其对于操作的单元可以细粒度到线程级别。需要注意的是,当父任务创建完子任务后,子任务依然处于父任务的CGroups组中。
功能
CGroups主要提供了以下四大功能:
- 资源限制:CGroups可以针对任务使用的资源总额进行限制
- 优先级分配:通过对CPU时间片、IO带宽等的限制,达到了控制任务优先级的目的
- 资源统计:提供资源使用量的统计功能,例如CPU的使用时长、mem的占用量等
- 任务控制:CGroups可以针对任务进行挂起、恢复等。
概念
CGroups提供了非常强大的资源控制能力,当然,其相关概念也比较多,本节主要介绍CGroups涉及的概念,以及基本含义。本节对后边的内容相当重要,一定要熟悉相关概念。
task(任务)
表示CGroups中的一个任务,该任务可以是线程,也可以是进程。
subsystem(子系统)
CGroups中对于某种资源的调度控制器,例如CPU子系统,控制CPU时间片的分配;mem子系统,控制cgroup对于内存的使用量等。在Linux中,子系统主要有以下9种组成(不同内核版本看到的种类数可能不同):
- blkio:对于块设备输入输出的限制
- cpu:控制对于CPU的使用
- cpuacct:自动生成cgroup中任务对CPU资源使用情况的报告
- cpuset:可以为cgroup中任务分配独立的CPU和内存
- devices:开启或关闭cgroup中任务对设备的访问
- freezer:挂起或回复cgroup中的任务
- memory:设定cgroup中任务对内存使用量的限定,并自动生成资源使用情况报告
- perf_event:cgroup中任务可进行统一性能测试
- net_cls:通过等级标识标记网络数据包,从而允许流量控制程序识别从cgroup中生成的数据包
- net_prio:可以使用CGroup控制流量的优先级
- pids:限制任务的数量
cgroup(控制组)
在CGroups中,所有的控制都是以控制组为单位实现的,表示将某种或某些资源按照某种控制标准进行划分而成的任务组,包含一个或多个subsystem。一个task可以加入某个cgroup,也可以从某个cgroup迁移到另一个cgroup。
hierarchy(层级)
由一系列cgroup以树状结构排列而成,每个层级通过绑定对应的子系统进行资源控制。层级中的cgroup节点可以包含零个或多个自己诶点,子节点继承父节点挂载的子系统。
组织规则
cgroups的主要由以下四大规则进行组织
规则1
同一层级可以附加一个或多个子系统,例如一个层级中,可以附加cpu子系统,也可以附加mem子系统。
规则2
当且仅当目标层级只有一个子系统时,一个子系统可以附加到多个层级上。例如:层级A上拥有CPU子系统,层级B上拥有mem子系统时,CPU子系统无法再添加到层级B上;如果层级B上没有mem子系统时,CPU子系统可以附加于层级B上。
规则3
系统新建一个层级时,该系统上所有任务默认加入该新层级的初始化cgroup中,这个cgroup被称为root cgroup。对于创建的每个层级,任务只能存在于其中一个cgroup中,即一个任务不能存在于同一个层级的不同cgroup中,但一个任务可以存在于不同层级中的多个cgroup中。
规则4
任务在fork、clone自身时创建的子任务默认与父任务在同一个cgroup中,但子任务允许被移动到不同cgroup中。
实操
本节利用限制cpu使用率的例子,理解CGroups的简单使用。本节主要使用Linux命令行对文件进行操作,在程序中,我们可以使用程序提供的文件操作函数来对CGroup进行操作。
查看挂载
通过 mount | grep cgroup 命令查看cgroup的挂载
第一行显示的是cgroup的文件系统类型为tmpfs(内存中的临时文件系统);第二行表示systemd对cgroup的支持,systemd可以通过相关命令来控制进程;其余各行就是cgroups对应的子系统挂载目录。
挂载cgroups
通过 mount -t cgroup 命令挂载cgroup后,即可看到对应的子系统挂载目录
限制CPU
在操作限制CPU的实验之前,我们先编写一个消耗CPU高的程序,用来实验。
#include <stdlib.h>
int main(int argc, char *argv[]) {
while(1){
for(int i = 0 ;i<100;i++);
}
return EXIT_SUCCESS;
}
编译运行上述程序,并使用top命令查看其CPU占比
从图中可以看到,不一会CPU占用就飙升到了100%。我们先看下对应进程所属的cgroup(输入 cat /proc/[pid]/cgroup即可)
我们看到,对应的cgroup都是 / 。此时,我们进入cpu子系统的cgroup目录,创建新的cgroup名为cg1
进入cg1 cgroup后,我们ls后看到有很多文件,这里重点介绍两个我们限制cpu使用时用到的文件,其他文件大家有兴趣可以自行查阅相关资料。
- cpu.cfs_period_us:用来设置一个CFS调度时间周期长度,默认值是100000us(100ms),一般cpu.cfs_period_us作为系统默认值我们不会去修改它。
- cpu.cfs_quota_us:用来设置在一个CFS调度时间周期(cfs_period_us)内,允许此控制组执行的时间。默认值为-1表示没有限制时间。
- cgroup.procs:需要加入该cgroup的进程号
对于cpu.cfs_period_us和cpu.cfs_quota_us之间的关系再多做一点解释:
cpu.cfs_quota_us / cpu.cfs_period_us = 需要的CPU核数
例如:
- cpu.cfs_quota_us = 50000
- cpu.cfs_period_us = 100000
则,该表明cgroup需要 0.5 核 CPU
通过上边的理论支撑,我们如果要将上述程序的CPU使用率限制在50%,则需要分三步操作:
- 先cat cpu.cfs_period_us查看当前CFS的调度周期长度
- echo cpu.cfs_quota_us 写入允许此控制组执行的时间为cpu.cfs_period_us的二分之一
- 将进程号echo进 cgroup.procs
执行完上述操作后,我们可以看到,CPU的使用率已经被限制在了50%左右。
在日常的使用中,除了上述 cpu.cfs_quota_us 和 cpu.cfs_period_us 外,还有一个非常重要的文件是 cpu.shares,他是用来设置cpu子系统对于不同cgroup之间的cpu分配比例
例如,A cgroup 设置需要 4 个 CPU (cpu.cfs_quota_us/cpu.cfs_period_us = 4 ),B cgroup 也设置需要4个CPU,假设当前宿主机有16核CPU,则A和B都可以跑满4个CPU,即当CPU资源充足时,各自都可跑满设置的CPU核数。但是在CPU资源紧张的情况下,这个时候需要通过 cpu.shares 设置的比例来判断CPU分配的个数。例如还是上边的例子,假设此时宿主机只有4个CPU,A的cpu.shares为1024,B的cpu.shares 为 3072,他俩的比值为1:3,这个时候如果A满负荷运行,B不占用CPU或休眠,则A依然可以获得4个CPU。但是,如果此时A和B都需要满负荷运行,则A只能获得1个CPU,B可以获得3个CPU。
cpu.shares在生产环境最广泛的应用就是用来解决宿主机CPU资源紧张时优先保障核心业务或在线业务,主动降级非核心业务或离线业务。例如:我们可以将核心业务的cpu.shares设置为10240,将离线业务的cpu.shares设置为1024,则在资源紧张情况下,可以最大程度保障核心服务拿到CPU时间片的概率。
总结
CGroups是内核提供的一种资源限制机制,对用户态程序以伪文件系统的方式提供操作API,虽然概念繁多,但是我们通过对于cpu限制的实操,简单了解了CGroups的使用方法,也算是入门了CGroups这门技术。