一、cgroup简介
1.1、cgroup子系统
cgroup支持多种维度的限制,具体如下:
子系统 | 说明 |
blkio | 这个子系统为块设备设定输入/输出限制,比如物理设备(磁盘,固态硬盘,USB 等等) |
cpu | 这个子系统使用调度程序提供对 CPU 的 cgroup 任务访问 |
cpuacct | 这个子系统自动生成 cgroup 中任务所使用的 CPU 报告 |
cpuset | 这个子系统为 cgroup 中的任务分配独立 CPU(在多核系统)和内存节点 |
devices | 这个子系统可允许或者拒绝 cgroup 中的任务访问设备 |
freezer | 这个子系统挂起或者恢复 cgroup 中的任务 |
memory | 这个子系统设定 cgroup 中任务使用的内存限制,并自动生成内存资源使用报告。 net_cls — 这个子系统使用等级识别符(classid)标记网络数据包,可允许 Linux 流量控制程序(tc)识别从具体 cgroup 中生成的数据包 |
net_prio | 这个子系统用来设计网络流量的优先级 |
hugetlb | 这个子系统主要针对于HugeTLB系统进行限制,这是一个大页文件系统 |
1.2、cgroup目录
通常安装完系统之后,会自动挂载这些目录,可以发现这些目录与上面的子系统是一一对应的
root@localhot:/sys/fs/cgroup/cpu$ mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_prio,net_cls)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
root@localhost:/sys/fs/cgroup/cpu$
cgroup限制,常用的子系统是cpu和memory。下面将通过cpu和内存限制进行说明。
二、cpu限制
2.1、创建测试程序
我们启动一个进程,代码如下:
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <pthread.h>
#include <sys/types.h>
void* run(void *data) {
printf("sub-thread-id:%d\n", syscall(SYS_gettid));
while(1);//子线程
}
int main(){
printf("main-thread-id:%d\n", syscall(SYS_gettid));
pthread_t thread;
pthread_attr_t attr;
pthread_attr_init( &attr );
pthread_attr_setdetachstate(&attr,1);
pthread_create(&thread, &attr, run, 0); //第二个参数决定了分离属性
while(1);//主线程
return 0;
}
[root@localhost ~]# gcc a.c -lpthread
[root@localhost ~]# ./a.out
main-thread-id:18867
sub-thread-id:18868
top - 15:31:30 up 36 min, 1 user, load average: 2.89, 1.72, 1.27
Threads: 2 total, 2 running, 0 sleeping, 0 stopped, 0 zombie
%Cpu(s): 62.8 us, 18.3 sy, 0.3 ni, 18.6 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 4772156 total, 2041864 free, 1462712 used, 1267580 buff/cache
KiB Swap: 4980732 total, 4980732 free, 0 used. 2866188 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
9288 root 20 0 14700 388 304 R 98.7 0.0 2:43.01 a.out
9289 root 20 0 14700 388 304 R 97.0 0.0 2:42.36 a.out
可以看到占用了两个core,200%
2.2、在系统cgroup目录直接创建
root@localhost:/sys/fs/cgroup/cpu$ mkdir tmp-cgroup
root@localhost:/sys/fs/cgroup/cpu$ ls tmp-cgroup/
cgroup.clone_children cgroup.procs cpuacct.usage cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release
cgroup.event_control cpuacct.stat cpuacct.usage_percpu cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat tasks
root@localhost:/sys/fs/cgroup/cpu$
创建完目录,目录中内容是自动创建出来的,参数说明可以参考《3.2. cpu Red Hat Enterprise Linux 6 | Red Hat Customer Portal》
配置 | 说明 | 备注 |
cpu.cfs_period_us | 默认值100000(即100ms), 最小值1000us和最大值1s | 详细举例 |
cpu.cfs_quota_us | 默认值-1,表示不限制 | |
tasks | 被限制的task id,实际是线程id(通过top -H -p 查看线程id) | 按照线程维度进行限制 |
cgroup.procs | 被限制的process id,进程id | 按照进程维度进行限制 |
举例说明:
cpu.cfs_period_us = 100000 (100ms)
cpu.cfs_quota_us = 50000 (50ms)
在这100ms调度周期内,最多占用50ms,换句话说,占用50%cpu,也就是0.5core。
在举例:
cpu.cfs_period_us = 100000 (100ms)
cpu.cfs_quota_us = 200000 (200ms)
在这100ms调度周期内,最多占用200ms,换句话说,占用200%cpu,也就是2core。
cpu核心数 = cpu.cfs_quota_us / cpu.cfs_period_us
2.3、按照线程维度限制cpu
[root@localhost cpu]# cd tmp-cgroup/
[root@localhost tmp-cgroup]# echo 50000 > cpu.cfs_quota_us
[root@localhost tmp-cgroup]# echo 18868 > tasks
[root@localhost tmp-cgroup]#
top - 16:12:42 up 33 min, 1 user, load average: 1.99, 1.52, 1.21
Threads: 2 total, 2 running, 0 sleeping, 0 stopped, 0 zombie
%Cpu(s): 39.3 us, 0.5 sy, 0.0 ni, 60.2 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 4772156 total, 2173732 free, 1345920 used, 1252504 buff/cache
KiB Swap: 4980732 total, 4980732 free, 0 used. 3023376 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
18867 root 20 0 14700 388 304 R 99.7 0.0 3:12.76 a.out
18868 root 20 0 14700 388 304 R 49.8 0.0 3:06.64 a.out
通过上面操作,我们对线程18868进行了限制,cpu使用率为50%。
如何解除限制呢?只需要将线程id,写入父级cgroup tasks文件中即可
[root@localhost tmp-cgroup]# echo 18868 > ../tasks
[root@localhost tmp-cgroup]#
top - 16:19:57 up 40 min, 1 user, load average: 2.40, 2.22, 1.66
Threads: 2 total, 2 running, 0 sleeping, 0 stopped, 0 zombie
%Cpu(s): 61.2 us, 4.2 sy, 0.8 ni, 33.4 id, 0.5 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 4772156 total, 2099132 free, 1378992 used, 1294032 buff/cache
KiB Swap: 4980732 total, 4980732 free, 0 used. 2952392 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
18868 root 20 0 14700 388 304 R 99.0 0.0 7:56.57 a.out
18867 root 20 0 14700 388 304 R 98.3 0.0 10:23.67 a.out
由于一个进程内的线程,是不固定的,尤其是java程序。因此我们需要按照整个进程级别进行限制,就要用到cgroup.procs。
2.4、按照进程维度限制cpu
按照刚才实现的代码,main-thread-id就是进程id。所以通过如下命令,可以对整个进程进行限制
[root@localhost tmp-cgroup]#
[root@localhost tmp-cgroup]# echo 18867 > cgroup.procs
[root@localhost tmp-cgroup]#
[root@localhost ~]# top -H -p 18867
top - 16:25:46 up 46 min, 2 users, load average: 1.23, 1.92, 1.74
Threads: 2 total, 2 running, 0 sleeping, 0 stopped, 0 zombie
%Cpu(s): 15.5 us, 1.4 sy, 0.0 ni, 83.1 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 4772156 total, 2110104 free, 1389992 used, 1272060 buff/cache
KiB Swap: 4980732 total, 4980732 free, 0 used. 2966200 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
18867 root 20 0 14700 388 304 R 24.9 0.0 15:28.42 a.out
18868 root 20 0 14700 388 304 R 24.9 0.0 13:01.60 a.out
2.5、cpu绑定
在某些特殊场景,希望进程能够独占cpu而不被置换出去,这就需要进行cpu绑定。我们可以通过代码方式进行cpu绑定,可以参考我之前写的博客《Linux进程、线程绑定CPU以及独占CPU》。这里介绍cgroup cpuset实现的。
通常家用电脑至少4个核心了,我们使用02号cpu( 编号是00开始,)。cpu绑定,是在/sys/fs/cgroup/cpuset目录中,我们在这个目录中创建一个目录tmp-cgroup-cpuset并且进入该目录。查看如下两个文件,默认都是空
[root@localhost tmp-cgroup-cpuset]# cat cpuset.cpus
[root@localhost tmp-cgroup-cpuset]# cat cpuset.mems
[root@localhost tmp-cgroup-cpuset]#
[root@localhost tmp-cgroup-cpuset]# echo 2-2 >> cpuset.cpus
[root@localhost tmp-cgroup-cpuset]# cat cpuset.cpus
2
[root@localhost tmp-cgroup-cpuset]# echo 0 >> cpuset.mems
[root@localhost tmp-cgroup-cpuset]# cat cpuset.mems
0
[root@localhost tmp-cgroup-cpuset]#
仍然使用上面那个测试程序并且重新启动一次
[root@localhost ~]#
[root@localhost ~]# ./a.out
main-thread-id:20951
sub-thread-id:20952
通过htop -p PID 命令进行常看,如果htop命令不存在需要自己安装一下,注意:htop显示cpu序号是从1开始:
1 [####################################################100.0%] Tasks: 156, 498 thr; 3 running
2 [#*** 4.6%] Load average: 1.41 0.83 0.99
3 [#* 1.9%] Uptime: 01:17:26
4 [####################################################100.0%]
Mem[|||||||||||||||||||||#************** 1.64G/4.55G]
Swp[ 0K/4.75G]
PID USER PRI NI VIRT RES SHR S CPU% MEM% TIME+ Command
20951 root 20 0 14700 388 304 R 200. 0.0 2:29.19 ./a.out
20952 root 20 0 14700 388 304 R 100. 0.0 1:14.43 ./a.out
可以观察一段时间,发现cpu 100%,核心数总是在变化,不是固定的。接下来是重点,我们将进程id写入到配置文件(cpuset中的cgroup.procs)中:
[root@localhost tmp-cgroup-cpuset]# cat cgroup.procs
[root@localhost tmp-cgroup-cpuset]# echo 20951 > cgroup.procs
[root@localhost tmp-cgroup-cpuset]#
1 [#** 3.3%] Tasks: 156, 497 thr; 3 running
2 [##* 3.3%] Load average: 2.15 1.48 1.22
3 [####################################################100.0%] Uptime: 01:20:14
4 [* 0.7%]
Mem[|||||||||||||||||||||#************** 1.65G/4.55G]
Swp[ 0K/4.75G]
PID USER PRI NI VIRT RES SHR S CPU% MEM% TIME+ Command
20951 root 20 0 14700 388 304 R 100. 0.0 7:05.96 ./a.out
20952 root 20 0 14700 388 304 R 49.7 0.0 3:32.95 ./a.out
我们发现,只有02号cpu(htop中是3)cpu使用率为100%,说明我们设置的是生效的
三、内存限制
下面对内存的限制,内存限制,在/sys/fs/memory目录中,我们仍然创建一个目录并将虚拟内存和swap内存设置相同值30M, 如下:
[root@localhost ~]# cd /sys/fs/cgroup/memory/
[root@localhost memory]#
[root@localhost memory]# mkdir tmp-cgroup-memory
[root@localhost memory]# cd tmp-cgroup-memory/
[root@localhost tmp-cgroup-memory]#
[root@localhost tmp-cgroup-memory]# echo "30M" >> memory.limit_in_bytes
[root@localhost tmp-cgroup-memory]# echo "30M" >> memory.memsw.limit_in_bytes
[root@localhost tmp-cgroup-memory]#
测试代码如下:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(){
printf("main-pid=%d\n", getpid());
int i = 100;
int mem_size=5*1024*1024; // 5mb
char *p = NULL;
int size = 0;
while(i) {//主线程
p = malloc(sizeof(char)*mem_size); //有内存泄露,没有关系,进程结束后就释放了
i--;
if (i > 80) {
sleep(3);
}
memset(p, 1, mem_size);
size += mem_size;
printf("[%d] - memory is allocated [%8d] bytes \n", getpid(), size);
}
printf(" malloc over\n");
while(1);
return 0;
}
[root@localhost tmp-cgroup-memory]#
[root@localhost tmp-cgroup-memory]# cat memory.limit_in_bytes
31457280
[root@localhost tmp-cgroup-memory]# cat memory.max_usage_in_bytes
31457280
[root@localhost tmp-cgroup-memory]# echo 8488 > cgroup.procs
[root@localhost tmp-cgroup-memory]#
[root@localhost ~]# ./memory-cgroup
main-pid=8488
[8488] - memory is allocated [ 5242880] bytes
[8488] - memory is allocated [10485760] bytes
[8488] - memory is allocated [15728640] bytes
[8488] - memory is allocated [20971520] bytes
[8488] - memory is allocated [26214400] bytes
[8488] - memory is allocated [31457280] bytes
已杀死
[root@localhost ~]#
[root@localhost tmp-cgroup-memory]# dmesg | grep 8488
[ 68.148488] wlp6s0b1: RX AssocResp from 64:64:4a:d5:a1:0f (capab=0x431 status=0 aid=3)
[ 644.945348] CPU: 2 PID: 8488 Comm: memory-cgroup Kdump: loaded Tainted: G W ------------ 3.10.0-957.el7.x86_64 #1
[ 644.945723] [ 8488] 0 8488 10020 9075 25 0 0 memory-cgroup
[ 644.945729] Memory cgroup out of memory: Kill process 8488 (memory-cgroup) score 1149 or sacrifice child
[ 644.945736] Killed process 8488 (memory-cgroup) total-vm:40080kB, anon-rss:35884kB, file-rss:416kB, shmem-rss:0kB
[root@localhost tmp-cgroup-memory]#
[root@localhost tmp-cgroup-memory]#
测试在死循环中每次申请5MB空间,当超过30MB的时候,就会被kill掉。通过终端以及dmesg命令可以查看出,说明memory限制生效了。
四、线程数限制
工作中,大部分都是java程序,在pass平台中,某些场景需要对线程数进行限制。
[root@localhost 8743]# cd /sys/fs/cgroup/pids
[root@localhost pids]#
[root@localhost pids]#
[root@localhost pids]# mkdir tmp-cgroup-pids
[root@localhost pids]# cd tmp-cgroup-pids/
[root@localhost tmp-cgroup-pids]#
[root@localhost tmp-cgroup-pids]# ls
cgroup.clone_children cgroup.event_control cgroup.procs notify_on_release pids.current pids.max tasks
[root@localhost tmp-cgroup-pids]#
[root@localhost tmp-cgroup-pids]# cat pids.max
max
[root@localhost tmp-cgroup-pids]#
[root@localhost tmp-cgroup-pids]#
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void* run(void *data) {
printf("sub-thread-id:%d\n", *(int*)data);
while(1);//子线程
}
int main(){
int i = 100;//创建最多100线程
printf("processor-id:%d, main-thread-id:0\n", getpid());
pthread_t thread;
pthread_attr_t attr;
pthread_attr_init( &attr );
pthread_attr_setdetachstate(&attr,1);
int j = 1;
while(i) {
pthread_create(&thread, &attr, run, &j); //第二个参数决定了分离属性
i--;
sleep(3);
j++;
}
return 0;
}
[root@localhost ~]# ./thread-cgroup
processor-id:9646, main-thread-id:0
sub-thread-id:1
sub-thread-id:2
sub-thread-id:3
[root@localhost tmp-cgroup-pids]#
[root@localhost tmp-cgroup-pids]# echo 9400 > cgroup.procs
[root@localhost tmp-cgroup-pids]#
最终当到了20个线程,再继续创建线程就会失败,被kill掉,如下所示:
sub-thread-id:15
sub-thread-id:16
sub-thread-id:17
sub-thread-id:18
sub-thread-id:19
段错误(吐核)
[root@localhost ~]#
六、总结
cgroup大体介绍就这样了,常用的限制基本介绍了一遍。对于其他隔离,需要自己查阅相关资料。但是可能细心的同僚,可能会说,有没有相关命令直接操作,这样操作下来的确不太方便?以及docker是如何实现的?ok,其实是有相关命令的,libcgroup,在下一篇将介绍libcgroup以及docker的实现。