一、Cgroup的目的    

    Cgroup和namespa类似,也是将进程进程分组,但是目的与namespace不一样,namespace是为了隔离进程组之前的资源,而Cgroup是为了对一组进程进行统一的资源监控和限制。

二、为什么需要Cgroup

    在Linux里,对进程进程分组,比如Session group、process group等,后来需要追踪一组进程的内存和IO使用情况,出现了cgroup,用来统一对进程进行分组,并在分组的基础上对进程进程监控和资源控制管理等。

三、Cgroup中名词概念

任务(Task)在Cgroup中,任务就是系统的一个进程
控制族群(control group)控制族群就是按照一组某种标准划分的进程。Cgroup中的资源控制都是以控制族群为单位实现。一个进程可以加入到某个控制族群,也可以从一个进程组迁移到另一个控制族群。一个进程组的进程可以使用cgroups以控制族群为单位分配资源,同时受到cgroups以控制族群为单位设定限制。
层级(hierachy)控制族群可以组织成hierarchical的形式,既一颗控制族群树。控制族群树上的子节点控制族群是父节点控制族群的孩子,继承父控制族群的特定属性
子系统(subsystem)一个子系统就是一个资源控制器

    Cgroup可以指整个cgrop技术,也可以指一个具体的进程组。

    Cgroup是Linux下一种将进程按组管理的机制,在用户层面看,Cgroup技术就是可以把系统中的所有进程组织成一颗独立的树,每颗树都包含系统所有进程,树的每个节点就是一个进程组,而每颗树又和一个或者多个subsystem关联,树的作用是对进程分组,而subsystem作用是对这些组进行操作。

  • subsystem:一个subsystem就是一个内核模块,它被关联到一颗Cgroup树之后,就会对树的每个节点(进程组)上做具体操作。subsystem也被称为resource controller,因为它主要用来调度和限制每个进程组资源,有时也可以将进程分组只是为了做监控,观察进程组的状态。例如,perf_event subsystem。目前Linux支持12中system,比如限制CPU的使用时间,限制使用内存,统计CPU使用情况,冻结和恢复一组进程等。
  • 下面每个子系统都需要与内核的其它模块配合来完成资源控制,比如对CPU资源的限制是通过进程调度模块根据CPU子系统的配置来完成;对内存资源的限制是内存模块根据memoty子系统的配置来完成,对网络数据包的控制则需要Traffic Control子系统配合完成。
    subsystem作用
    cpu主要限制进程CPU使用率
    cpuacct统计cgroups中进程的cpu使用率
    cpuset为cgroups中进程分配单独的CPU节点(多核)或内存节点
    blkio限制进程的块设备IO
    memory限制进程的memory使用量
    devices可以控制进程能够访问某些设备
    freezer挂起或者恢复cgrops中进程
    net_cls可以标记cgroups中进程的网络数据包,然后可以使用tc(traffic control)对数据包进程控制
    ns可以使不同cgroups下面的进程使用不同的namespace

四、cgroups层级结构(Hierachy)

    内核使用cgroup结构体表示一个control group对某一个或者某几个cgroups子系统的资源限制。cgroup结构体可以组成一棵树,每一颗cgroup结构体组成的的树称为cgroup层级结构。cgroups层级结构可以attach一个或几个cgroups子系统,当前层级结构可以对其attach的cgroups子系统进行资源限制。每一个cgroups子系统只能被attach到一个cpu层级结构中

  上图表示两个cgroups层级结构,每一个层级结构中是一颗树形结构,树的每一个节点是一个cgroups结构体(比如cpu_cgrp,memory_cgrp)。第一个cgroups层级结构attach了cpu子系统和cpuacct子系统,当前

cgroups层级结构中的cgroups层级结构中的cgroup结构体就可以对cpu资源进进行限制,并且对进程的cpu使用情况进行统计。

    第二个cgroups层级结构中attach了memory子系统,当前cgroups层级结构中的cgroup结构体就可以对memory的资源进行限制。在每一个cgroups层级结构中,每一个节点(cgroup结构体)可以设置对资源不同的限制权重。比如上图中cgrp1组中的进程可以使用60%cpu时间片,而cgrp2组中的进程可以使用20%的cpu时间片。

 

 cgroup层级关系显示,CPU和Memory两个子系统有自己独立的层级系统而又通过Task group取得关联关系

相互关系:

