CGroup操作

一、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和内存限制进行说明,注意实验在cgroup-v1版本实验

二、cpu限制
2.1、创建测试程序

我们启动一个进程,代码如下:

#define _GNU_SOURCE
#include 
#include 
#include 
#include 
#include 

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                                                               

tasks和procs区别,重点内容:

【tasks】

比如当前进程A,存在线程:主线程id-100(进程id也是100),子线程101后,我们手动将进程id-100,写入到tasks文件中后,会发现:

a、cgroup.procs也会出现100

b、子线程101,并不会加入tasks中

c、当在创建一个新的线程102后,发现tasks文件会出现102

【procs】

比如当前进程A,存在线程:主线程id-100(进程id也是100),子线程101后,我们手动将进程id-100,写入到cgroup.procs文件中后,会发现:

a、tasks也会出现100

b、子线程101,会自动加入tasks中

c、当在创建一个新的线程102后,发现tasks文件会出现102

主要区别在b)

如果写入task文件,是子线程id(不是进程id),会出现什么结果呢?

a)将子线程id写入task文件后,cgroup.procs文件是进程id(主线程id)

b)后续新建线程不会添加到task文件中

c)如果后续将进程id(主线程id),写入task文件,后续在创建新的线程仍然会自动添加到task中   

测试代码:

#define _GNU_SOURCE
#include 
#include 
#include 
#include 
#include 

void* run(void *data) { 
    printf("sub-thread-id:%d\n", syscall(SYS_gettid));
    while(1);//子线程
} 

int main(){ 
    int i = 5;
    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); 
    while(i) {//主线程
      pthread_create(&thread, &attr, run, 0); //第二个参数决定了分离属性
      i--;
      sleep(30);
    }
    while(1);
    return 0; 
}

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 
#include 
#include 
#include 

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 
#include 
#include 

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的实现。

你可能感兴趣的:(系统,cgroup入门操作,cpu,内存和线程限制)