Linux Cgroup 深度解析: 从原理到实践
一、引言: Cgroup 技术概述
Linux Control Group(Cgroup)是 Linux 内核的一项核心功能, 它允许系统管理员将进程组织成层次化的组, 并对这些组进行资源分配、限制和监控. 想象一下, 你是一个大型云平台的管理员, 需要确保数百个容器不会相互竞争资源而导致系统崩溃------Cgroup 就是解决这个问题的瑞士军刀
Cgroup 最早由 Google 的工程师 Paul Menage 和 Rohit Seth 在 2006 年提出, 并在 2007 年并入 Linux 2.6.24 内核主线. 经过十多年的发展, 它已成为容器化技术(如 Docker、Kubernetes)的基石, 也是现代云原生基础设施不可或缺的部分
二、核心概念解析
2.1 三大基础概念
让我们从一个简单类比开始: 把整个服务器看作一座大型公寓楼, Cgroup 就是为每个租户(进程组)分配和管理水电资源(CPU、内存等)的系统
Linux Kernel Cgroup Virtual Filesystem Cgroup Hierarchy 1 Cgroup Hierarchy 2 Cgroup Hierarchy N Subsystem: cpu Subsystem: memory Subsystem: blkio Subsystem: cpuset Subsystem: devices Cgroup A Cgroup B Task 1 Task 2 Task 3 Cgroup X Cgroup Y
表1: Cgroup 核心三要素对比
| 概念 | 作用 | 类比 | 关键特性 |
|---|---|---|---|
| 子系统(Subsystem) | 资源控制器 | 水电计量表 | 每种资源一个控制器 |
| 控制组(Cgroup) | 进程分组容器 | 公寓房间 | 层次化组织, 可嵌套 |
| 层级树(Hierarchy) | 组织框架 | 公寓楼结构 | 每个层级可挂载不同子系统 |
2.2 子系统的深度剖析
目前 Linux 内核支持的主要子系统包括:
- cpu: 控制 CPU 时间分配, 基于 CFS(完全公平调度器)
- cpuacct: CPU 使用统计, 自动生成报告
- cpuset: 限制进程在特定 CPU 核心和内存节点上运行
- memory: 内存使用限制和统计
- blkio: 块设备 I/O 访问控制
- devices: 设备访问权限控制
- freezer: 暂停/恢复进程组
- net_cls/net_prio: 网络流量分类和优先级
- hugetlb: 大页内存控制
- perf_event: 性能监控集成
- pids: 限制进程数量
- rdma: RDMA 资源控制
每个子系统都是独立的内核模块, 可以通过配置选择性地启用或禁用
三、实现机制与代码框架
3.1 整体架构设计
Cgroup 的架构设计体现了 Unix 的"一切皆文件"哲学. 它在内核中通过虚拟文件系统(VFS)接口暴露给用户空间, 用户可以通过文件操作来管理控制组
Kernel Space Subsystems User Space Cgroup VFS Interface Cgroup Core Framework Task Scheduler Memory Manager Block Layer CPU Subsystem Memory Subsystem Blkio Subsystem ...Other Subsystems Applications Systemd Docker Custom Tools
3.2 核心数据结构分析
让我们深入内核源码, 看看 Cgroup 的核心数据结构(基于 Linux 5.x 内核):
c
/* 核心数据结构定义在 include/linux/cgroup-defs.h 中 */
/* 控制组数据结构 */
struct cgroup {
// 自旋锁, 保护该结构体的并发访问
spinlock_t lock;
// 控制组在层级中的深度
int level;
// 父控制组指针
struct cgroup *parent;
// 子控制组链表
struct list_head children;
// 兄弟控制组链表
struct list_head sibling;
// 关联的 css_set 集合
struct list_head cset_links;
// 控制组 ID(唯一标识)
u64 id;
// 控制组名称
char name[CGROUP_NAME_MAX];
// 控制组根目录
struct kernfs_node *kn;
// 控制组根目录的 dentry
struct dentry *dentry;
// 控制组统计信息
struct cgroup_stats stats;
// 子系统状态数组
struct cgroup_subsys_state __rcu *subsys[CGROUP_SUBSYS_COUNT];
// 控制组文件系统根节点
struct cgroup_root *root;
// 控制组内进程列表
struct list_head procs;
// 引用计数
struct cgroup_subsys_state refcnt;
};
/* 子系统状态结构 */
struct cgroup_subsys_state {
// 所属的 cgroup
struct cgroup *cgroup;
// 对应的子系统
struct cgroup_subsys *ss;
// 引用计数
percpu_ref_t refcnt;
// 兄弟节点链表
struct list_head sibling;
// 工作队列, 用于异步销毁
struct work_struct destroy_work;
// RCU 回调
struct rcu_head rcu_head;
};
/* 子系统定义结构 */
struct cgroup_subsys {
// 子系统 ID
int id;
// 子系统名称
const char *name;
// 子系统层级结构中的根节点
struct cgroup_root *root;
// 子系统标志位
unsigned int flags;
// 关键回调函数指针
int (*css_online)(struct cgroup_subsys_state *css);
void (*css_offline)(struct cgroup_subsys_state *css);
struct cgroup_subsys_state *(*css_alloc)(struct cgroup_subsys_state *parent_css);
void (*css_free)(struct cgroup_subsys_state *css);
void (*css_reset)(struct cgroup_subsys_state *css);
// 是否可以附加任务
bool can_attach;
// 附加/分离任务时的回调
void (*attach)(struct cgroup_subsys_state *css,
struct cgroup_taskset *tset);
void (*fork)(struct task_struct *task);
void (*exit)(struct cgroup_subsys_state *css,
struct cgroup_subsys_state *old_css,
struct task_struct *task);
// 销毁时的回调
void (*destroy)(struct cgroup_subsys_state *css);
// 绑定到控制组的回调
int (*can_attach)(struct cgroup_subsys_state *css,
struct cgroup_taskset *tset);
// 取消绑定的回调
void (*cancel_attach)(struct cgroup_subsys_state *css,
struct cgroup_taskset *tset);
// 文件系统操作
struct cftype *cfts;
};
/* css_set: 进程与 cgroup 的关联集合 */
struct css_set {
// 引用计数
atomic_t refcount;
// 子系统状态指针数组
struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
// 哈希表节点, 用于快速查找
struct hlist_node hlist;
// 任务列表头
struct list_head tasks;
// 进程列表头
struct list_head mg_tasks;
// cgroup 链接列表
struct list_head cgrp_links;
// 垃圾回收工作
struct work_struct put_work;
};
contains 1 * implements 1 1 references * * belongs to 1 1 contains 1 * cgroup +spinlock_t lock +int level +cgroup* parent +list_head children +list_head sibling +list_head cset_links +u64 id +char name[CGROUP_NAME_MAX] +kernfs_node* kn +dentry* dentry +cgroup_stats stats +cgroup_subsys_state* subsys[CGROUP_SUBSYS_COUNT] +cgroup_root* root +list_head procs +cgroup_subsys_state refcnt cgroup_subsys_state +cgroup* cgroup +cgroup_subsys* ss +percpu_ref_t refcnt +list_head sibling +work_struct destroy_work +rcu_head rcu_head cgroup_subsys +int id +const char* name +cgroup_root* root +unsigned int flags +bool can_attach +cftype* cfts +css_online() +css_offline() +css_alloc() +css_free() +css_reset() +attach() +fork() +exit() +destroy() +can_attach() +cancel_attach() css_set +atomic_t refcount +cgroup_subsys_state* subsys[CGROUP_SUBSYS_COUNT] +hlist_node hlist +list_head tasks +list_head mg_tasks +list_head cgrp_links +work_struct put_work task_struct +pid_t pid +css_set* cgroups +list_head cg_list
3.3 关键机制详解
3.3.1 进程附加机制
当一个进程被附加到某个 Cgroup 时, 内核会执行以下操作:
- 权限检查: 验证当前用户是否有权限操作目标 Cgroup
- 资源限制检查: 确保附加不会违反现有的资源限制
- 状态迁移 : 更新进程的
css_set指向新的 Cgroup - 回调通知 : 调用相关子系统的
attach回调函数
c
/* 进程附加的核心逻辑(简化版本) */
static int cgroup_attach_task(struct cgroup *dst_cgrp,
struct task_struct *task,
bool threadgroup)
{
struct css_set *cset;
struct cgroup_subsys *ss;
int ssid;
/* 1. 获取当前进程的 css_set */
cset = task_css_set(task);
/* 2. 为每个子系统执行预附加检查 */
for_each_subsys(ss, ssid) {
struct cgroup_subsys_state *css = cgroup_css(dst_cgrp, ss);
if (ss->can_attach) {
int ret = ss->can_attach(css, &tset);
if (ret) {
/* 回滚之前的操作 */
goto out_cancel_attach;
}
}
}
/* 3. 更新进程的 css_set */
task_lock(task);
rcu_assign_pointer(task->cgroups, new_cset);
task_unlock(task);
/* 4. 调用子系统的附加回调 */
for_each_subsys(ss, ssid) {
if (ss->attach) {
ss->attach(css, &tset);
}
}
return 0;
out_cancel_attach:
/* 回滚: 调用子系的取消回调 */
for_each_subsys(ss, ssid) {
if (ss->cancel_attach) {
ss->cancel_attach(css, &tset);
}
}
return ret;
}
3.3.2 资源限制执行机制
以 memory 子系统为例, 当进程尝试分配内存时:
c
/* 内存分配时的 Cgroup 检查(简化) */
static bool mem_cgroup_charge(struct page *page, struct mm_struct *mm,
gfp_t gfp_mask)
{
struct mem_cgroup *memcg;
/* 获取当前进程的内存控制组 */
rcu_read_lock();
memcg = mem_cgroup_from_task(current);
rcu_read_unlock();
/* 检查内存使用是否超过限制 */
if (page_counter_try_charge(&memcg->memory, PAGE_SIZE, &counter)) {
/* 内存充足, 允许分配 */
page->mem_cgroup = memcg;
return true;
} else {
/* 内存不足, 触发回收或拒绝分配 */
if (!mem_cgroup_out_of_memory(memcg, gfp_mask, PAGE_SIZE)) {
return false;
}
/* 回收后重试 */
return mem_cgroup_charge(page, mm, gfp_mask);
}
}
四、Cgroup v1 vs v2 对比分析
Linux Cgroup 经历了两个主要版本的发展, 下面是详细对比:
表2: Cgroup v1 与 v2 核心差异对比
| 特性维度 | Cgroup v1 | Cgroup v2 | 演进意义 |
|---|---|---|---|
| 架构设计 | 多层级树, 每层级独立 | 单一统一层级树 | 简化管理, 避免冲突 |
| 资源分配 | 各子系统独立控制 | 统一资源分配接口 | 保证资源分配的公平性 |
| 内存控制 | 多种内存统计分离 | 统一内存统计和限制 | 更准确的内存资源管理 |
| CPU控制 | 多种CPU控制器并存 | 统一CPU权重和限制 | 简化配置, 减少混淆 |
| I/O控制 | blkio权重与限制分离 | 统一I/O权重模型 | 更一致的I/O优先级控制 |
| 进程管理 | 分散的进程控制 | 统一的进程控制接口 | 简化进程生命周期管理 |
| 文件系统接口 | 每个子系统独立目录 | 统一控制文件接口 | 更直观的配置方式 |
| 默认启用 | 需要手动配置 | 内核默认启用 | 降低使用门槛 |
| 向后兼容 | 完全独立 | 通过混合模式兼容v1 | 平滑过渡路径 |
| 生产就绪 | 广泛使用但存在缺陷 | 逐渐成为新标准 | 长期技术发展方向 |
Cgroup v2 Architecture Cgroup v1 Architecture Controller: cpu Unified Hierarchy Controller: memory Controller: io Cgroup /sys/fs/cgroup/group1 统一控制文件 Cgroup /sys/fs/cgroup/group2 统一控制文件 Cgroup /sys/fs/cgroup/group3 统一控制文件 CPU Subsystem Root Hierarchy Memory Subsystem Blkio Subsystem CPU Cgroup A CPU Cgroup B Memory Cgroup X Memory Cgroup Y Blkio Cgroup M Blkio Cgroup N
五、实践应用: 完整示例
5.1 基础环境准备
首先确认系统支持 Cgroup:
bash
# 检查内核 Cgroup 支持
$ grep CGROUP /boot/config-$(uname -r)
CONFIG_CGROUPS=y
CONFIG_CGROUP_CPUACCT=y
CONFIG_CGROUP_DEVICE=y
CONFIG_CGROUP_FREEZER=y
CONFIG_CGROUP_SCHED=y
CONFIG_CGROUP_PERF=y
CONFIG_CGROUP_BPF=y
# 查看已挂载的 Cgroup
$ mount | grep cgroup
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
5.2 手动创建和管理 Cgroup
bash
# 创建新的 Cgroup v2 层级
$ sudo mkdir -p /sys/fs/cgroup/example_group
# 查看自动生成的控制文件
$ ls /sys/fs/cgroup/example_group/
cgroup.controllers cgroup.events cgroup.freeze cgroup.max.depth
cgroup.max.descendants cgroup.procs cgroup.stat cgroup.subtree_control
cgroup.threads cpu.pressure io.pressure memory.pressure
# 启用内存和 CPU 控制器
$ echo "+memory +cpu" > /sys/fs/cgroup/example_group/cgroup.subtree_control
# 设置内存限制为 100MB
$ echo "100M" > /sys/fs/cgroup/example_group/memory.max
# 设置 CPU 权重为 500(默认是100)
$ echo "500" > /sys/fs/cgroup/example_group/cpu.weight
# 将当前 shell 进程添加到 Cgroup
$ echo $$ > /sys/fs/cgroup/example_group/cgroup.procs
# 验证进程是否在 Cgroup 中
$ cat /proc/$$/cgroup
0::/example_group
# 运行一个测试程序
$ stress-ng --cpu 4 --timeout 60s &
# 监控资源使用情况
$ cat /sys/fs/cgroup/example_group/memory.current
$ cat /sys/fs/cgroup/example_group/cpu.stat
5.3 C语言编程接口示例
c
/**
* Cgroup v2 编程接口示例
* 创建一个新的 Cgroup, 设置资源限制, 并运行子进程
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#define CGROUP_ROOT "/sys/fs/cgroup"
#define CGROUP_NAME "myapp"
// 创建 Cgroup 目录
static int create_cgroup(const char *cgroup_path) {
if (mkdir(cgroup_path, 0755) < 0 && errno != EEXIST) {
perror("mkdir cgroup");
return -1;
}
return 0;
}
// 写入 Cgroup 控制文件
static int write_cgroup_file(const char *cgroup_path,
const char *filename,
const char *value) {
char filepath[512];
int fd, ret;
snprintf(filepath, sizeof(filepath), "%s/%s", cgroup_path, filename);
fd = open(filepath, O_WRONLY);
if (fd < 0) {
perror("open cgroup file");
return -1;
}
ret = write(fd, value, strlen(value));
if (ret < 0) {
perror("write cgroup file");
close(fd);
return -1;
}
close(fd);
return 0;
}
// 将进程添加到 Cgroup
static int add_pid_to_cgroup(const char *cgroup_path, pid_t pid) {
char pid_str[32];
snprintf(pid_str, sizeof(pid_str), "%d", pid);
return write_cgroup_file(cgroup_path, "cgroup.procs", pid_str);
}
// 设置资源限制
static int setup_resource_limits(const char *cgroup_path) {
// 启用 memory 和 cpu 控制器
if (write_cgroup_file(cgroup_path, "cgroup.subtree_control", "+memory +cpu") < 0) {
return -1;
}
// 设置内存限制为 200MB
if (write_cgroup_file(cgroup_path, "memory.max", "209715200") < 0) {
return -1;
}
// 设置 CPU 权重
if (write_cgroup_file(cgroup_path, "cpu.weight", "300") < 0) {
return -1;
}
// 设置 CPU 最大使用率(每周期100ms中最多使用50ms)
if (write_cgroup_file(cgroup_path, "cpu.max", "50000 100000") < 0) {
return -1;
}
// 设置进程数限制
if (write_cgroup_file(cgroup_path, "pids.max", "100") < 0) {
return -1;
}
return 0;
}
int main(int argc, char *argv[]) {
char cgroup_path[512];
pid_t child_pid;
// 构建 Cgroup 路径
snprintf(cgroup_path, sizeof(cgroup_path), "%s/%s", CGROUP_ROOT, CGROUP_NAME);
printf("Creating cgroup at: %s\n", cgroup_path);
// 创建 Cgroup
if (create_cgroup(cgroup_path) < 0) {
fprintf(stderr, "Failed to create cgroup\n");
return 1;
}
// 设置资源限制
if (setup_resource_limits(cgroup_path) < 0) {
fprintf(stderr, "Failed to setup resource limits\n");
return 1;
}
// 创建子进程
child_pid = fork();
if (child_pid < 0) {
perror("fork");
return 1;
}
if (child_pid == 0) {
// 子进程: 将自己添加到 Cgroup
printf("Child process PID: %d\n", getpid());
if (add_pid_to_cgroup(cgroup_path, getpid()) < 0) {
fprintf(stderr, "Failed to add process to cgroup\n");
exit(1);
}
// 验证在 Cgroup 中
char cmd[128];
snprintf(cmd, sizeof(cmd), "cat /proc/%d/cgroup", getpid());
system(cmd);
// 执行实际工作负载(这里模拟一个 CPU 密集型任务)
printf("Starting workload in cgroup...\n");
unsigned long long i;
volatile double result = 0;
for (i = 0; i < 1000000000ULL; i++) {
result += 1.0 / (i + 1);
}
printf("Workload completed. Result: %f\n", result);
exit(0);
} else {
// 父进程: 监控资源使用
printf("Parent process monitoring child PID: %d\n", child_pid);
// 等待子进程结束
int status;
waitpid(child_pid, &status, 0);
// 打印资源使用统计
printf("\nResource usage statistics:\n");
char stat_file[512];
FILE *fp;
char buffer[256];
// 读取内存使用
snprintf(stat_file, sizeof(stat_file), "%s/memory.stat", cgroup_path);
fp = fopen(stat_file, "r");
if (fp) {
printf("Memory statistics:\n");
while (fgets(buffer, sizeof(buffer), fp)) {
printf(" %s", buffer);
}
fclose(fp);
}
// 读取 CPU 使用
snprintf(stat_file, sizeof(stat_file), "%s/cpu.stat", cgroup_path);
fp = fopen(stat_file, "r");
if (fp) {
printf("\nCPU statistics:\n");
while (fgets(buffer, sizeof(buffer), fp)) {
printf(" %s", buffer);
}
fclose(fp);
}
// 清理: 移除 Cgroup(需要先移到根 Cgroup)
printf("\nCleaning up cgroup...\n");
// 将进程移回根 Cgroup
write_cgroup_file(CGROUP_ROOT, "cgroup.procs", "1");
// 删除 Cgroup 目录
if (rmdir(cgroup_path) < 0) {
perror("rmdir cgroup");
} else {
printf("Cgroup removed successfully\n");
}
}
return 0;
}
5.4 编译和运行
bash
# 编译程序
$ gcc -o cgroup_demo cgroup_demo.c -Wall
# 需要 root 权限运行
$ sudo ./cgroup_demo
# 或者使用 capability
$ sudo setcap cap_dac_override,cap_sys_admin+ep cgroup_demo
$ ./cgroup_demo
六、高级特性与生产实践
6.1 压力失速信息(Pressure Stall Information)
Cgroup v2 引入了 PSI 机制, 可以监控资源压力:
bash
# 查看内存压力
$ cat /sys/fs/cgroup/memory.pressure
# 查看 CPU 压力
$ cat /sys/fs/cgroup/cpu.pressure
# 查看 IO 压力
$ cat /sys/fs/cgroup/io.pressure
# 配置 PSI 监控阈值(示例: 每2秒采样, 当60秒内CPU等待时间超过80%时触发)
$ echo "some 80000 2000000" > /sys/fs/cgroup/cpu.pressure
6.2 委托控制(Delegation)
Cgroup v2 支持控制权委托, 这在多用户环境中非常有用:
bash
# 查看可委托的控制器
$ cat /sys/fs/cgroup/cgroup.controllers
# 查看当前已启用的控制器
$ cat /sys/fs/cgroup/cgroup.subtree_control
# 将内存控制器委托给子 Cgroup
$ echo "+memory" > /sys/fs/cgroup/cgroup.subtree_control
# 在子 Cgroup 中创建孙 Cgroup 并控制内存
$ mkdir /sys/fs/cgroup/child
$ cd /sys/fs/cgroup/child
$ echo "+memory" > cgroup.subtree_control
$ mkdir grandchild
6.3 Cgroup 与 Namespace 的协同
User Space Kernel Cgroup Namespace clone() with CLONE_NEW* flags Create new namespaces Check cgroup permissions Permission granted Return new PID Move process to cgroup Update process cgroup association Apply namespace isolation Apply resource limits Track resource usage Enforce limits if exceeded Generate events/notifications loop [Resource Monitoring] Process exits Release resources Cleanup namespace Update statistics User Space Kernel Cgroup Namespace
七、调试与监控工具
7.1 命令行工具集
表3: Cgroup 相关命令行工具
| 工具名称 | 主要功能 | 安装方式 | 使用示例 |
|---|---|---|---|
| systemd-cgls | 显示 Cgroup 层次结构 | systemd 自带 | systemd-cgls |
| systemd-cgtop | 实时监控 Cgroup 资源使用 | systemd 自带 | systemd-cgtop |
| cgcreate | 创建 Cgroup | libcgroup-tools | cgcreate -g cpu,memory:mygroup |
| cgset | 设置 Cgroup 参数 | libcgroup-tools | cgset -r cpu.shares=512 mygroup |
| cgexec | 在指定 Cgroup 中运行程序 | libcgroup-tools | cgexec -g cpu:mygroup ./app |
| lssubsys | 列出可用子系统 | libcgroup-tools | lssubsys -am |
| cat /proc/... | 查看进程 Cgroup 信息 | 内核接口 | cat /proc/$$/cgroup |
| bpftrace | 动态跟踪 Cgroup 事件 | bpftrace 包 | bpftrace -e 'tracepoint:cgroup:* { ... }' |
7.2 系统监控与调试
bash
# 1. 查看所有 Cgroup 层次结构
$ find /sys/fs/cgroup -type d | sort
# 2. 查看特定进程的 Cgroup 信息
$ cat /proc/<PID>/cgroup
$ ps xawf -eo pid,user,cgroup,args
# 3. 实时监控 Cgroup 事件
$ sudo bpftrace -e '
tracepoint:cgroup:cgroup_mkdir {
printf("Cgroup created: %s\n", str(args->cgroup->kn->name));
}
tracepoint:cgroup:cgroup_rmdir {
printf("Cgroup removed: %s\n", str(args->cgroup->kn->name));
}
tracepoint:cgroup:cgroup_attach_task {
printf("Task %d attached to %s\n", args->pid,
str(args->dst_cgrp->kn->name));
}
'
# 4. 使用 perf 跟踪 Cgroup 相关系统调用
$ sudo perf record -e cgroup:* -a sleep 10
$ sudo perf script
# 5. 调试内存 Cgroup
$ echo 1 > /sys/kernel/debug/cgroup/memory/memory.stat
$ cat /sys/kernel/debug/cgroup/memory/memory.stat
# 6. 使用 ftrace 跟踪 Cgroup 内部函数
$ echo function > /sys/kernel/debug/tracing/current_tracer
$ echo cgroup_* > /sys/kernel/debug/tracing/set_ftrace_filter
$ echo 1 > /sys/kernel/debug/tracing/tracing_on
$ cat /sys/kernel/debug/tracing/trace
7.3 常见问题诊断
bash
# 问题1: 无法创建 Cgroup
$ dmesg | grep -i cgroup
$ cat /proc/cgroups # 检查子系统是否启用
# 问题2: 资源限制不生效
$ cat /sys/fs/cgroup/<cgroup>/cgroup.controllers # 检查控制器是否启用
$ cat /sys/fs/cgroup/<cgroup>/<controller>.max # 检查限制值
# 问题3: 进程无法加入 Cgroup
$ ls -l /sys/fs/cgroup/<cgroup>/cgroup.procs # 检查文件权限
$ cat /sys/fs/cgroup/<cgroup>/cgroup.threads # 检查线程信息
# 问题4: Cgroup 无法删除
$ lsof +D /sys/fs/cgroup/<cgroup> # 检查是否有进程在使用
$ fuser /sys/fs/cgroup/<cgroup> # 查看使用进程
八、生产环境最佳实践
8.1 配置建议
表4: 生产环境 Cgroup 配置策略
| 应用类型 | CPU 策略 | 内存策略 | I/O 策略 | 特殊考虑 |
|---|---|---|---|---|
| Web 服务器 | 权重分配, 预留突发余量 | 限制+交换空间 | 中等优先级 | 监控并发连接数 |
| 数据库 | 固定份额, 避免争抢 | 硬限制, 无交换 | 高优先级 | 大页内存支持 |
| 批处理作业 | 限制最大使用率 | 按需分配 | 低优先级 | 考虑 freezer 控制 |
| 实时应用 | 实时调度类 | 锁定内存 | 确保带宽 | CPU 亲和性设置 |
| 容器平台 | 配额+周期控制 | 层次化限制 | 权重分配 | 命名空间集成 |
8.2 性能调优
bash
# CPU 调优示例
# 1. 设置 CPU 配额(每100ms周期内最多使用50ms)
$ echo "50000 100000" > /sys/fs/cgroup/myapp/cpu.max
# 2. 设置 CPU 权重(相对权重, 默认100)
$ echo "300" > /sys/fs/cgroup/myapp/cpu.weight
# 3. 设置 CPU 亲和性(如果使用 cpuset 子系统)
$ echo "0-3" > /sys/fs/cgroup/myapp/cpuset.cpus
$ echo "0" > /sys/fs/cgroup/myapp/cpuset.mems
# 内存调优示例
# 1. 设置内存硬限制
$ echo "1G" > /sys/fs/cgroup/myapp/memory.max
# 2. 设置内存+交换空间总限制
$ echo "2G" > /sys/fs/cgroup/myapp/memory.swap.max
# 3. 设置内存软限制(触发回收的阈值)
$ echo "900M" > /sys/fs/cgroup/myapp/memory.high
# 4. 设置最小内存保证
$ echo "500M" > /sys/fs/cgroup/myapp/memory.min
# I/O 调优示例
# 1. 设置权重(100-1000, 默认100)
$ echo "500" > /sys/fs/cgroup/myapp/io.weight
# 2. 设置最大带宽(字节/秒)
$ echo "major:minor wbps=10485760" > /sys/fs/cgroup/myapp/io.max
# 3. 设置最大 IOPS
$ echo "major:minor riops=1000 wiops=1000" > /sys/fs/cgroup/myapp/io.max
九、总结
经过对 Linux Cgroup 技术的深入剖析, 我们可以得出以下核心结论:
-
架构演进: Cgroup 从 v1 的多层次独立控制发展到 v2 的单一统一层次结构, 极大地简化了资源管理模型
-
核心机制: 通过虚拟文件系统接口、层次化控制组、资源控制器三大支柱, Cgroup 实现了对系统资源的精细控制
-
生产价值: 作为容器技术的基石, Cgroup 使得现代云原生应用能够高效、安全地共享物理资源, 是实现多租户隔离的关键技术
-
生态系统: Cgroup 已深度融入 Linux 生态系统, 与 systemd、容器运行时、编排平台等紧密集成