每次在系统中创建新层级时,该系统中所有任务都是那个层级默认cgroup(root cgroup,此cgoup在创建层级时自动创建,后面在该层级中创建的cgroup都是此cgroup的后代)的初始成员;

  • 同一个hierarchy可以附加到一个或者多个subsystem
  • 一个subsystem可以附加到多个hierarchy,当且仅当这个hierarchy只有这一个subsystem,如下图,hierarchy B已经有了一个subsystem Memory,所以hierarchy不能attach到CPU这个subsystem中

 

  • 同一个task不能属于同一个hierarchy的不同的cgroup:
  • fork出的子进程在初始化状态与其父进程处于同一个cgroup。进程(task)在fork出自身时创建子任务(child task)默认与原task在同一个cgroup,但之后这个子任务可以被移动到其它cgroup中去,可以理解为这个子任务是一个独立的进程:

五、cgroups与进程

  在创建了cgroups层级结构中的节点(cgroup结构体)之后,可以把进程加入到某个节点的控制任务列表中,一个节点的控制列表中的所有进程都会受到当前节点的资源限制、同时一个进程也可以被加入到不同的cgroups层级结构的节点中,因为不同的cgroups层级结构可以负责不同的系统资源。所以说进程和cgroup结构是一个多对多的关系。

    上图从总体结构描述了进程与cgroups之间的关系。最下面的P代表一个进程。每个进程的描述符中有一个指针指向一个辅助的数据结构css_set(cgroup subsystem set)。指向某一个 css_set的进程会被加入到当前的css_set的进程链表中。一个进程只能隶属一个css_set,一个css_set可以包含多个进程,隶属于同一css_set的进程受同一个css_set所管理的资源限制。

   “MXN Linkage”说明css_set通过辅助的数据结构可以与cgroups节点进行多对多的关联。但是cgroup实现不允许css_set同时关联同一个cgroup层级结构下的多个节点。这是因为cgroups对同一种资源不运行有多个限额配置。 

    一个ccs_set关联多个cgroups层级结构的节点时,表明需要对当前css_set下的进程进行多种资源控制。而一个cgroups节点关联多个css_set时,表面多个css_set下的进行列表受到同一份资源的相同限制。

六、cgroups文件系统

    Linux使用多种数据结构在内核中实现了cgroups的配置,关联了进程和cgroups节点,那么Linux如何让用户态的进程使用到cgroups的功能呢?

    Linux内核有一个很强大的模块VFS(Virtual File System)。VFS能够把具体文件系统的细节隐藏起来,给用户态进程提供一个统一的文件系统API接口。cgroups也是通过VFS把功能暴露给用户态,cgroups与VFS衔接部分称为cgroups文件系统。

    VFS:

     VFS是一个内核抽象层,能够隐藏具体文件系统实现细节,从而给用户态提供一整套统一的API接口。VFS使用了一种通用文件系统的设计,具体的文件系统只要实现VFS的设计接口,就能够注册到VFS中,从而使内核可以读写这种文件系统。这很像面向对象设计中抽象类和子类之间的关系,抽象类负责对外接口的设计,子类负责具体的实现。其实,VFS本身就是用c语言实现的一套面向对象的接口。

    VFS通用文件模型中包含以下四种元数据结构:

    1. 超级块对象(superblock object) ,用于存放已经注册的文件系统信息。比如ext2,ext3等这些基础的磁盘文件系统,还用于读写socket的socket文件系统,以及当前用于读写cgroups文件系统等。

    2. 索引节点对象(inode object),用于存放具体文件的信息。对于一般的磁盘文件系统而言,inode节点中一般会存放文件在硬盘中的存储块信息;对于socket文件系统,inode会存放socket的相关属性,而对于cgoups这样的特殊文件系统,inode会存放与cgroup节点相关的属性信息。这里比较重要的一部分叫做inode_operations的结构体,这个结构体定义了在具体文件系统中创建文件,删除文件等具体操作。

   3. 文件对象(file object),一个文件对象表示一个进程打开的一个文件,文件对象是存放在进程文件描述符表里面的。同样这个文件中比较重要的部分是file_operations的结构体,这个结构体描述了具体的文件系统读写实现。当进程在某一个文件描述符上调用读写操作时,实际调用的时file_operations中定义的方法。对于普通的磁盘文件系统,file_operations中定义的就是普通块设备读写操作;对于socket文件系统,file_operations中定义的就是socket对应的send/recv等操作;而对于cgroups这样特殊的文件系统,file_operations中定义的就是操作cgroup结构体等具体实现。

4. 目录项对象(dentry object):在每个文件系统中,内核在查找一个路径的文件时,会为内核路径上的每一个分量都生成一个目录项对象,通过目录项对象找到对应的indode对象,目录项对象一般会被缓存,从而提供内核查找速度。

基于VFS实现的文件系统,都必须实现VFS通用文件模型定义这些对象,并实现这些对象中定义的部分函数。cgroup文件系统也不例外:

