---
title: cgroup
url: https://doc.liz6.com/linux-kernel/12-containers-and-primitives/02-cgroup
locale: zh
area: linux-kernel
tags:
- linux-kernel
- 容器与原语
date: 2026-06-30
modified: 2026-07-11
description: '覆盖: cgroup v2 统一层级 → 控制器 (cpu/memory/io/pids/cpuset) → cgroupfs → memory reclaim → PSI → cgroup 编程模型 内核版本: 2.6 (v1) ~ 6.x (v2)'
---

# cgroup

> 覆盖: cgroup v2 统一层级 → 控制器 (cpu/memory/io/pids/cpuset) → cgroupfs → memory reclaim → PSI → cgroup 编程模型
> 内核版本: 2.6 (v1) ~ 6.x (v2)

## 概述

cgroup (control group) 提供进程的资源使用限制、优先级控制和统计。v2 统一了 v1 的多个独立层级为一棵树，解决了控制器之间无法协调的根本问题。

## cgroup v2 vs v1

```
cgroup v1:
  每个控制器有独立的层级 → CPU 和 memory 的控制树可能不同
  → 无法表达"在这个 cgroup 中 CPU 和 memory 的上限是一起作用的"

cgroup v2 (4.5+, 当前默认):
  单一统一层级 → 所有控制器共享同一棵树
  → 在同一个 cgroup 目录下设置所有控制器的参数
  → 控制器之间的协调变得可能 (如 memory pressure → IO throttling)
```

## cgroupfs 接口

```bash
# 挂载
mount -t cgroup2 none /sys/fs/cgroup

# 创建 cgroup
mkdir /sys/fs/cgroup/mygroup
# → 自动继承父级的控制器设置

# 移动进程
echo $PID > /sys/fs/cgroup/mygroup/cgroup.procs

# 查看进程所属 cgroup
cat /proc/$PID/cgroup
```

## 控制器详解

### cpu

```bash
# 权重 (调度优先级, 默认 100):
echo 200 > /sys/fs/cgroup/mygroup/cpu.weight   # 2x CPU

# 带宽限制:
echo "50000 100000" > cpu.max  # 50% of one CPU (50ms per 100ms)
```

### memory

```bash
echo 1G > memory.max       # 硬上限 (超出 → OOM kill)
echo 800M > memory.high     # 软上限 (超出 → throttle + reclaim)
echo 100M > memory.low       # 最佳努力保证 (内存紧张时尽量保留)

# 内存压力通知 (PSI):
cat memory.pressure  # some/full 的 10s/60s/300s 平均
```

### io

```bash
# 权重:
echo "8:0 200" > io.weight  # 设备 8:0 权重 200

# 带宽限制:
echo "8:0 rbps=1048576 wbps=2097152" > io.max
```

### pids

```bash
echo 100 > pids.max        # 此 cgroup 最多 100 个进程
echo pids.max              # 超出 → fork() 失败 (EAGAIN)
```

### cpuset

```bash
# 钉选 CPU/内存节点:
echo "0-3" > cpuset.cpus          # 只能用 CPU 0-3
echo "0" > cpuset.mems            # 只能用 NUMA node 0 的内存
```

---

## Memory Reclaim 深入

> `mm/memcontrol.c` — cgroup memory controller 的内核路径:

