4. cgroup v1与v2概述

cgroup(Control Group)是 Linux 内核提供的一种机制,用于对进程组进行资源限制、优先级控制、审计和隔离。自 2008 年合并入内核主线以来,cgroup 经历了从 v1 到 v2 的重大演进。理解这两个版本的层次结构、子系统划分以及核心差异,是掌握组调度与 CPU 带宽控制的基础。

4.1 cgroup 的层次结构

cgroup 的核心设计围绕“树形层次结构”展开。每个 cgroup 代表一个任务组,可以包含零个或多个子 cgroup,形成一棵从根节点向下延伸的树。进程可以隶属于某个 cgroup 节点,并继承该节点的资源约束。

关键概念: 在 cgroup v1 中,每个子系统可以挂载到独立的树,形成多树并行的结构;而在 cgroup v2 中,所有子系统统一挂载到一棵统一的树,消除了多树带来的混乱。
cgroup 层次结构:v1 多树 vs v2 统一树 cgroup v1(多树) /sys/fs/cgroup/cpu 子组 A 子组 B /sys/fs/cgroup/memory 子组 X cgroup v2(统一树) /sys/fs/cgroup(统一根) 子组 A(cpu+mem) 子组 B(cpu+io) 孙组 C(全继承) 核心差异 v1:每个子系统独立挂载,进程可加入不同树的节点,管理复杂且易出现资源竞争。 v2:所有子系统统一挂载,进程只属于一个 cgroup,资源控制内聚且无内部竞争。

4.2 子系统(Subsystem)详解

子系统是 cgroup 中实际执行资源控制的模块。每个子系统负责一种特定的资源类型。下表列出了 v1 和 v2 中主要的子系统及其功能对比。

子系统名称 控制资源 v1 支持 v2 支持 关键接口文件(v2)
cpu CPU 时间分配与带宽限制 ✓ ✓ cpu.weight, cpu.max
memory 内存使用上限与回收策略 ✓ ✓ memory.max, memory.high
io 块设备 I/O 带宽与 IOPS ✓(blkio) ✓ io.max, io.weight
cpuset CPU 核与内存节点绑定 ✓ ✓ cpuset.cpus, cpuset.mems
pids 进程/线程数量上限 ✓ ✓ pids.max
net_cls 网络包分类标记 ✓ ✗ —
net_prio 网络优先级 ✓ ✗ —
hugetlb 大页内存使用限制 ✓ ✓ hugetlb.1GB.max
rdma RDMA 资源限制 ✓ ✓ rdma.max
misc 其他杂项资源(如 perf_event) 部分 ✓ misc.max
注意: cgroup v2 移除了 net_cls 和 net_prio 子系统,网络 QoS 控制改由 eBPF 和 TC 等更灵活的机制实现。同时,v2 将 blkio 重命名为 io,并统一了接口语义。

4.3 cgroup v1 与 v2 的核心差异

从 v1 到 v2 的演进不仅仅是接口的简化,更是设计哲学的根本转变。以下是两者在架构、接口、安全性和可维护性方面的关键差异。

  • 树结构: v1 允许多树并行,每个子系统独立挂载;v2 强制统一树,所有子系统共享一个层次结构。
  • 进程归属: v1 中一个进程可以属于不同树的不同 cgroup;v2 中一个进程只能属于一个 cgroup,所有资源控制在该节点内统一配置。
  • 线程粒度: v1 支持线程级迁移(threaded mode),但实现复杂;v2 默认以进程为单位,但通过 cgroup.threads 文件支持线程模式。
  • 接口一致性: v2 统一了资源限制文件的命名规范(如 .max、.weight、.high),降低了学习成本。
  • 内部竞争消除: v2 禁止在同一个 cgroup 内同时存在子 cgroup 和进程(即“内部进程竞争”),确保资源控制的可预测性。
  • 安全性: v2 强化了 delegation(委派)模型,非特权用户可以在自己的 cgroup 子树内进行资源管理,无需全局 root 权限。
cgroup v1 vs v2 核心差异对比 cgroup v1 多树并行,管理复杂 进程可跨树,资源竞争风险高 接口命名不统一,学习成本高 委派模型弱,需 root 操作 cgroup v2 统一树,结构清晰 进程单归属,无内部竞争 统一命名规范(.max/.weight) 强委派模型,非特权用户可管理

4.4 从 v1 迁移到 v2 的注意事项

当前主流发行版(如 RHEL 9、Ubuntu 22.04+)已默认使用 cgroup v2。如果系统仍运行 v1,迁移时需关注以下要点:

  1. 内核参数: 通过 systemd.unified_cgroup_hierarchy=1 启用 v2,或通过 cgroup_no_v1=all 禁用 v1 子系统。
  2. 兼容性: 部分旧版容器运行时(如 Docker < 20.10)可能不完全支持 v2,需升级。
  3. 监控工具: 基于 v1 接口的监控脚本(如读取 memory.usage_in_bytes)需要调整为 v2 接口(如 memory.current)。
  4. 混合模式: 内核支持 v1 和 v2 共存(hybrid mode),但建议生产环境统一使用 v2 以避免混淆。
警告: 在混合模式下,某些子系统可能同时出现在 v1 和 v2 树中,导致资源控制冲突。务必通过 mount | grep cgroup 确认当前挂载状态,避免配置覆盖。

4.5 实战:检查当前系统的 cgroup 版本

在开始配置之前,首先确认你的系统运行的是 cgroup v1 还是 v2。以下命令可以快速判断:

# 方法1:检查 /sys/fs/cgroup 的挂载类型
stat -fc %T /sys/fs/cgroup/
# 输出 "cgroup2fs" 表示 v2,输出 "tmpfs" 表示 v1

# 方法2:查看内核参数
cat /proc/cmdline | grep -o 'systemd.unified_cgroup_hierarchy=[01]'
# 1 表示 v2,0 表示 v1

# 方法3:检查 cgroup 版本文件
cat /sys/fs/cgroup/cgroup.controllers 2>/dev/null && echo "v2" || echo "v1"
# 如果文件存在且可读,则为 v2