前言

本文主要会介绍笔者在学习Linux Cgroups时所总结的知识点,其中会涉及到Cgroups中的抽象概念、使用规则、实现原理以及API等方面的相关内容。 笔者也会将自己的理解在文中进行阐述,这也算是在和大家交流心得的一个过程。若文中有错误的理解和概念,请大家及时纠正;吸纳大家的建议,对于我来说也是很重要的学习过程之一。


(目录)


1.主要功能

Linux Cgroups(Control Groups)主要负责对指定的一组进程做资源限制,同时可以统计这些进程的资源使用情况。 它主要的功能包括:

  1. 资源限制 限制进程使用的资源上限,比如最大内存、文件系统缓存使用限制。

  2. 优先级控制 不同的进程组可以有不同的优先级,比如 CPU 使用和磁盘 IO 吞吐。

  3. 审计 计算一个进程组的资源使用情况。

  4. 控制 挂起一组进程,或者重启一组进程。


2.核心概念

Linux Cgroups是用来对进程进行资源管理的,因此Cgrpups对相关概念进行了抽象,并且定义了这些概念之间的组织关系(结构)。 Linux Cgroups中的核心概念分别是Task (任务)、Control Groups(控制组)、subsystem(子系统)、hierarchy(层级数)。

linamesp-cgrp13.jpg

注意: 笔者后续介绍的Linux Cgroups结构是基于Linux Cgroups V1版本的。读者如果感到后续内容与自身了解的知识所有差别时,请务必注意到这点。V2版本与V1版本的主要区别在于V2版本使用了Cgroups作为基础来进行资源管理,V1版本则是使用的资源类型(即subsystem)作为基础来进行资源管理。

2.1 Task

Task即任务。对应于系统中运行的一个实体,一般是指进程。 Linux内核本身的调度和管理并不对进程和线程进行区分,只是根据 clone 时传入的参数的不同来从概念上区分进程和线程。因此使用 Task 来表示系统的一个进程或线程。

2.2 Cgroups

Cgroups即控制组。Cgroups中包含了一组进程,是多个进程的集合。同时,Cgroups可以看作是一组Task和subsystem的关联关系,表示对一组Task进行怎样的资源管理策略。

Tips: 在Cgroup v1版本中,Cgroups是从属于subsystem的;而在Cgroup V2版本中,Cgroups被提升到了最底层,subsystem降到从属于Cgroups。一个Task可以加入某个Cgroup,也可以从某个Cgroup 迁移到另一个 Cgroup。

2.3 hierarchy

hierarchy即层级树,一个操作系统中可以有多个 hierarchy。 hierarchy是由一系列 Cgroup 组成的一个树形结构,树中的每个节点都是一个 Cgroup;而每个Cgroup 可以有多个子节点,子节点默认会继承父节点的属性。 hierarchy将 Cgroup 通过树状结构串起来,通过虚拟文件系统(VFS)的方式暴露给用户。系统每次新建一个hierarchy时,该系统上的所有Task默认构成了这个新建的hierarchy的初始化cgroup,即root cgroup。

2.4 subsystem

subsystem即子系统,指的是具体的资源控制器(resource class 或者 resource controller),控制某个特定的资源使用。比如 CPU 子系统可以控制 CPU 时间,memory 子系统可以控制内存使用量。 subsystem 作用于 hierarchy 的 Cgroup上,即帮助Cgroup控制其内部的多个进程的资源占用。

注意:subsystem是绑定在cgroup节点上,而非绑定在hierarchy上。之所以会说"subsystem作用在hierarchy上"是因为通常都会将subsystem绑定在一个hierarchy的root cgroup上。而root cgroup下的子cgroup节点就会继承root cgroup的属性,即使用绑定在root cgroup上的subsystem来进行资源管控。

截屏2024-02-04 15.00.13.png

2.5 实现原理

cgroups-connect.png

P代表一个进程。每个进程的描述符中有一个指针指向了一个辅助数据结构css_set(cgroups subsystem set)。指向某一个css_set的进程会被加入到当前css_set的进程链表中,一个进程只能隶属于一个css_set。一个css_set可以包含多个进程,隶属于同一css_set的进程受到同一个css_set所关联的hierarchy绑定的subsystem的资源限制。

