Part 01 cgroup概述
cgroup是Control Groups的缩写,是Linux内核提供的一种可以对进程或进程组进行物理资源(如:CPU,内存,设备IO等)限制、隔离和统计的机制。cgroup的用户空间管理是通过cgroup文件系统来实现,得益于Linux的虚拟文件系统,其文件系统的细节被隐藏,用户通过相关的控制文件来实现该功能的使用。
cgroup于2.6内核时期由Google公司主导引入,它是Linux内核实现资源虚拟化的技术基础,是LXC(Linux Containers)和Docker容器的技术基石。cgroup中有如下相关概念:
- 任务(task):进程的别称;
- 控制组(control group):按照某种标准划分的进程集合。Cgroup中的资源控制都是以控制组为单位来实现。进程可以加入到某个控制组,也可以从一个进程组迁移到另一个控制组中。一个进程组的进程可以使用cgroups以控制组为单位分配的资源,同时受到cgroup以控制组为单位设置的资源限制。
- 层级(hierarchy):控制组的层级关系,采用树的结构方式组织,子节点的控制组继承父节点的资源设置属性。
- 子系统(subsystem):一个子系统就是一种资源控制器,比如cpu子系统可以控制进程CPU使用时间分配,如图1所示。子系统必须附件到一个层级上才能起作用,一个子系统附加到某个层级以后,这个层级上的所有控制组都受到这个子系统的控制。
Part 02 cgroup子系统
cgroup子系统和内核版本有关,随着内核的迭代,能限制的资源也越来越多,一般包括如下子系统。
➤ blkio:对输入/输出访问存取块设备设定限制,比如物理设备(磁盘,固态硬盘,USB等等)。
➤ cpu:限制进程的cpu使用,涉及cpu调度时间片分配。
➤ cpuacct:自动生成cgroup中任务所使用的cpu报告。
➤ cpuset:为cgroup中的任务分配独立cpu(多核系统)和内存节点。
➤ devices:允许或者拒绝cgroup中的任务访问设备。
➤ freezer:挂起或恢复cgroup中的任务。
➤ memory:设定cgroup中任务使用的内存限制,并自动生成由那些任务使用的内存资源报告。
➤ net_cls:使用等级识别符标记网络数据包,可允许Linux流浪控制程序识别从具体cgroup中生成的数据包。
➤ ns:namespace子系统。
Part 03 cgroup层级规则
结合cgroup层级(hierarchy)可以理解为一颗树,树的每个节点就是一个进程组,每棵树都会与一到多个子系统关联。在一棵树里,会包含Linux系统中的所有进程,但每个进程只能属于一个节点(进程组)。系统中可以有很多颗cgroup树,每棵树都和不同的subsystem关联,一个进程可以属于多棵树,即一个进程可以属于多个进程组,只是这些进程组和不同的子系统关联。目前Linux最多可以建十二颗cgroup树,每棵树关联一个子系统,当然也可以只建一棵树,然后让这棵树关联到所有的子系统。当一颗cgroup树不和任何子系统关联的时候,意味着这棵树只是将进程进行分组,至于要在分组的基础上做些什么,将由应用程序自己决定,systemd就是这样一个例子。
层级的组成规则有四个,描述如下:
规则1:单个层次结构可以具有一个或多个子系统。如图1所示,/cpu_memory_cg这个层级对cgroup1,cgroup2设置了cpu和memory两个子系统。
图1 层级规则1
规则2:如果任何一个子系统已经附加到了一个层次,则不能将他们附加到另一个层次的结构中。如图2所示,层级A的cpu_cg首先管理cpu子系统,那么层级B的cpu_mem_cg就无法管理cpu子系统。
图2 cgroup层级规则2
规则3:每次在系统上创建新的层次结构时,系统上的所有任务最初都是该层次结构的默认cgroup(称为根cgroup)成员。对于创建的任何单个层次结构,系统上的每个任务都可以是该层次结构中的一个cgroup成员。一个任务可以位于多个cgroup中,只要这些cgroup中的每个处于不同的子系统层次结构中即可。任务一旦成为同一层次结构中的第二个cgroup成员,就会将其从该层次结构中的第一个cgroup中删除,即在同一层次结构中的两个不通cgroup,绝不会有同一任务,也即是对某进程某类cgroup子系统的限制方式只能有一种。创建第一个层次结构时,系统上的每个任务都是至少一个cgroup(根cgroup)的成员,因此,在使用cgroup时,每个系统任务始终至少位于一个cgroup中,如图3所示。
图3 cgroup层级规则3
规则4:系统上派生的任何进程都会创建一个子进程(或线程)。子进程自动继承其父级的cgroup成员资格,但可以根据需要移动到其他cgroup中,移动后父子进程完全独立,如图4所示。
图4 cgroup层级规则4
Part 04 cgroup层级关系分析
我们从进程的角度出发,结合源码中的数据结构来解析cgroups相关数据之间的关系。首先在Linux中,管理进程的数据结构是task_struct,其中与cgroups有关的成员如下:
其中cgroup指向一个css_set结构,其存储了与进程相关的cgroups信息。cg_list为使用同一个css_set的进程链表。css_set结构如下:
结构体的元素信息解释如下:
- refcount是css_set的引用计数,其可以被多个进程共用,只要这些进程的cgroups信息相同。比如,在所有已经创建的层级里面都在同一个cgroup里的进程。
- hlist用于把所有css_set构建成一个hash表,内核能快速查找特定的css_set。
- tasks将所有引用此css_set的进程链接成链表。
- cg_links指向一个由struct cg_group_link组成的链表
- subsys为一个指针数组,存储一组指向cgroup_subsys_state的指针。一个cgroup_subsys_state就是进程与一个特定的子系统相关的信息。通过这个指针,进程就可以获得相应的cgroups控制信息。
接下来我们看一下cgroup_subsys_state结构体情况:
结构体中cgroup指针指向一个cgroup结构,进程受到子系统的资源控制,实际上是通过加入特定的cgroup子系统实现,因为cgroup在特定的层级上,而子系统又是附加到层级上的。
我们来看看cgroup的结构,
- sibling,children和parent三个链表负责将同一层级的cgroup连接成一棵树。
- susys为之前描述过的子系统指针数组。
- root指向了一个cgroupfs_root的结构,就是cgroup所在的层级对应的结构体。
- root->top_cgroup指向所在层级的根cgroup,也就是幻剑层级时自动创建的那个cgroup。获取层级的根cgroup可以通过cgroup->root->top_cgroup。
- css_sets指向一个由cg_cgroup_link的链表,和css_set中cg_links一致。
为了理清楚css_set和cgroup的关系,我们还需对中间层的cg_cgroup_link结构进行分析,结构体数据如下:
结构体中的数据说明如下:
cgrp_link_list链接到cgroup->css_sets指向的链表。
cgrp则指向此cg_cgroup_link相关的group。
cg_link_list则链接到css_set->cg_links指向的链表。
cg则指向cg_cgroup_link相关的css_set。
可以看出cgroup和css_set实际上是一个多对多的关系,需要添加一个中间结构将两者结合,cg_group_link中的cgrp和cg元素就是结合部,cgrp_link_list和cg_link_list两个链表即为挂接的cgroup和css_set实体,方便轮询。
从cgroup的层级规则中可以看出,一组进程可以同属于不在同一层级的cgroup,相结合理解,一个css_set存储了一组进程根各个子系统相关的信息,子系统来自不通的cgroup层级,因此一个css_set存储的cgroup_subsys_state可以对应多个cgroup。 另一方面,cgroup层级也存储了一组cgroup_subsys_state,其从cgroup所在的层级附加的子系统中获得,一个cgroup可以有多个进程,进程的css_set不一定相同,因为进程可能使用了多个层级,所以一个cgroup也需要对应多个css_set。图5详细描述了多对多的挂接关系。
图5 进程和cgroup多对多关系图
Part 05 结语
本文在cgroup概念基础上,对其和进程之间多对多的关系进行了拆解,从相关结构体中变量的挂接分析其具体代码实现方式,希望能帮助读者对cgroup层级关系和使用方式有更好的理解。