Linux Cgroup 详解:从 v1 到 v2 的演进与实践
cgroup(Control Groups) 是 Linux 内核的一项核心功能,用于精细化地管理和限制进程组使用的系统资源,如 CPU、内存、I/O 等。它是实现操作系统级虚拟化(如容器技术 Docker、Kubernetes)的关键基石。
随着 Linux 内核的发展,cgroup 经历了从 v1 到 v2 的重要演进。cgroup v2 旨在解决 v1 在设计和使用上的一些复杂性和不一致性。本文将深入探讨 cgroup 的基本概念,详细对比 v1 和 v2 的核心差异,并通过实例展示如何使用它们来控制资源。
1. 什么是 cgroup?
cgroup 主要提供以下能力:
- 资源限制(Resource Limiting):限制进程组可以使用的资源上限(例如,内存使用量、CPU 核心数)。
- 优先级控制(Prioritization):控制不同进程组对资源的访问优先级(例如,CPU 时间片分配、块 I/O 调度)。
- 资源审计(Accounting):统计进程组使用的资源量,用于监控和计费。
- 进程控制(Control):将进程分组管理,可以冻结(freeze)或恢复(thaw)组内所有进程。
主要应用场景:
- 容器技术:Docker、Kubernetes 等使用 cgroup 隔离和限制容器资源。
- 系统服务管理:Systemd 使用 cgroup 管理服务的资源。
- 性能调优:限制特定应用的资源消耗,保障关键服务性能。
- 虚拟化:配合 Namespace 等技术提供轻量级隔离环境。
2. 为何需要 cgroup v2?
cgroup v1 虽然功能强大,但在设计和使用上存在一些问题:
- 多层级结构混乱:每种资源控制器(子系统)可以有自己独立的层级树,导致进程可能属于多个不同的层级,管理复杂且容易出错。
- 控制器行为不一致:不同控制器之间的启用、禁用和管理方式存在差异。
- 接口不统一:控制文件的命名和功能缺乏一致性。
- 进程关联复杂:需要将进程 ID 写入每个相关控制器的
tasks
文件中。
cgroup v2 的设计目标就是解决这些问题,提供一个更统一、简洁、一致的资源控制框架。
3. cgroup v1 与 v2 的核心区别
3.1 层级结构(Hierarchy)
- cgroup v1:允许多个独立的层级结构。一个进程可以同时属于多个不同控制器的 cgroup 组。例如,一个进程可能在
cpu
控制器的/cpusetA
组,同时在memory
控制器的/memoryB
组。1
2
3
4
5# v1 示例挂载点
/sys/fs/cgroup/cpu/
/sys/fs/cgroup/memory/
/sys/fs/cgroup/blkio/
# ... 可能还有 cpuset, devices 等独立挂载 - cgroup v2:强制使用统一的层级结构。所有可用的控制器都挂载在同一个层级树下。一个进程只能属于一个 cgroup 组。这种统一结构极大地简化了管理,避免了 v1 中复杂的层级关系和潜在冲突。
1
2# v2 示例挂载点 (通常是 /sys/fs/cgroup)
/sys/fs/cgroup/
3.2 控制器管理(Controller Management)
- cgroup v1:控制器(子系统)在挂载时确定。不同的挂载点可以挂载不同的控制器组合。
- cgroup v2:控制器在层级内部进行管理。
- 根 cgroup (
/sys/fs/cgroup/
) 的cgroup.controllers
文件显示所有可用的控制器。 - 父 cgroup 的
cgroup.subtree_control
文件用于启用或禁用哪些控制器可以传递给其子 cgroup 使用。例如,向cgroup.subtree_control
写入+cpu +memory
表示允许子 cgroup 使用 CPU 和内存控制器。
- 根 cgroup (
3.3 进程关联(Process Association)
- cgroup v1:将进程 PID 写入特定控制器层级下的
tasks
文件,以将进程加入该 cgroup。如果需要同时受多个控制器管理,可能需要写入多个tasks
文件。 - cgroup v2:将进程 PID 写入目标 cgroup 目录下的
cgroup.procs
文件。由于是统一层级,只需写入一次即可。注意: 在 v2 中,只有叶子节点(没有子 cgroup 的节点)才能包含进程(除非是根 cgroup)。一个 cgroup 要么管理资源分配给子 cgroup(通过cgroup.subtree_control
),要么直接包含进程。
3.4 接口文件(Interface Files)
cgroup v2 努力统一和规范接口文件。
通用文件:
cgroup.procs
: 组内进程列表(PID)。cgroup.controllers
: 当前 cgroup 可用的控制器。cgroup.subtree_control
: 为子 cgroup 启用/禁用控制器。
资源控制文件对比:v2 的命名更规范,功能更聚合。
功能 (示例) cgroup v1 参数 (示例) cgroup v2 参数 (示例) 说明 CPU 带宽限制 cpu.cfs_quota_us
,cpu.cfs_period_us
cpu.max
v2 使用 <quota> <period>
格式,更直观。CPU 权重 cpu.shares
cpu.weight
v2 范围 1-10000,默认 100。v1 范围 2-262144,默认 1024。 内存上限 memory.limit_in_bytes
memory.max
v2 接口更简洁。 内存低水位 memory.low_limit_in_bytes
(部分内核支持)memory.low
内存回收保护。 内存高水位 无直接对应 memory.high
内存压力调节,超过此值会尝试回收。 当前内存使用 memory.usage_in_bytes
memory.current
I/O 带宽/IOPS 限制 blkio.throttle.read_bps_device
,blkio.throttle.write_iops_device
io.max
v2 统一接口,格式如 maj:min rbps=N wips=M
(读写带宽/IOPS)。
3.5 内部进程约束(Internal Process Constraint)
- cgroup v1:允许非叶子节点(即同时拥有子 cgroup 的节点)包含进程。
- cgroup v2:默认情况下,只有叶子节点才能包含进程。一个 cgroup 节点要么是资源分配的“分发者”(通过
cgroup.subtree_control
控制子节点可用资源),要么是资源的“使用者”(包含进程)。这使得资源分配模型更清晰。
4. 实践:限制 CPU 使用率
我们使用一个简单的 Go 程序来模拟 CPU 密集型任务,它会启动两个 goroutine 无限循环,尝试占满两个 CPU 核心。
1 |
|
编译并运行:
1 |
|
此时运行 top
命令,会看到 busyloop
进程的 CPU 使用率接近 200%。
1 |
|
现在,我们将使用 cgroup 将其 CPU 使用率限制在 10% (相当于 0.1 个 CPU 核心)。
4.1 使用 cgroup v1 限制 CPU
假设 cgroup v1 的 CPU 子系统挂载在 /sys/fs/cgroup/cpu
。
1 |
|
4.2 使用 cgroup v2 限制 CPU
假设 cgroup v2 挂载在 /sys/fs/cgroup
。
1 |
|
清理(示例结束后):
1 |
|
5. 如何检查系统使用的是 cgroup v1 还是 v2?
检查 cgroup 文件系统的挂载类型是区分 v1 和 v2 的最常用方法:
1 |
|
- cgroup v2: 如果输出中看到类似
cgroup2 on /sys/fs/cgroup type cgroup2
的行,表示系统主要使用 cgroup v2(通常是统一挂载)。 - cgroup v1: 如果看到多行
cgroup
挂载,每行对应一个或多个控制器(子系统),例如cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
和cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
,则表示系统主要使用 cgroup v1。 - 混合模式: 某些系统可能同时挂载了 v1 和 v2(例如,systemd 可能使用 v2,而 Docker 仍配置为使用 v1),需要仔细查看挂载点和类型。
另一个方法是检查 /sys/fs/cgroup
目录结构:
- 如果该目录下直接包含
cgroup.controllers
,cgroup.procs
等文件,并且有子目录用于组织 cgroup,则很可能是 cgroup v2。 - 如果该目录下包含
cpu
,memory
,blkio
等以控制器命名的子目录,则很可能是 cgroup v1。
6. 可视化对比
下图简要展示了 v1 和 v2 在层级结构上的差异:

(图示:左侧为 cgroup v1 的多层级结构,右侧为 cgroup v2 的统一层级结构)
7. 总结
Cgroup 是 Linux 资源管理的核心机制。从 v1 到 v2 的演进,体现了 Linux 内核在追求更统一、简洁、高效的资源控制模型方面的努力。Cgroup v2 以其统一的层级结构和更一致的接口,简化了资源管理配置,是现代容器化和系统管理的发展趋势。理解 cgroup v1 和 v2 的差异对于系统管理员和开发者进行性能调优、资源隔离和容器管理至关重要。