<svg viewBox="0 0 720 400" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="try_charge 内存计费决策流程:依次检查 memory.max 与 memory.high,触发拒绝、reclaim 或放行">
  <defs>
    <marker id="cgArrow" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker>
  </defs>
  <rect width="720" height="400" fill="#ffffff"/>
  <text x="360" y="26" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">try_charge() 内核路径:分配时的两级检查</text>

  <rect x="230" y="44" width="260" height="32" rx="7" fill="#4f46e5"/>
  <text x="360" y="64" text-anchor="middle" font-size="12.5" font-weight="700" fill="#ffffff">try_charge(memcg, gfp, nr_pages)</text>
  <line x1="360" y1="76" x2="360" y2="90" stroke="#475569" stroke-width="1.6" marker-end="url(#cgArrow)"/>

  <rect x="230" y="92" width="260" height="38" rx="7" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="360" y="116" text-anchor="middle" font-size="12" font-weight="600" fill="#3730a3">usage + nr_pages &gt; memory.max ?</text>
  <line x1="360" y1="130" x2="360" y2="144" stroke="#475569" stroke-width="1.6" marker-end="url(#cgArrow)"/>
  <text x="376" y="141" font-size="10.5" fill="#64748b">否</text>

  <rect x="230" y="146" width="260" height="38" rx="7" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="360" y="170" text-anchor="middle" font-size="12" font-weight="600" fill="#3730a3">usage + nr_pages &gt; memory.high ?</text>
  <line x1="360" y1="184" x2="360" y2="198" stroke="#475569" stroke-width="1.6" marker-end="url(#cgArrow)"/>
  <text x="376" y="195" font-size="10.5" fill="#64748b">否</text>

  <rect x="230" y="200" width="260" height="34" rx="7" fill="#dcfce7" stroke="#4ade80"/>
  <text x="360" y="222" text-anchor="middle" font-size="12" font-weight="700" fill="#166534">OK → memcg-&gt;memory-&gt;usage += nr_pages</text>

  <line x1="490" y1="111" x2="556" y2="111" stroke="#475569" stroke-width="1.6" marker-end="url(#cgArrow)"/>
  <text x="500" y="105" font-size="10.5" fill="#64748b">是</text>
  <rect x="560" y="92" width="130" height="38" rx="7" fill="#ffffff" stroke="#ef4444" stroke-width="1.4"/>
  <text x="625" y="110" text-anchor="middle" font-size="11.5" font-weight="700" fill="#dc2626">拒绝</text>
  <text x="625" y="124" text-anchor="middle" font-size="10" fill="#dc2626">ENOMEM / OOM</text>

  <line x1="490" y1="165" x2="556" y2="165" stroke="#475569" stroke-width="1.6" marker-end="url(#cgArrow)"/>
  <text x="500" y="159" font-size="10.5" fill="#64748b">是</text>
  <rect x="560" y="146" width="130" height="38" rx="7" fill="#ffedd5"/>
  <text x="625" y="169" text-anchor="middle" font-size="11" font-weight="600" fill="#9a3412">触发 memcg reclaim</text>

  <line x1="625" y1="184" x2="620" y2="196" stroke="#475569" stroke-width="1.6" marker-end="url(#cgArrow)"/>
  <rect x="520" y="198" width="200" height="32" rx="7" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="620" y="218" text-anchor="middle" font-size="11" fill="#115e59">try_to_free_mem_cgroup_pages()</text>

  <line x1="620" y1="230" x2="620" y2="242" stroke="#475569" stroke-width="1.6" marker-end="url(#cgArrow)"/>
  <rect x="500" y="244" width="240" height="32" rx="7" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="620" y="264" text-anchor="middle" font-size="11" fill="#0f766e">shrink_lruvec() → 扫描 LRU → 回收</text>

  <rect x="40" y="298" width="640" height="82" rx="8" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="56" y="320" font-size="12.5" fill="#115e59">reclaim 优先级(usage + nr_pages &gt; memory.high 触发时):</text>
  <text x="56" y="342" font-size="12.5" fill="#115e59">① 回收此 cgroup 无用 cache(page cache / slab) → ② 不够则 swap 此 cgroup 的匿名页</text>
  <text x="56" y="364" font-size="12.5" fill="#115e59">→ ③ 仍不够且 memcg-&gt;oom_group → kill 整个 cgroup</text>
</svg>

---

## PSI (Pressure Stall Information)

```bash
# /proc/pressure/ — 三级压力指标:
cat /proc/pressure/cpu     # some/total 百分比
cat /proc/pressure/memory  # some/full
cat /proc/pressure/io      # some/full

# some: 至少一个 task 在等待 → 资源竞争
# full: 所有非 idle task 都在等待 → 资源完全饱和
```

---

## 编程接口 (内核侧)

```c
// include/linux/cgroup.h
// 内核中 controller 的实现:

struct cgroup_subsys {
    struct cgroup_subsys_state *(*css_alloc)(struct cgroup_subsys_state *parent);
    int (*css_online)(struct cgroup_subsys_state *css);
    void (*css_free)(struct cgroup_subsys_state *css);
    // ...
};

// memcg: css = mem_cgroup (每个 cgroup 一个)
// blkio: css 挂载在 request_queue 上
```

## 参考

- **源码**: `kernel/cgroup/cgroup.c` (核心框架), `mm/memcontrol.c` (memory), `block/blk-cgroup.c` (io), `kernel/sched/core.c` (cpu)
- **内核文档**: `Documentation/admin-guide/cgroup-v2.rst` (极好)
- **LWN**: "cgroup v2", "Memory control group design"

*关键词: cgroup v2, controllers, cpu.weight, memory.max, io.max, PSI, memcg reclaim, cgroupfs*
