V2EX 首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Distributions
Ubuntu
Fedora
CentOS
中文资源站
网易开源镜像站
V2EX  ›  Linux

Cgroup - 从 CPU 资源隔离说起(一)

  •  7
     
  •   jerry017cn · 2015-12-28 22:35:32 +08:00 · 5099 次点击
    这是一个创建于 696 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Cgroup - 从 CPU 资源隔离说起

    Zorro ] icon

    Hi ,我是 Zorro 。这是我的微博地址,我会不定期在这里更新文章,如果你有兴趣,可以来关注我呦。

    本文有配套视频演示,一起服用效果更佳。

    另外,我的其他联系方式:

    Email: mini.jerry@gmail.com

    QQ: 30007147

    今天我们来谈谈:

    什么是 Cgroup ?

    cgroups ,其名称源自控制组群( control groups )的简写,是 Linux 内核的一个功能,用来限制,控制与分离一个进程组群的资源(如 CPU 、内存、磁盘输入输出等)。

    --引自维基百科:cgroup

    引用官方说法总是那么冰冷的让人不好理解,所以我还是稍微解释一下:

    一个正在运行着服务的计算机系统,跟我们高中上课的情景还是很相似的。如果把系统中的每个进程理解为一个同学的话,那么班主任就是操作系统的核心( kernel ),负责管理班里的同学。而 cgroup ,就是班主任控制学生行为的一种手段,所以,它起名叫 control groups 。

    既然是一种控制手段,那么 cgroup 能控制什么呢?当然是资源啦!对于计算机来说,资源大概可以分成以下几个部分:

    • 计算资源
    • 内存资源
    • io 资源
    • 网络资源

    这就是我们常说的内核四大子系统。当我们学习内核的时候,我们也基本上是围绕这四大子系统进行研究。
    我们今天要讨论的,主要是 cgroup 是如何对系统中的 CPU 资源进行隔离和分配的。其他资源的控制,我们以后有空再说喽。

    如何看待 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 的时候,那么操作系统在一个 cpu 核心上是进行分时处理的。比如说,我们把一秒钟分成 1000 份,那么每一份就是 1 毫秒,假设现在有 5 个进程都要用 cpu ,那么我们就让它们 5 个轮着使用,比如一人一毫秒,那么 1 秒过后,每个进程只占用了这个 CPU 的 200ms ,使用率为 20%。整体 cpu 使用比率为 100%。
    同理,如果只有一个进程占用,而且它只用了 300ms ,那么在这一秒的尺度看来, cpu 的占用时间是 30 %。于是显示出来的状态就是占用 30%的 CPU 时间。

    这就是内核是如何看待和分配计算资源的。当然实际情况要比这复杂的多,但是基本思路就是这样。 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 的相关目录下应该出现相关组的配置文件:
    本文中所出现的的含义,如无特殊说明都是对应 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
    

    所有相关环境都准备就绪,后续我们将在此程序的基础上进行各种隔离的测试。

    15 回复  |  直到 2015-12-29 20:20:47 +08:00
        1
    Earthman   2015-12-28 23:02:08 +08:00   ♥ 1
    没人顶楼主,可惜,战略性 mark
        2
    Evovil   2015-12-28 23:05:15 +08:00
    mark 。。。
        3
    donge   2015-12-28 23:20:49 +08:00   ♥ 1
    楼主讲的不错,技术理解与表达能力兼具。
        4
    luohaha   2015-12-28 23:22:45 +08:00
    mark
        5
    bluecubic   2015-12-28 23:28:42 +08:00 via Android
    Lz 的技术文章不错,可是能不能介绍一下背景为什么要讨论这个主题
        6
    jerry017cn   2015-12-28 23:38:08 +08:00
    @bluecubic cgroup 是 Docker 的底层实现资源隔离的技术,我本人算是在工作中有一些使用上的心得经验,分享一下。
        7
    Gothack   2015-12-28 23:57:37 +08:00
    遇到熟人 😄
        8
    raingolee   2015-12-28 23:59:58 +08:00
    支持楼主~已下载 pdf

    最近刚好在看 namespace cgroup
        9
    janxin   2015-12-29 00:04:41 +08:00
    已感谢
        10
    br00k   2015-12-29 00:38:56 +08:00
    mark 好文
        11
    erik0   2015-12-29 00:48:55 +08:00
    关注。最近在学习容器。
        12
    Andiry   2015-12-29 00:54:53 +08:00
    不得不说这个测试程序写的很 2 ,要 mutex 和 cond 干嘛,每个线程根据自己的 id 直接取数不就完了
        13
    eastlhu   2015-12-29 08:37:10 +08:00
    还没仔细看,不过楼主这个认真劲不错
        14
    GNiux   2015-12-29 08:41:13 +08:00 via iPhone
    Mark. 昨晚就看到了。不过睡着了。
        15
    itfanr   2015-12-29 20:20:47 +08:00
    已收藏
    DigitalOcean
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   鸣谢   ·   608 人在线   最高记录 3541   ·  
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.0 · 56ms · UTC 18:51 · PVG 02:51 · LAX 10:51 · JFK 13:51
    ♥ Do have faith in what you're doing.
    沪ICP备16043287号-1