Cgroup - 从 CPU 资源隔离说起Hi ,我是 Zorro 。这是我的微博地址,我会不定期在这里更新文章,如果你有兴趣,可以来关注我呦。 本文有配套视频演示,一起服用效果更佳。 另外,我的其他联系方式: Email: mini.jerry@gmail.com QQ: 30007147 今天我们来谈谈: 什么是 Cgroup ?
引用官方说法总是那么冰冷的让人不好理解,所以我还是稍微解释一下: 一个正在运行着服务的计算机系统,跟我们高中上课的情景还是很相似的。如果把系统中的每个进程理解为一个同学的话,那么班主任就是操作系统的核心( kernel ),负责管理班里的同学。而 cgroup ,就是班主任控制学生行为的一种手段,所以,它起名叫 control groups 。 既然是一种控制手段,那么 cgroup 能控制什么呢?当然是资源啦!对于计算机来说,资源大概可以分成以下几个部分:
这就是我们常说的内核四大子系统。当我们学习内核的时候,我们也基本上是围绕这四大子系统进行研究。 如何看待 CPU 资源?由于进程和线程在 Linux 的 CPU 调度看来没啥区别,所以本文后续都会用进程这个名词来代表内核的调度对象,一般来讲也包括线程 如果要分配资源,我们必须先搞清楚这个资源是如何存在的,或者说是如何组织的。我想 CPU 大家都不陌生,我们都在系统中用过各种工具查看过 CPU 的使用率,比如说以下这个命令和它的输出: [zorro@zorrozou-pc0 ~]$ mpstat -P ALL 1 1 Linux 4.2.5-1-ARCH (zorrozou-pc0) 2015 年 12 月 22 日 _x86_64_ (4 CPU)mt 16 时 01 分 08 秒 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle 16 时 01 分 09 秒 all 0.25 0.00 0.25 0.00 0.00 0.00 0.00 0.00 0.00 99.50 16 时 01 分 09 秒 0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00 16 时 01 分 09 秒 1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00 16 时 01 分 09 秒 2 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00 16 时 01 分 09 秒 3 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00 99.00 Average: CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle Average: all 0.25 0.00 0.25 0.00 0.00 0.00 0.00 0.00 0.00 99.50 Average: 0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00 Average: 1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00 Average: 2 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00 Average: 3 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00 99.00 显示的内容具体什么意思,希望大家都能了解,我就不在这细解释了。根据显示内容我们知道,这个计算机有 4 个 cpu 核心,目前的 cpu 利用率几乎是 0 ,就是说系统整体比较闲。 从这个例子大概可以看出,我们对 cpu 资源的评估一般有两个观察角度:
目前的计算机基本都是多核甚至多 cpu 系统,一个服务器上存在几个到几十个 cpu 核心的情况都很常见。所以,从这个角度看, cgroup 应该提供一种手段,可以给进程们指定它们可以占用的 cpu 核心,以此来做到 cpu 计算资源的隔离。 当然不是啦!这个 cpu 的百分比是按时间比率计算的。基本思路是:一个 CPU 一般就只有两种状态,要么被占用,要么不被占用。当有多个进程要占用 cpu 的时候,那么操作系统在一个 cpu 核心上是进行分时处理的。比如说,我们把一秒钟分成 1000 份,那么每一份就是 1 毫秒,假设现在有 5 个进程都要用 cpu ,那么我们就让它们 5 个轮着使用,比如一人一毫秒,那么 1 秒过后,每个进程只占用了这个 CPU 的 200ms ,使用率为 20%。整体 cpu 使用比率为 100%。 这就是内核是如何看待和分配计算资源的。当然实际情况要比这复杂的多,但是基本思路就是这样。 Linux 内核是通过 CPU 调度器 CFS --完全公平调度器对 CPU 的时间进行调度的,由于本文的侧重点是 cgroup 而不是 CFS ,对这个题目感兴趣的同学可以到这里进一步学习。 CFS 是内核可以实现真对 CPU 资源隔离的核心手段,因此,理解清楚 CFS 对理解清楚 CPU 资源隔离会有很大的帮助。 如何隔离 CPU 资源?根据 CPU 资源的组织形式,我们就可以理解 cgroup 是如何对 CPU 资源进行隔离的了。 无非也是两个思路,一个是分配核心进行隔离,另一个是分配 CPU 使用时间进行隔离。 再介绍如何做隔离之前,我们先来介绍一下我们的实验系统环境:没有特殊情况,我们的实验环境都是一台 24 核心、 128G 内存的服务器,上面安装的系统可以认为是 Centos7. 搭建测试环境我们将使用 cgconfig 服务和 cgred 服务对 cgroup 进行配置和使用。我们将配置两个 group ,一个叫 zorro ,另一个叫 jerry 。它们分别也是系统上的两个账户,其中 zorro 用户所运行的进程都默认在 zorro group 中进行限制, jerry 用户所运行的进程都放到 jerry group 中进行限制。配置文件内容和配置方法如下: 本文并不对以下配置方法的具体含义做解释,大家只要知道如此配置可以达到相关试验环境要求即可。如果大家对配置的细节感兴趣,可以自行查找相关资料进行学习。 首先添加两个用户, zorro 和 jerry : [root@zorrozou-pc ~]# useradd zorro [root@zorrozou-pc ~]# useradd jerry 修改 /etc/cgrules.conf ,添加两行内容: [root@zorrozou-pc ~]# cat /etc/cgrules.conf zorro cpu,cpuacct zorro jerry cpu,cpuacct jerry 修改 /etc/cgconfig.conf ,添加以下内容: [root@zorrozou-pc ~]# cat /etc/cgconfig.conf mount { cpuset = /cgroup/cpuset; cpu = /cgroup/cpu; cpuacct = /cgroup/cpuacct; memory = /cgroup/memory; devices = /cgroup/devices; freezer = /cgroup/freezer; net_cls = /cgroup/net_cls; blkio = /cgroup/blkio; } group zorro { cpuset { cpuset.cpus = '1,2'; } } group jerry { cpuset { cpuset.cpus = '3,4'; } } 重启 cgconfig 服务和 cgred 服务: [root@zorrozou-pc ~]# service cgconfig restart [root@zorrozou-pc ~]# service cgred restart 根据上面的配置,我们给 zorro 组合 jerry 组分别配置了 cpuset 的隔离设置,那么在 cgroup 的相关目录下应该出现相关组的配置文件: [root@zorrozou-pc ~]# ls /cgroup/cpuset/{zorro,jerry} /cgroup/cpuset/jerry: cgroup.clone_children cpuset.cpu_exclusive cpuset.mem_exclusive cpuset.memory_pressure cpuset.mems cpuset.stat cgroup.event_control cpuset.cpuinfo cpuset.mem_hardwall cpuset.memory_spread_page cpuset.sched_load_balance notify_on_release cgroup.procs cpuset.cpus cpuset.memory_migrate cpuset.memory_spread_slab cpuset.sched_relax_domain_level tasks /cgroup/cpuset/zorro: cgroup.clone_children cpuset.cpu_exclusive cpuset.mem_exclusive cpuset.memory_pressure cpuset.mems cpuset.stat cgroup.event_control cpuset.cpuinfo cpuset.mem_hardwall cpuset.memory_spread_page cpuset.sched_load_balance notify_on_release cgroup.procs cpuset.cpus cpuset.memory_migrate cpuset.memory_spread_slab cpuset.sched_relax_domain_level tasks 至此,我们的实验环境已经搭建完成。 测试用例设计无论是针对 CPU 核心的隔离还是针对 CPU 时间的隔离,我们都需要一个可以消耗大量的 CPU 运算资源的程序来进行测试,考虑到我们是一个多 CPU 核心的环境,所以我们的测试用例一定也是一个可以并发使用多个 CPU 核心的计算型测试用例。针对这个需求,我们首先设计了一个使用多线程并发进行筛质数的简单程序。这个程序可以打印出从 100010001 到 100020000 数字范围内的质数有哪些。并发 48 个工作线程从一个共享的 count 整型变量中取数进行计算。程序源代码如下: #include <pthread.h> #include <stdio.h> #include <stdlib.h> #define NUM 48 #define START 100010001 #define END 100020000 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; static int count = 0; void *prime(void *p) { int n, i, flag; while (1) { if (pthread_mutex_lock(&mutex) != 0) { perror('pthread_mutex_lock()'); pthread_exit(NULL); } while (count == 0) { if (pthread_cond_wait(&cond, &mutex) != 0) { perror('pthread_cond_wait()'); pthread_exit(NULL); } } if (count == -1) { if (pthread_mutex_unlock(&mutex) != 0) { perror('pthread_mutex_unlock()'); pthread_exit(NULL); } break; } n = count; count = 0; if (pthread_cond_broadcast(&cond) != 0) { perror('pthread_cond_broadcast()'); pthread_exit(NULL); } if (pthread_mutex_unlock(&mutex) != 0) { perror('pthread_mutex_unlock()'); pthread_exit(NULL); } flag = 1; for (i=2;i<n/2;i ) { if (n%i == 0) { flag = 0; break; } } if (flag == 1) { printf('%d is a prime form %d!\n', n, pthread_self()); } } pthread_exit(NULL); } int main(void) { pthread_t tid[NUM]; int ret, i; for (i=0;i<NUM;i ) { ret = pthread_create(&tid[i], NULL, prime, NULL); if (ret != 0) { perror('pthread_create()'); exit(1); } } for (i=START;i<END;i =2) { if (pthread_mutex_lock(&mutex) != 0) { perror('pthread_mutex_lock()'); pthread_exit(NULL); } while (count != 0) { if (pthread_cond_wait(&cond, &mutex) != 0) { perror('pthread_cond_wait()'); pthread_exit(NULL); } } count = i; if (pthread_cond_broadcast(&cond) != 0) { perror('pthread_cond_broadcast()'); pthread_exit(NULL); } if (pthread_mutex_unlock(&mutex) != 0) { perror('pthread_mutex_unlock()'); pthread_exit(NULL); } } if (pthread_mutex_lock(&mutex) != 0) { perror('pthread_mutex_lock()'); pthread_exit(NULL); } while (count != 0) { if (pthread_cond_wait(&cond, &mutex) != 0) { perror('pthread_cond_wait()'); pthread_exit(NULL); } } count = -1; if (pthread_cond_broadcast(&cond) != 0) { perror('pthread_cond_broadcast()'); pthread_exit(NULL); } if (pthread_mutex_unlock(&mutex) != 0) { perror('pthread_mutex_unlock()'); pthread_exit(NULL); } for (i=0;i<NUM;i ) { ret = pthread_join(tid[i], NULL); if (ret != 0) { perror('pthread_join()'); exit(1); } } exit(0); } 我们先来看一下这个程序在不做限制的情况下的执行效果和执行时间: [root@zorrozou-pc ~/test]# time ./prime_thread ...... 100019603 is a prime form 2068363008! 100019471 is a prime form 1866938112! 100019681 is a prime form 1934079744! 100019597 is a prime form 1875330816! 100019701 is a prime form 2059970304! 100019657 is a prime form 1799796480! 100019761 is a prime form 1808189184! 100019587 is a prime form 1824974592! 100019659 is a prime form 2076755712! 100019837 is a prime form 1959257856! 100019923 is a prime form 2034792192! 100019921 is a prime form 1908901632! 100019729 is a prime form 1850152704! 100019863 is a prime form -2109106432! 100019911 is a prime form -2125891840! 100019749 is a prime form 2101933824! 100019879 is a prime form 2026399488! 100019947 is a prime form 1942472448! 100019693 is a prime form 1917294336! 100019683 is a prime form 2051577600! 100019873 is a prime form 2110326528! 100019929 is a prime form -2134284544! 100019977 is a prime form 1892116224! real 0m8.945s user 3m32.095s sys 0m0.235s [root@zorrozou-pc ~]# mpstat -P ALL 1 11:21:51 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle 11:21:52 all 99.92 0.00 0.08 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 0 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 1 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 2 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 3 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 4 99.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 5 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 6 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 7 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 8 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 9 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 10 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 11 99.01 0.00 0.99 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 12 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 13 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 14 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 15 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 16 99.01 0.00 0.00 0.00 0.99 0.00 0.00 0.00 0.00 0.00 11:21:52 17 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 18 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 19 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 20 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 21 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 22 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 11:21:52 23 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 经过多次测试,程序执行时间基本稳定: [root@zorrozou-pc ~/test]# time ./prime_thread &> /dev/null real 0m8.953s user 3m31.950s sys 0m0.227s [root@zorrozou-pc ~/test]# time ./prime_thread &> /dev/null real 0m8.932s user 3m31.984s sys 0m0.231s [root@zorrozou-pc ~/test]# time ./prime_thread &> /dev/null real 0m8.954s user 3m31.794s sys 0m0.224s 所有相关环境都准备就绪,后续我们将在此程序的基础上进行各种隔离的测试。 |
|
来自: 昵称32165413 > 《文件夹1》