cgroup 比较有趣的地方是它没有提供任何的系统调用接口,所以你不能用 API Call 的方式使用 cgroup,实际上 cgroup 实现了 linux 虚拟文件系统 vfs,所以类似我们熟悉的 btfrs, ext4, 因此可以用类似文件系统的方式进行操作。
比如用 mount 命令看一下 linux 上挂载了哪些设备:
# mount -t cgroup
/dev/sda2 on / type ext4 (rw,relatime)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)
可以看到,
- 第一行是磁盘 sda2 挂载在根目录
/
, 它的类型是 ext4 - 后面几行是 cgroup 挂载在了目录
/sys/fs/cgroup/
,类型是 cgroup - 如果你的内核比较新的话,将看不到上面那些 cgroup 的行,而是只能看到最后这一行 cgroup2,这是因为新版本的内核使用了 cgroup v2 。
另外类似于 “net_cls”, “rdma” 这些都是 cgroup 子系统的名字,详见本文结尾的附录。
知道了上面这些,那么我们就能用操作文件系统的方式使用 cgroup 了,正好我有两台 linux VM,
- 一个 Ubuntu Server 22.04,内核 5.15, 默认使用了 cgroup v2
- 另一个是 Ubuntu Server 20.04, 内核 5.4,使用 cgroup v1
cgroup 和 cgroup2 有很多不一样的地方,具体见参考资料 1。本文所有例子都基于 cgroup v2。
直接使用 cgroup
既然 cgroup 实现了 VFS,那么就能用 mkdir 创建一个目录,比如
# cd /sys/fs/cgroup/
# mkdir test
# ls test
cgroup.controllers cgroup.type cpu.stat hugetlb.1GB.rsvd.max io.stat memory.oom.group pids.events
cgroup.events cpu.idle cpu.uclamp.max hugetlb.2MB.current io.weight memory.pressure pids.max
cgroup.freeze cpu.max cpu.uclamp.min hugetlb.2MB.events memory.current memory.stat rdma.current
cgroup.kill cpu.max.burst
- cgroup.controllers:这个文件显示了当前cgoup可以限制的相关资源有哪些。
- cpu.max 限制了最大可用的 cpu 时间。
- 在 cgroup v1 中可以设置
cpu.cfs_quota_us
和cpu.cfs_period_us
的值来限制 cpu 使用率,参考资料里大多是用的 v1。 - 到了 cgroup v2 我们需要在 cpu.max 文件中写入这两个值。后面还会提到。
- 在 cgroup v1 中可以设置
如果要设置某个 cgroup 的参数就是直接往对应的文件中写入特定格式的内容,比如要限制 cgroup 能够使用的 CPU 核数:
# echo 0-1 > /sys/fs/cgroup/test/cpuset.cpus
如果要用 “test” group 限制某个进程,只要把这个进程 PID 写入 cgroup.procs 文件
// $$ 表示当前 bash 的 PID,
$ echo $$ | sudo tee /sys/fs/cgroup/test/cgroup.procs
25982
上面这个例子的结果就是所有在当前 bash 创建的进程都会收到 cgroup “test” 的限制。
显然,这样直接的方式很不方便,因此就有了 cgroup-tools。
使用 cgroup-tools
在 ubuntu 系统中可以用下面这个命令安装
sudo apt-get install -y cgroup-tools
新版本的 tools 也加入了对 cgroup v2 的支持。下面通过 2 个例子来熟悉一下使用方式。
例 1 - 限制进程可用的 CPU 时间
还是沿用上面已经创建好的名为 “test” 的 group。
首先确认一下 root group 里面的 cgroup.controllers 和 subtree_control 包含了 cpu,如果没有的话需要手动加上 “cpu”。
root@loquat:/sys/fs/cgroup# cat cgroup.controllers
cpuset cpu io memory hugetlb pids rdma misc
root@loquat:/sys/fs/cgroup# cat cgroup.subtree_control
cpuset cpu io memory pids
# test group 的 cgroup.controllers 与上层 subtree_control 保持一致
root@loquat:/sys/fs/cgroup# cat test/cgroup.controllers
cpuset cpu io memory pids
再在 cpu.max 中写入
root@loquat:/sys/fs/cgroup# cat test/cpu.max
10000 100000
其含义是在linux CFS 进程调度的周期 1000000 us (也就是 100ms) 内,使用 10000 us (10 ms)。
再写一个程序循环很多次
void main(){
unsigned int i, end;
end = 1024 * 1024 * 1024;
for(i = 0; i < end; )
{
i ++;
}
}
结果:
root@loquat:/home/runzhen/code/cgroups# time ./cputime
real 0m1.937s
user 0m1.937s
sys 0m0.000s
root@loquat:/home/runzhen/code/cgroups# time cgexec -g cpu:test ./cputime
real 0m19.560s
user 0m1.975s
sys 0m0.000s
其中 user 表示的就是用户感知的时间,可见,当没有 cgroup 限制时大约 1.9 秒就结束了,当有 cgroup 限制时用了 19 秒,十倍的差距,符合我们之前设定的值。
例 2 - 限制进程可用的最大内存
类似的,再 memory 相关文件中写入值。
root@loquat:/sys/fs/cgroup/test# cat memory.max
5242880
root@loquat:/sys/fs/cgroup/test# cat memory.swap.max
0
memory.max 中值的单位是 “bytes”,因此 5242880 表示最大使用 500MB 内存。
另外需要注意的是关闭 swap,设置为 0
测试程序如下
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define CHUNK_SIZE 1024 * 1024 * 100
void main()
{
char *p;
int i;
for(i = 0; i < 5; i ++)
{
p = malloc(sizeof(char) * CHUNK_SIZE);
if(p == NULL)
{
printf("fail to malloc!");
return ;
}
memset(p, 0, CHUNK_SIZE);
printf("malloc memory %d MB\n", (i + 1) * 100);
}
}
运行结果
root@loquat:/home/runzhen/code/cgroups# ./mem
malloc memory 100 MB
malloc memory 200 MB
malloc memory 300 MB
malloc memory 400 MB
malloc memory 500 MB
root@loquat:/home/runzhen/code/cgroups# cgexec -g memory:test ./mem
Killed
测试程序因为 OOM 被 kill 了。
使用 stress 测试工具也能看到进程被 SIGKILL signal 9 了。
# cgexec -g memory:test stress --vm 1 --vm-bytes 500000000 --vm-keep --verbose
stress: info: [2684] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: dbug: [2684] using backoff sleep of 3000us
stress: dbug: [2684] --> hogvm worker 1 [2685] forked
stress: dbug: [2685] allocating 500000000 bytes ...
stress: dbug: [2685] touching bytes in strides of 4096 bytes ...
stress: FAIL: [2684] (416) <-- worker 2685 got signal 9
stress: WARN: [2684] (418) now reaping child worker processes
stress: FAIL: [2684] (452) failed run completed in 0s
附录
子资源系统(Resource Classes or SubSystem)
目前有下面这些资源子系统:
- Block IO(
blkio
):限制块设备(磁盘、SSD、USB 等)的 IO 速率 - CPU Set(
cpuset
):限制任务能运行在哪些 CPU 核上 - CPU Accounting(
cpuacct
):生成 cgroup 中任务使用 CPU 的报告 - CPU (
CPU
):限制调度器分配的 CPU 时间 - Devices (
devices
):允许或者拒绝 cgroup 中任务对设备的访问 - Freezer (
freezer
):挂起或者重启 cgroup 中的任务 - Memory (
memory
):限制 cgroup 中任务使用内存的量,并生成任务当前内存的使用情况报告 - Network Classifier(
net_cls
):为 cgroup 中的报文设置上特定的 classid 标志,这样 tc 等工具就能根据标记对网络进行配置 - Network Priority (
net_prio
):对每个网络接口设置报文的优先级 perf_event
:识别任务的 cgroup 成员,可以用来做性能分析
参考资料
- https://adtxl.com/index.php/archives/179.html
- https://zorrozou.github.io/docs/详解Cgroup V2.html
- cgroup-tools 支持 cgroup v2 https://github.com/libcgroup/libcgroup/issues/12
- https://askubuntu.com/questions/1406329/how-to-run-cgexec-without-sudo-as-current-user-on-ubuntu-22-04-with-cgroups-v2
- https://www.cnblogs.com/sparkdev/p/8296063.html
- https://cizixs.com/2017/08/25/linux-cgroup/