理解 Cgroup
简单的类比
Cgroup
是Linux
内核的一项特性,它可以对进程进行一些系统资源(比如CPU、内存、IO等)限制。为什么要做这样的限制呢? 因为供需关系不平衡!
举个例子,假设小明家里有一台台式电脑,爸爸需要用它处理公务,妈妈想用它看电视剧,小明想用它来玩游戏。 于是小明一家人决定,每天爸爸处理一个小时公务,妈妈再看一个小时电视剧,最后小明再玩一个小时游戏。
这样一家人都可以得到有限的满足。 如果把上面的人都换成内核中的进程,而将台式电脑换成CPU
。那么上面的例子就变成了三个进程都希望获取CPU
的控制权从而运行,通常来说,这是调度器的工作,与Cgroup
扯不上关系。
但如果小明家里又买了一台笔记本电脑,并且爸爸说它的工作比较重要,需要单独使用一台电脑。于是小明一家人决定爸爸单独使用笔记本电脑,而妈妈和小明分享台式电脑的使用时间。这种分配类比到内核,就是Cgroup
可以完成的工作。
我们可以创建两个组(分别称为cg0
和cg1
),然后将Father
进程加入到cg0
,将Mother
进程和Ming
进程加入到cg1
。最后再设置cg0
中的进程只能使用cpu0
,cg1
中的进程只能使用cpu1
。这样的结果就是Father
进程将独占cpu0
,
而Mother
进程和Ming
进程将共享cpu1
。
Cgroup
管理进程资源
子系统(subsystem)
Cgroup
限制的是每个进程注
的资源,这是通过将进程加入cgroup
组实现的。每个cgroup
组都关联(bind
)了一个或多个subsystem
,属于同一个cgroup
组的进程在绑定的subsystem
资源上拥有同样的限制。
随着内核的发展,越来越多的subsystem
加入到内核,而最常见的subsystem
有:
cpuset
:在多核系统中,为cgroup
进程划分特定的cpu
范围和NUMA
内存范围(也就是上文的例子)cpu
:当cpu
繁忙时,为cgroup
中的进程保证最小的CPU
使用率memory
: 限制cgroup
中的进程设置使用内存的上限devices
:限制cgroup
的进程可以访问的设备
其他的子系统还有blkio
、cpuacct
、freezer
等等。
一个cgroup
可以包含多个进程,相反的,一个进程也可以加入多个cgroup
组,只要这些cgroup
组关联的subsystem
不同就行。
注
也可以说是线程,这里我不打算区分进程与线程,本文中所说的进程,其实就是内核中的task_struct
层级(hierarchy)
hierarchy
这个概念在Cgroup
中是比较容易引起困惑的,其实说白了hierarchy
就是树状结构,它将cgroup
组串起来。前面提到cgroup
组关联subsystem
,更准确的说法是hierarchy
在创建的时候就关联subsystem
了。
默认情况下,内核启动后会创建多个hierarchy
,它们都关联了一个或两个subsystem
,这可以通过查看/sys/fs/cgroup
目录发现,该目录下每个子目录都是一个hierarchy
,并绑定了对应目录名的subsystem
。
root@ubuntu:/sys/fs/cgroup# ls -l
total 0
dr-xr-xr-x 5 root root 0 Jul 22 13:15 blkio
lrwxrwxrwx 1 root root 11 Jul 22 13:15 cpu -> cpu,cpuacct
lrwxrwxrwx 1 root root 11 Jul 22 13:15 cpuacct -> cpu,cpuacct
dr-xr-xr-x 5 root root 0 Jul 22 13:15 cpu,cpuacct
dr-xr-xr-x 3 root root 0 Jul 22 13:15 cpuset
dr-xr-xr-x 5 root root 0 Jul 22 13:15 devices
dr-xr-xr-x 2 root root 0 Jul 22 13:15 freezer
dr-xr-xr-x 2 root root 0 Jul 22 13:15 hugetlb
dr-xr-xr-x 5 root root 0 Jul 22 13:15 memory
lrwxrwxrwx 1 root root 16 Jul 22 13:15 net_cls -> net_cls,net_prio
dr-xr-xr-x 2 root root 0 Jul 22 13:15 net_cls,net_prio
lrwxrwxrwx 1 root root 16 Jul 22 13:15 net_prio -> net_cls,net_prio
dr-xr-xr-x 2 root root 0 Jul 22 13:15 perf_event
dr-xr-xr-x 5 root root 0 Jul 22 13:15 pids
dr-xr-xr-x 5 root root 0 Jul 22 13:15 systemd
hierarchy
化组织的cgroup
组的一个特性是我们可以为已有的cgroup
组创建子cgroup
组,并且子cgroup
组可以使用的subsystem
资源不能超过父cgroup
组.
每个hierarchy
在创建时,都会创建一个root cgroup
组,并将系统中的所有进程加入到这个根cgroup
组。
举个例子,在一台有8个cpu(编号0~7)的系统中,假设我们在根cgroup
组下创建了一个子cgroup
组cg1
,它限制其中的进程能使用的cpu
编号为0~3,那么如果我们为cg1
创建一个子cgroup
组cg2
,则cg2
中的进程最多也只能使用
编号为0~3的cpu。
前面说过,一个进程可以加入到多个cgroup
组,但有一个限制,这些cgroup
组不能属于同一个hierarchy
,否则,系统也不知道该进程应该遵循哪个cgroup
组的资源..
比如上面的httpd
进程,它加入了两个cgroup
组:/cpu_mem_cg/cg1
和/net/cg3
, 这两个cgroup
组分属不同的hierarchy
。此时,它就不能再加入同属hierarchy A
的/cpu_mem_cg/cg2
了。
当一个进程fork
出子进程后,子进程将继承父进程加入的所有cgroup
组。
虚拟文件系统(VFS)
我们可以通过内核为Cgroup
提供的虚拟文件系统来轻松地进行Cgroup
操作. 这个虚拟文件系统在内核启动时被挂载在/sys/fs/cgroup
路径。
···
root@ubuntu:/ # df -h
……
tmpfs 992M 0 992M 0% /sys/fs/cgroup
……
···
使用 Cgroup
创建/删除子cgroup
组
我们可以为已有的cgroup
组创建子cgroup
组,以绑定cpuset
的hierarchy
为例,路径/sys/fs/cgroup/cpu_set
下的内容就是前文提到的一个root cgroup
组
root@ubuntu:/sys/fs/cgroup/cpuset# ls
cgroup.clone_children cpuset.cpus cpuset.mem_hardwall cpuset.memory_spread_page cpuset.sched_relax_domain_level
cgroup.procs cpuset.effective_cpus cpuset.memory_migrate cpuset.memory_spread_slab notify_on_release
cgroup.sane_behavior cpuset.effective_mems cpuset.memory_pressure cpuset.mems release_agent
cpuset.cpu_exclusive cpuset.mem_exclusive cpuset.memory_pressure_enabled cpuset.sched_load_balance tasks
使用mkdir
创建子目录,我们可以创建名为cg1
的子cgroup
组
root@ubuntu:/sys/fs/cgroup/cpuset# mkdir cg1
root@ubuntu:/sys/fs/cgroup/cpuset# ls
cg1 cpuset.cpu_exclusive cpuset.mem_exclusive cpuset.memory_pressure_enabled cpuset.sched_load_balance tasks
cgroup.clone_children cpuset.cpus cpuset.mem_hardwall cpuset.memory_spread_page cpuset.sched_relax_domain_level
cgroup.procs cpuset.effective_cpus cpuset.memory_migrate cpuset.memory_spread_slab notify_on_release
cgroup.sane_behavior cpuset.effective_mems cpuset.memory_pressure cpuset.mems release_agent
root@ubuntu:/sys/fs/cgroup/cpuset# ls cg1
cgroup.clone_children cpuset.cpus cpuset.mem_exclusive cpuset.memory_pressure cpuset.mems notify_on_release
cgroup.procs cpuset.effective_cpus cpuset.mem_hardwall cpuset.memory_spread_page cpuset.sched_load_balance tasks
cpuset.cpu_exclusive cpuset.effective_mems cpuset.memory_migrate cpuset.memory_spread_slab cpuset.sched_relax_domain_level
可以看到,内核会自动填充这个子文件夹。
与创建cgroup
组相对应的,我们可以通过rmdir
删除子cgroup
组
root@ubuntu:/sys/fs/cgroup/cpuset# rmdir cg1
root@ubuntu:/sys/fs/cgroup/cpuset# ls
cgroup.clone_children cpuset.cpus cpuset.mem_hardwall cpuset.memory_spread_page cpuset.sched_relax_domain_level
cgroup.procs cpuset.effective_cpus cpuset.memory_migrate cpuset.memory_spread_slab notify_on_release
cgroup.sane_behavior cpuset.effective_mems cpuset.memory_pressure cpuset.mems release_agent
cpuset.cpu_exclusive cpuset.mem_exclusive cpuset.memory_pressure_enabled cpuset.sched_load_balance tasks
对cgroups
组进行设置
绑定了不同的subsystem
的cgroups
组有不同的参数,这些参数都可以通过写入对应cgroups
组文件系统路径下的文件进行设置。
以cpuset
为例,目录下的所有文件都是cgroups
组的参数。其中最基础的是cpuset.cpus
、cpuset.mems
和tasks
,
cpuset.cpus
表示加入该cgroups
组中的进程可以运行的cpu编号cpuset.mems
表示可以申请的内存所属的cpu节点(对非NUMA架构,这个值只能为0)。tasks
表示加入到该cgroup
中的进程号。
root@ubuntu:/sys/fs/cgroup/cpuset# cat cpuset.cpus
0-3
root@ubuntu:/sys/fs/cgroup/cpuset# cat cpuset.mems
0
root@ubuntu:/sys/fs/cgroup/cpuset# cat tasks
1
2
3
5
.....省略....
5937
5938
5939
5955
8113
8227
root cgroups
组的参数如上,可以看出,该cgroups
组限制该cgroups
组可用的cpu
编号是0~3
,包含所有进程
我们可以创建一个子cgroup
组,并设置它只能使用cpu0~1
root@ubuntu:/sys/fs/cgroup/cpuset# mkdir cg1
root@ubuntu:/sys/fs/cgroup/cpuset# cd cg1
root@ubuntu:/sys/fs/cgroup/cpuset/cg1# echo 0-1 > cpuset.cpus
root@ubuntu:/sys/fs/cgroup/cpuset/cg1# cat cpuset.cpus
0-1
root@ubuntu:/sys/fs/cgroup/cpuset/cg1# echo 0 > cpuset.mems
root@ubuntu:/sys/fs/cgroup/cpuset/cg1# cat cpuset.mems
0
root@ubuntu:/sys/fs/cgroup/cpuset/cg1# cat tasks
root@ubuntu:/sys/fs/cgroup/cpuset/cg1#
用户新创建的cgroup
组的tasks
文件是空的,说明没有进程受到cgroups
的限制。
将进程加入cgroup
组
每个进程在创建时都是继承其父进程的cgroups
组,默认情况下,它们都会加入root cgroups
组。而我们通过写入子cgroups
下的tasks
文件将指定的进程移动到该cgroups
root@ubuntu:/sys/fs/cgroup/cpuset/cg1# echo $$
5956
root@ubuntu:/sys/fs/cgroup/cpuset/cg1# echo 5956 > tasks
上面的命令可以将当前进程(bash) 加入到cg1
(如果是追加写入,就将上面的>
切换为>>
)
查看tasks
文件可以发现已经写入了5956 (8383这一个是cat
产生的临时进程,并不重要)
root@ubuntu:/sys/fs/cgroup/cpuset/cg1# cat tasks
5956
8383
我们使用stress
工具来测试,(没有安装的话可以通过apt-get install stress
进行安装)
通过以下命令创建4个计算密集型的子进程,并让它们工作在后台,这些子进程同样会受到cg1
的限制
root@ubuntu:/sys/fs/cgroup/cpuset/cg1# stress -c 4 &
通过top
命令可以看到这些进程都跑在了cpu0
和cpu1
上 (启动top
后按1切换cpu面板)
root@ubuntu:/sys/fs/cgroup/cpuset/cg1# top
top - 02:22:13 up 12:43, 2 users, load average: 1.77, 0.79, 1.64
Tasks: 286 total, 5 running, 281 sleeping, 0 stopped, 0 zombie
%Cpu0 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu2 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu3 : 0.3 us, 0.0 sy, 0.0 ni, 98.3 id, 0.0 wa, 0.0 hi, 1.3 si, 0.0 st
KiB Mem : 2029760 total, 281172 free, 671696 used, 1076892 buff/cache
KiB Swap: 1003516 total, 1003516 free, 0 used. 1082568 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
8425 root 20 0 7480 92 0 R 99.7 0.0 0:24.44 stress
8427 root 20 0 7480 92 0 R 96.7 0.0 0:28.36 stress
8428 root 20 0 7480 92 0 R 53.2 0.0 0:30.85 stress
8426 root 20 0 7480 92 0 R 49.8 0.0 0:30.22 stress