static struct file_system_type cgroup_fs_type = {
    .name = "cgroup",
    .mount = cgroup_mount,       //安装cgroup文件系统要执行的函数
    .kill_sb = cgroup_kill_sb,  //卸载cgroup文件系统要执行的函数
}

 

   每次把一个cgroups子系统安装到某个转载点时,cgroup_mount方法都会被调用,这个方法生成一个cgroup_root(cgroup层级结构的根)并封装成超级块对象。

    cgroups超级块对象定义的操作:

static file_system_type cgroup_fs_type = {
    .statfs = simple_statfs,
    .drop_inode = generic_delelte_inode,
    .show_option = cgroup_show_options,
    .remount_fd = cgroup_remount,
}

 cgroups文件系统对inode对象和file对象定义的特殊实现:

static const struct inode_operations cgroup_dir_inode_operations = {
        .lookup = cgroup_lookup,
        .mkdir = cgroup_mkdir,
        .rmdir = cgroup_rmdir,
        .rename = cgroup_rename,
};

static const struct file_operations cgroup_file_operations = {
        .read = cgroup_file_read,
        .write = cgroup_file_write,
        .llseek = generic_file_llseek,
        .open = cgroup_file_open,
        .release = cgroup_file_release,
};

 

    cgroups通过实现VFS的通用文件系统模型,把cgroups层级结构的细节,隐藏在cgroups文件系统的这些实现函数中。从令一个方面说,用户在用户态对cgroups文件系统的操作,通过VFS转化为对cgroups层级结构的维护。通过这样的方式,内核把cgroups的功能暴露给用户态进程。

七、cgroups使用方法

#################cgroups文件系统挂载###########################

Linux用户使用mount命令挂载cgroup文件系统,

mount -t cgroup -o subsystems name /cgroup/name  #其中subsystems表示要挂载的cgroups子系统, /cgroup/name表示挂载点,这条命令同时在内核中创建了一个cgroups层级结构

 比如挂载cpu_set,cpu, cpuacct,memory 4个subsystem到cgroup/cpu_and_mem目录下,

mount -t cgroup -o remount, cpu,cpuset, cpu_acct,memory cpu_and mem /cgroup/cup_and_mem

在centos下,使用yum install libcgroup安装了cgroups模块之后,在/etc/cgfong.conf文件中自动生成cgroups子系统的挂载点:

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;
}

上面的每一条配置都等于展开的mount命令,例如

mount -t cgroup -o cpuset cpuset /cgroup/cpuset  #这样系统启动之后会自动把这些子系统挂载到相应的挂载点

#########子节点和进程############

    挂载某个cgroups子系统到挂载点后,就可以通过在挂载点下面建立文件夹或者使用cgcreate命令创建cgroups层级结构中的节点,比如通过命令

cgcreate -t sankkuai:sankuai -g cpu: test #就可以在cpu子系统下面建立一个名为test节点
[root@idx cpu]# ls
cgroup.event_control  cgroup.procs  cpu.cfs_period_us  cpu.cfs_quota_us  cpu.rt_period_us   cpu.rt_runtime_us  cpu.shares  cpu.stat  lxc  notify_on_release  release_agent  tasks  test

  然后可以通过写入需要的值到test下面不同文件,来配置需要限制的资源。每个子系统下面都可以进行多种不同的配置,需要配置的参数各不相同,详细的参数配置参考cgroups手册。可以使用cgset命令设置cgroups子系统参数,格式为

cgset -r parameter=value path_to_cgroup

  当删除某一个cgroup节点时候,可以使用cgdelete命令,比如要删除test节点,

cgdelete -r cpu:test #删除test节点

 把进程加入到cgroups子节点有很多方法,可以把pid写入到子节点下面的task文件中,也可以通过cgclassify添加进程

cgclassify -g subsystem:path_to_cgroup pidlist

   也可以直接使用cgexec在某个cgroups下启动进程

cgexec -g subsystem:path_to_cgoup command aguments

八、cgroups实践

   Docker在实现不同的Container之间资源隔离和控制的时候,可以创建复杂的cgroups节点和配置文件完成,对于同一个Container中的进程,可以把这些进程的PID添加到同一组cgroups子节点中达到对这些进程进行同样的资源限制。

九、cgroups优点

    1. 在cgroups引入内核之前,想要完成对某一个进程CPU使用率进行限制,只能通过nice命令调整进程的优先级,或者cpulimit命令限制进程使用进程的CPU使用率,缺点是无法限制一个进程组的资源使用限制,也无法完成Docker或者其它云平台所需的这一类轻型容器资源限制。

   2. 在cgroups之前,想要完成一个或者一组进程的物理内存使用率的限制,几乎不可能完成。使用cgroups提供的功能,可以轻易的限制限制系统内某一组物理内存占用率、对于网络包,设备访问或者io资源控制,cgroups同样提供了之前无法完成的精细化控制。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