cgroups 是Linux内核提供的一种可以限制单个进程或者多个进程所使用资源的机制,可以对 cpu,内存等资源实现精细化的控制,目前越来越火的轻量级容器 Docker 就使用了 cgroups 提供的资源限制能力来完成cpu,内存等部分的资源控制。
另外,开发者也可以使用 cgroups 提供的精细化控制能力,限制某一个或者某一组进程的资源使用。比如在一个既部署了前端 web 服务,也部署了后端计算模块的八核服务器上,可以使用 cgroups 限制 web server 仅可以使用其中的六个核,把剩下的两个核留给后端计算模块。
文章目录
cgroups子系统
cgroups 的全称是control groups,cgroups为每种可以控制的资源定义了一个子系统
。典型的子系统介绍如下:
- cpu 子系统,主要限制进程的 cpu 使用率。
- cpuacct 子系统,可以统计 cgroups 中的进程的 cpu 使用报告。
- cpuset 子系统,可以为 cgroups 中的进程分配单独的 cpu 节点或者内存节点。
- memory 子系统,可以限制进程的 memory 使用量。
- blkio 子系统,可以限制进程的块设备 io。
- devices 子系统,可以控制进程能够访问某些设备。
- net_cls 子系统,可以标记 cgroups 中进程的网络数据包,然后可以使用 tc 模块(traffic control)对数据包进行控制。
- freezer 子系统,可以挂起或者恢复 cgroups 中的进程。
- ns 子系统,可以使不同 cgroups 下面的进程使用不同的 namespace。
这里面每一个子系统都需要与内核的其他模块配合来完成资源的控制,比如对 cpu 资源的限制是通过进程调度模块根据 cpu 子系统的配置来完成的
;对内存资源的限制则是内存模块根据 memory 子系统的配置来完成的
,而对网络数据包的控制则需要 Traffic Control 子系统来配合完成
。
cgroups层级结构
内核使用 cgroup 结构体
来表示一个 control group 对某一个或者某几个
cgroups 子系统的资源限制(也就是一个cgroup层级下面可以 attach,或者说管理多个维度的 子系统)。
cgroup 结构体可以组织成一颗树的形式,每一棵cgroup 结构体组成的树称之为一个 cgroups 层级结构
。cgroups层级结构可以 attach 一个或者几个 cgroups 子系统,当前层级结构可以对其 attach 的 cgroups 子系统进行资源的限制。每一个 cgroups 子系统只能被 attach 到一个 cpu 层级结构中。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KXLxzLYv-1594865228400)(/Users/lvchentao/Desktop/cloudPoint/学习随笔/docker-张晋涛/b3270d03.png)]
比如上图表示两个cgroups层级结构,每一个层级结构中是一颗树形结构,树的每一个节点是一个 cgroup 结构体
(比如cpu_cgrp, memory_cgrp)。第一个 cgroups 层级结构 attach 了 cpu 子系统和 cpuacct 子系统
, 当前 cgroups 层级结构中的 cgroup 结构体就可以对 cpu 的资源进行限制,并且对进程的 cpu 使用情况进行统计。 第二个 cgroups 层级结构 attach 了 memory 子系统,当前 cgroups 层级结构中的 cgroup 结构体就可以对 memory 的资源进行限制。
在每一个 cgroups 层级结构中,每一个节点(cgroup 结构体)可以设置对资源不同的限制权重
。比如上图中 cgrp1 组中的进程可以使用60%的 cpu 时间片,而 cgrp2 组中的进程可以使用20%的 cpu 时间片。
每一个层级结构下,可以有多级的子目录,每个子目录继承上一级 cgroup的默认设置,但是可以重写一些设置,比如cpu所在的层级下有 pod1,pod2两个子目录,每个子目录可以单独设置自己的控制组信息,比如cpu60%,cpu20%这种,然后在每个子组下控制自己的tasks
一个已经附加在某个hierarchy之上的 subSystem,无法附加在其他的hierarchy之上
也就是cpu已经在某个路径下了,无法再附加在别的路径下
而某个task(进程)也不能在同一个hierarchy下的不同cgroup。很好理解,这是为了避免冲突,因为不同的cgroup下 可能对同一个资源的 限制都不同。
fork出来的子进程在初始状态和父进程处于同一个cgroup
所以:一个进程,可以加入不同的hierarchy下面的不同cgroup中,同样,一个cgroup可以包含多个task,所以进程和cgroup的关系是 多对多
上面这个图从整体结构上描述了进程与 cgroups 之间的关系。最下面的P
代表一个进程。每一个进程的描述符中有一个指针指向了一个辅助数据结构css_set
(cgroups subsystem set)。 指向某一个css_set
的进程会被加入到当前css_set
的进程链表中。一个进程只能隶属于一个css_set
,一个css_set
可以包含多个进程,隶属于同一css_set
的进程受到同一个css_set
所关联的资源限制。
上图中的”M×N Linkage”说明的是css_set
通过辅助数据结构可以与 cgroups 节点进行多对多的关联。但是 cgroups 的实现不允许css_set
同时关联同一个cgroups层级结构下多个节点(比如同时关联到图中的/cgrp1和/cgrp2)。 这是因为 cgroups 对同一种资源不允许有多个限制配置。
一个css_set
关联多个 cgroups 层级结构的节点时,表明需要对当前css_set
下的进程进行多种资源的控制。而一个 cgroups 节点关联多个css_set
时,表明多个css_set
下的进程列表受到同一份资源的相同限制。
图中的MxN Linkage 可以类比做 数据库中的多对多关系表?
每个css_set其实是一个 子系统的集合,通过这个集合和 进程建立了一对多的关系(正因为是子系统集合和进程是一对多关系,所以子系统和进程是 多对多关系)
每一个css_set 可以看做是不同的子系统的集合(对于那些使用了具体的某些子系统 资源限定的process来说,他们就属于 这些特定子系统组成的css_set)
cgroups文件系统
inux 使用了多种数据结构在内核中实现了 cgroups 的配置,关联了进程和 cgroups 节点,那么 Linux 又是如何让用户态的进程使用到 cgroups 的功能呢? Linux内核有一个很强大的模块叫 VFS (Virtual File System)。 VFS 能够把具体文件系统的细节隐藏起来,给用户态进程提供一个统一的文件系统 API 接口。 cgroups 也是通过 VFS 把功能暴露给用户态的,cgroups 与 VFS 之间的衔接部分称之为 cgroups 文件系统。下面先介绍一下 VFS 的基础知识,然后再介绍下 cgroups 文件系统的实现。
VFS
VFS 是一个内核抽象层,能够隐藏具体文件系统的实现细节,从而给用户态进程提供一套统一的 API 接口。VFS 使用了一种通用文件系统的设计,具体的文件系统只要实现了 VFS 的设计接口,就能够注册到 VFS 中,从而使内核可以读写这种文件系统。 这很像面向对象设计中的抽象类与子类之间的关系,抽象类负责对外接口的设计,子类负责具体的实现。其实,VFS本身就是用 c 语言实现的一套面向对象的接口。
可以这么猜测或者理解:
对于用户态进程来说,使用VFS提供的接口方法即可使用 操作文件系统的方式来写常规文件,socket文件或者操作cgroup文件,而这些文件实现的差异被内核屏蔽,他们内部通过不同的file_operations 去做不同的实现,这对于用户态进程无需关心。
通用文件模型
VFS 通用文件模型中包含以下四种元数据结构:
- 超级块对象(superblock obj