css_set通过数据结构cgroup_subsys_state与cgroups节点进行多对多的关联:

  1. css_set关联多个cgroups时 表明需要对当前css_set下的进程使用多种资源限制逻辑。

    注意:不允许css_set同时关联同一个hierarchy中的多个cgroups节点,

  2. cgroups节点关联多个css_set时 表明多个css_set下的进程使用同一份资源限制逻辑。


3.使用规则

对于 Task (任务)、Control Groups(控制组)、subsystem(子系统)、hierarchy(层级数),在实现和使用这几种概念需要遵守一定的规则,否则Cgroup的配置将无法生效。

  1. 同一个hierarchy能够附加一个或多个subsystem

截屏2024-02-04 16.06.50.png

  1. Task与Cgroup的关系 一个Task不能存在于同一个hierarchy的多个cgroup中。如果操作时把一个Task添加到同一个hierarchy中的另一个cgroup中,则会从第一个cgroup中移除。但一个Task可以存在在不同hierarchy中的多个cgroup中

截屏2024-02-04 16.09.14.png

  1. 子Task自动继承父Task的Cgroup配置 对一个Task(Linux中的进程)进行fork操作后,生成的子Task会其在同一个Cgroup中,并且会自动继承父Task的Cgroup配置。而子Task可以根据需要移到其它不同的Cgroup中,即父子Task之间是相互独立不依赖的

截屏2024-02-04 16.12.24.png


4.Cgroup驱动

Linux 内核提供了很多 Cgroup 驱动,容器中常用的是下面两种。

4.1 Cgroupfs 驱动

需要限制 CPU 或内存使用时,直接把容器进程的 PID 写入相应的 CPU 或内存的 Cgroup。

4.2 systemdCgroup 驱动

提供 Cgroup 管理,所有的 Cgroup 写操作需要通过 systemd 的接口来完成,不能手动修改。


5. API

5.1 实现原理

Linux 使用了多种数据结构在内核中实现了 Cgroups 的配置关联了进程和 Cgroups 节点,所以内核态的进程可直接调用操作配置Cgroup。但对于用户态的进程就需要使用Cgroups对外提供的一些API来对Cgroups进行操作

在 Linux 中,Cgroups 通过 VFS 把API暴露给用户态的进程的。Cgroups 与 VFS 之间的衔接部分称之为 Cgroups 文件系统,即Cgroups以文件和目录的方式组织在操作系统的 /sys/fs/Cgroup 路径下。在 /sys/fs/Cgroup 下面有很多诸如 cpuset、cpu、 memory 这样的子目录,也就是Cgroup的各个子系统。

Tips: VFS 能够把具体文件系统的细节隐藏起来,给用户态进程提供一个统一的文件系统 API 接口。

在实际操作中需要通过mount操作、对目录层级操作以及在目录中编辑文件的方式来配置Cgroups,就是因为Cgroups的API是使用VFS来实现的。

Tips: 如果需要了解更多关于VFS的知识,可以阅读笔者总结的VFS相关知识点的文章

5.2 调用方式

使用 Cgroups 的方式有几种:

  1. 使用 Cgroups 提供的虚拟文件系统 因为Cgroups实际上是对Linux VFS的一个实现,因此可以用类似文件系统的方式进行操作。即直接通过创建、读写,删除目录以及挂载来文件目录来配置Cgroups。

  2. 使用命令行工具 比如libcgroup包提供的 cgcreate、cgexec、cgclassify 命令。

  3. 使用rules engine daemon提供的配置文件

  4. 使用第三方工具 systemd、lxc、Docker这些封装了 软件也封装了Cgroups的接口


6.实现细节

6.1 目录层级结构

Cgroups的相关文件一般会挂载到/sys/fs/cgroup/中。在/sys/fs/cgroup/中会进一步按照子系统来区分目录,即/sys/fs/cgroup/{subsystem}/

Tips: 这里的subsystem也可以是多个subsystem组合的子系统集合。即表示绑在该hierarchy上的subsystem有多个。

