CGroup 是 Control Groups 的缩写,是 Linux 内核提供的一种能够限制、记录、隔离进程组 (process groups) 所使用的物力资源 (如 cpu memory i/o 等等) 的机制。2007 年进入 Linux 2.6.24 内核,CGroups 不是全新创造的,它将进程管理从 cpuset 中剥离出来,做者是 Google 的 Paul Menage。CGroups 也是 LXC 为实现虚拟化所使用的资源管理手段。css
CGroup 功能及组成java
CGroup 是将任意进程进行分组化管理的 Linux 内核功能。CGroup 自己是提供将进程进行分组化管理的功能和接口的基础结构,I/O 或内存的分配控制等具体的资源管理功能是经过这个功能来实现的。这些具体的资源管理功能称为 CGroup 子系统或控制器。CGroup 子系统有控制内存的 Memory 控制器、控制进程调度的 CPU 控制器等。运行中的内核可使用的 Cgroup 子系统由/proc/cgroup 来确认。node
CGroup 提供了一个 CGroup 虚拟文件系统,做为进行分组管理和各子系统设置的用户接口。要使用 CGroup,必须挂载 CGroup 文件系统。这时经过挂载选项指定使用哪一个子系统。数组
CGroup 支持的文件种类网络
表 1. CGroup 支持的文件种类数据结构
文件名 | R/W | 用途 |
---|---|---|
Release_agent多线程 |
RW架构 |
删除分组时执行的命令,这个文件只存在于根分组dom |
Notify_on_releaseide |
RW |
设置是否执行 release_agent。为 1 时执行 |
Tasks |
RW |
属于分组的线程 TID 列表 |
Cgroup.procs |
R |
属于分组的进程 PID 列表。仅包括多线程进程的线程 leader 的 TID,这点与 tasks 不一样 |
Cgroup.event_control |
RW |
监视状态变化和分组删除事件的配置文件 |
CGroup 相关概念解释
任务(task)。在 cgroups 中,任务就是系统的一个进程;
控制族群(control group)。控制族群就是一组按照某种标准划分的进程。Cgroups 中的资源控制都是以控制族群为单位实现。一个进程能够加入到某个控制族群,也从一个进程组迁移到另外一个控制族群。一个进程组的进程可使用 cgroups 以控制族群为单位分配的资源,同时受到 cgroups 以控制族群为单位设定的限制;
层级(hierarchy)。控制族群能够组织成 hierarchical 的形式,既一颗控制族群树。控制族群树上的子节点控制族群是父节点控制族群的孩子,继承父控制族群的特定的属性;
子系统(subsystem)。一个子系统就是一个资源控制器,好比 cpu 子系统就是控制 cpu 时间分配的一个控制器。子系统必须附加(attach)到一个层级上才能起做用,一个子系统附加到某个层级之后,这个层级上的全部控制族群都受到这个子系统的控制。
相互关系
每次在系统中建立新层级时,该系统中的全部任务都是那个层级的默认 cgroup(咱们称之为 root cgroup,此 cgroup 在建立层级时自动建立,后面在该层级中建立的 cgroup 都是此 cgroup 的后代)的初始成员;
一个子系统最多只能附加到一个层级;
一个层级能够附加多个子系统;
一个任务能够是多个 cgroup 的成员,可是这些 cgroup 必须在不一样的层级;
系统中的进程(任务)建立子进程(任务)时,该子任务自动成为其父进程所在 cgroup 的成员。而后可根据须要将该子任务移动到不一样的 cgroup 中,但开始时它老是继承其父任务的 cgroup。
图 1. CGroup 层级图
图 1 所示的 CGroup 层级关系显示,CPU 和 Memory 两个子系统有本身独立的层级系统,而又经过 Task Group 取得关联关系。
CGroup 特色
在 cgroups 中,任务就是系统的一个进程。
控制族群(control group)。控制族群就是一组按照某种标准划分的进程。Cgroups 中的资源控制都是以控制族群为单位实现。一个进程能够加入到某个控制族群,也从一个进程组迁移到另外一个控制族群。一个进程组的进程可使用 cgroups 以控制族群为单位分配的资源,同时受到 cgroups 以控制族群为单位设定的限制。
层级(hierarchy)。控制族群能够组织成 hierarchical 的形式,既一颗控制族群树。控制族群树上的子节点控制族群是父节点控制族群的孩子,继承父控制族群的特定的属性。
子系统(subsytem)。一个子系统就是一个资源控制器,好比 cpu 子系统就是控制 cpu 时间分配的一个控制器。子系统必须附加(attach)到一个层级上才能起做用,一个子系统附加到某个层级之后,这个层级上的全部控制族群都受到这个子系统的控制。
CGroup 应用架构
图 2. CGroup 典型应用架构图
如图 2 所示,CGroup 技术能够被用来在操做系统底层限制物理资源,起到 Container 的做用。图中每个 JVM 进程对应一个 Container Cgroup 层级,经过 CGroup 提供的各种子系统,能够对每个 JVM 进程对应的线程级别进行物理限制,这些限制包括 CPU、内存等等许多种类的资源。下一部分会具体对应用程序进行 CPU 资源隔离进行演示。
讲解 CGroup 设计原理前,咱们先来作一个简单的实验。实验基于 Linux Centosv6.564 位版本,JDK1.7。实验目的是运行一个占用 CPU 的 Java 程序,若是不用 CGroup 物理隔离 CPU 核,那程序会由操做系统层级自动挑选 CPU 核来运行程序。因为操做系统层面采用的是时间片轮询方式随机挑选 CPU 核做为运行容器,因此会在本机器上 24 个 CPU 核上随机执行。若是采用 CGroup 进行物理隔离,咱们能够选择某些 CPU 核做为指定运行载体。
清单 1.Java 程序代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
清单 1 程序会启动 10 个线程,这 10 个线程都在作占用 CPU 的计算工做,它们可能会运行在 1 个 CPU 核上,也可能运行在多个核上,由操做系统决定。咱们稍后会在 Linux 机器上经过命令在后台运行清单 1 程序。本实验须要对 CPU 资源进行限制,因此咱们在 cpu_and_set 子系统上建立本身的层级“zhoumingyao”。
清单 2. 建立层级
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
|
经过 mkdir 命令新建文件夹 zhoumingyao,因为已经预先加载 cpu_and_set 子系统成功,因此当文件夹建立完毕的同时,cpu_and_set 子系统对应的文件夹也会自动建立。
运行 Java 程序前,咱们须要确认 cpu_and_set 子系统安装的目录,如清单 3 所示。
清单 3. 确认目录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
输出显示 cpuset_cpu 的目录是 cpuset,cpu:/zhoumingyao,因为本实验所采用的 Java 程序是多线程程序,因此须要使用 cgexec 命令来帮助启动,而不能如网络上有些材料所述,采用 java –jar 命令启动后,将 pid 进程号填入 tasks 文件便可的错误方式。清单 4 即采用 cgexec 命令启动 java 程序,须要使用到清单 3 定位到的 cpuset_cpu 目录地址。
清单 4. 运行 Java 程序
[root@facenode4 zhoumingyao]# cgexec -g cpuset,cpu:/zhoumingyao java -jar test.jars
咱们在 cpuset.cpus 文件中设置须要限制只有 0-10 这 11 个 CPU 核能够被用来运行上述清单 4 启动的 Java 多线程程序。固然 CGroup 还能够限制具体每一个核的使用百分比,这里再也不作过多的描述,请读者自行翻阅 CGroup 官方材料。
清单 5.cpu 核限制
1 2 |
|
接下来,经过 TOP 命令得到清单 4 启动的 Java 程序的全部相关线程 ID,将这些 ID 写入到 Tasks 文件。
清单 6. 设置线程 ID
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
所有设置完毕后,咱们能够经过 TOP 命令查看具体的每一颗 CPU 核上的运行状况,发现只有 0-10 这 11 颗 CPU 核上有计算资源被调用,能够进一步经过 TOP 命令确认所有都是清单 4 所启动的 Java 多线程程序的线程。
清单 7. 运行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
整体上来讲,CGroup 的使用方式较为简单,目前主要的问题是网络上已有的中文材料缺乏详细的配置步骤,一旦读者经过反复实验,掌握了配置方式,使用上应该不会有大的问题。
CGroups 的源代码较为清晰,咱们能够从进程的角度出发来剖析 cgroups 相关数据结构之间的关系。在 Linux 中,管理进程的数据结构是 task_struct,其中与 cgroups 有关的代码如清单 8 所示:
清单 8.task_struct 代码
1 2 3 4 5 6 |
|
其中 cgroups 指针指向了一个 css_set 结构,而 css_set 存储了与进程有关的 cgroups 信息。cg_list 是一个嵌入的 list_head 结构,用于将连到同一个 css_set 的进程组织成一个链表。下面咱们来看 css_set 的结构,代码如清单 9 所示:
清单 9.css_set 代码
1 2 3 4 5 6 7 8 |
|
其中 refcount 是该 css_set 的引用数,由于一个 css_set 能够被多个进程公用,只要这些进程的 cgroups 信息相同,好比:在全部已建立的层级里面都在同一个 cgroup 里的进程。hlist 是嵌入的 hlist_node,用于把全部 css_set 组织成一个 hash 表,这样内核能够快速查找特定的 css_set。tasks 指向全部连到此 css_set 的进程连成的链表。cg_links 指向一个由 struct_cg_cgroup_link 连成的链表。
Subsys 是一个指针数组,存储一组指向 cgroup_subsys_state 的指针。一个 cgroup_subsys_state 就是进程与一个特定子系统相关的信息。经过这个指针数组,进程就能够得到相应的 cgroups 控制信息了。cgroup_subsys_state 结构如清单 10 所示:
清单 10.cgroup_subsys_state 代码
1 2 3 4 5 6 |
|
cgroup 指针指向了一个 cgroup 结构,也就是进程属于的 cgroup。进程受到子系统的控制,其实是经过加入到特定的 cgroup 实现的,由于 cgroup 在特定的层级上,而子系统又是附和到上面的。经过以上三个结构,进程就能够和 cgroup 链接起来了:task_struct->css_set->cgroup_subsys_state->cgroup。cgroup 结构如清单 11 所示:
清单 11.cgroup 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
sibling,children 和 parent 三个嵌入的 list_head 负责将统一层级的 cgroup 链接成一棵 cgroup 树。
subsys 是一个指针数组,存储一组指向 cgroup_subsys_state 的指针。这组指针指向了此 cgroup 跟各个子系统相关的信息,这个跟 css_set 中的道理是同样的。
root 指向了一个 cgroupfs_root 的结构,就是 cgroup 所在的层级对应的结构体。这样一来,以前谈到的几个 cgroups 概念就所有联系起来了。
top_cgroup 指向了所在层级的根 cgroup,也就是建立层级时自动建立的那个 cgroup。
css_set 指向一个由 struct_cg_cgroup_link 连成的链表,跟 css_set 中 cg_links 同样。
下面分析一个 css_set 和 cgroup 之间的关系,cg_cgroup_link 的结构如清单 12 所示:
清单 12.cg_cgroup_link 代码
1 2 3 4 5 |
|
cgrp_link_list 连入到 cgrouo->css_set 指向的链表,cgrp 则指向此 cg_cgroup_link 相关的 cgroup。
cg_link_list 则连入到 css_set->cg_lonks 指向的链表,cg 则指向此 cg_cgroup_link 相关的 css_set。
cgroup 和 css_set 是一个多对多的关系,必须添加一个中间结构来将二者联系起来,这就是 cg_cgroup_link 的做用。cg_cgroup_link 中的 cgrp 和 cg 就是此结构提的联合主键,而 cgrp_link_list 和 cg_link_list 分别连入到 cgroup 和 css_set 相应的链表,使得能从 cgroup 或 css_set 均可以进行遍历查询。
那为何 cgroup 和 css_set 是多对多的关系呢?
一个进程对应一个 css_set,一个 css_set 存储了一组进程 (有可能被多个进程共享,因此是一组) 跟各个子系统相关的信息,可是这些信息由可能不是从一个 cgroup 那里得到的,由于一个进程能够同时属于几个 cgroup,只要这些 cgroup 不在同一个层级。举个例子:咱们建立一个层级 A,A 上面附加了 cpu 和 memory 两个子系统,进程 B 属于 A 的根 cgroup;而后咱们再建立一个层级 C,C 上面附加了 ns 和 blkio 两个子系统,进程 B 一样属于 C 的根 cgroup;那么进程 B 对应的 cpu 和 memory 的信息是从 A 的根 cgroup 得到的,ns 和 blkio 信息则是从 C 的根 cgroup 得到的。所以,一个 css_set 存储的 cgroup_subsys_state 能够对应多个 cgroup。另外一方面,cgroup 也存储了一组 cgroup_subsys_state,这一组 cgroup_subsys_state 则是 cgroup 从所在的层级附加的子系统得到的。一个 cgroup 中能够有多个进程,而这些进程的 css_set 不必定都相同,由于有些进程可能还加入了其余 cgroup。可是同一个 cgroup 中的进程与该 cgroup 关联的 cgroup_subsys_state 都受到该 cgroup 的管理 (cgroups 中进程控制是以 cgroup 为单位的) 的,因此一个 cgroup 也能够对应多个 css_set。
从前面的分析,咱们能够看出从 task 到 cgroup 是很容易定位的,可是从 cgroup 获取此 cgroup 的全部的 task 就必须经过这个结构了。每一个进程都回指向一个 css_set,而与这个 css_set 关联的全部进程都会链入到 css_set->tasks 链表,而 cgroup 又经过一个中间结构 cg_cgroup_link 来寻找全部与之关联的全部 css_set,从而能够获得与 cgroup 关联的全部进程。最后,咱们看一下层级和子系统对应的结构体。层级对应的结构体是 cgroupfs_root 如清单 13 所示:
清单 13.cgroupfs_root 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
sb 指向该层级关联的文件系统数据块。subsys_bits 和 actual_subsys_bits 分别指向将要附加到层级的子系统和如今实际附加到层级的子系统,在子系统附加到层级时使用。hierarchy_id 是该层级惟一的 id。top_cgroup 指向该层级的根 cgroup。number_of_cgroups 记录该层级 cgroup 的个数。root_list 是一个嵌入的 list_head,用于将系统全部的层级连成链表。子系统对应的结构体是 cgroup_subsys,代码如清单 14 所示。
清单 14. cgroup_subsys 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
cgroup_subsys 定义了一组操做,让各个子系统根据各自的须要去实现。这个至关于 C++中抽象基类,而后各个特定的子系统对应 cgroup_subsys 则是实现了相应操做的子类。相似的思想还被用在了 cgroup_subsys_state 中,cgroup_subsys_state 并未定义控制信息,而只是定义了各个子系统都须要的共同信息,好比该 cgroup_subsys_state 从属的 cgroup。而后各个子系统再根据各自的须要去定义本身的进程控制信息结构体,最后在各自的结构体中将 cgroup_subsys_state 包含进去,这样经过 Linux 内核的 container_of 等宏就能够经过 cgroup_subsys_state 来获取相应的结构体。
从基本层次顺序定义上来看,由 task_struct、css_set、cgroup_subsys_state、cgroup、cg_cgroup_link、cgroupfs_root、cgroup_subsys 等结构体组成的 CGroup 能够基本从进程级别反应之间的响应关系。后续文章会针对文件系统、各子系统作进一步的分析。
就象大多数开源技术同样,CGroup 不是全新创造的,它将进程管理从 cpuset 中剥离出来。经过物理限制的方式为进程间资源控制提供了简单的实现方式,为 Linux Container 技术、虚拟化技术的发展奠基了技术基础,本文的目标是让初学者能够经过本身动手的方式简单地理解技术,将起步门槛放低。