在子系统目录中会建立不同的Cgroups目录,即/sys/fs/cgroup/{subsystem}/{cgroup}。也就是说,子系统目录中内部的目录层级描述的就是hierarchy的结构

6.2 Cgroup通用文件

每个Cgroup目录下面都会有描述该Cgroup的文件。其中除了每个Cgroup独特的资源控制文件,还有一些通用的文件:

  1. tasks 当前Cgroup包含的任务(task)pid 列表。把某个进程的 pid 添加到这个文件中就等于把进程移到该 Cgroup。

  2. cgroup.procs 当前Cgroup中包含的thread group 列表,使用逻辑和 tasks 相同。

  3. notify_on_release 指是否在Cgroup销毁的时候执行发送通知操作,取值为0 或者 1。 如果为 1,那么当这个Cgroup最后一个任务离开时(退出或者迁移到其他Cgroup)并且最后一个子Cgroup被删除时系统会执行 release_agent 中指定的命令。可以理解为是触发了相关清理环境的hook。

  4. release_agent 即定义相关的hook,用于清理环境。

6.3 资源管理文件

每个subsystem负责系统的一部分资源管理,subsystem中还分别提供多个参数可以进行细节控制。其中每个参数对应一个文件,往文件中写入特定格式的内容就能控制该资源。

Tips: 注意这里并不是每一种subsystem对应一个资源管理文件。而是subsystem中的每一个配置参数对应一个文件,该文件中往往只有非常简短、简单的参数值。

6.4 相关操作

由于Cgroups是使用VFS实现的,因此对其操作实际上就是对文件系统的操作。

  1. 创建Cgroups 即系统挂载的Cgroups目录内,在相应的子系统目录内创建新目录即可。 例如:# mkdir /sys/fs/cgroup/cpu/mycgroup

  2. 删除Cgroups 直接删除系统挂载的Cgroups目录内,相应的子系统目录内指定目录即可。删除之后,如果 tasks 文件中有进程,它们会自动迁移到父 cgroup 中。

  3. 设定Cgroups参数 直接往特定的文件中写入特定格式的参数值即可。

  4. 将进程加入到 Cgroups 要把某个已经运行的进程加入到 Cgroups,可以直接往需要的 Cgroups tasks 文件中写入进程的 PID即可。 例如: # echo 23456 > /sys/fs/cgroup/memory/mycgroup/tasks

  5. 移动进程到其他Cgroups 操作方式依旧是把进程 PID 写入到目标 Cgroups tasks 文件中即可。此时原Cgroups的 tasks 文件会自动删除该进程。


7.Linux Cgroup V1版本与V2版本的区别

7.1 系统结构

由于Linux Cgroup V2版本相较于V1版本在整个系统结构上进行了一些大的改动。

1*P7ZLLF_F4TMgGfaJ2XIfuQ.png

7.1.1 V1版本

在V1版本中,Linux Cgroup是以资源类型为基础来进行管理的。每一个subsystem都是独立的,所有的资源限制都是基于subsystem来制定的。在每一种资源/subsystem内部,支持创建相应的cgroup;并将需要管理的进程加入到的这些控制组中,以来管理这些进程所涉及到的相应资源。

2019-06-12-cgroups-fig1.png

这种系统结构并不能很好的处理一些混合操作。 例如像Linux Buffered I/O这种既使用到I/O资源,也使用到了内存资源的操作。 此时就需要将同一个进程加到多个资源/subsystem的cgroup中才能实现相应的资源控制。对于这种涉及到多种类型资源的操作,V1版本不能很好的对这些使用了多种资源类型的进程进行资源限制管控。

7.1.2 V2版本

在V2版本中,Linux Cgroup更改为了以cgroup为基础来进行资源管控。一个进程属于一个cgroup,而每个cgroup里可以定义自己需要的多个subsystem。即将V1中的cgroup概念与资源概念在系统结构中进行了位置互换。这样的设计使得Linux Cgroup可以更方便、更直观的对每个cgroup所使用的所有类型的资源进行统一的管理

2019-06-12-cgroups-fig2.png

V2版本也是可以实现V1版本的相应功能,即在相应的cgroup中只启用单一资源的subsystem就可以模拟V1版本的系统结构了。