•
13 min read
Cgroup v2:Linux 资源控制机制详解

Cgroup v2:Linux 资源控制机制详解

一、Cgroup 是什么?

1.1 术语定义

术语定义
cgroup”control group” 的缩写,指单个控制组。本文中统一使用小写形式
cgroups复数形式,指多个独立的控制组或整个特性
controller控制器,负责分发特定类型的系统资源(如 CPU、内存、IO)

说明:本文主要介绍 cgroup v2。现代主流 Linux 发行版已默认启用 v2,通常通过内核参数 cgroup_no_v1=all 完全禁用 v1 控制器。

1.2 核心概念

cgroup 是 Linux 内核的一种资源管理机制,用于层级化地组织进程,并以可控、可配置的方式分配系统资源。

cgroup 由两个核心组件构成:

组件职责
Core负责层级化组织进程,维护 cgroup 树结构
Controller负责在层级结构中分配特定类型的系统资源

1.3 关键特性

树状结构

cgroup 构成树状层次结构,每个节点代表一个 cgroup。进程从属于某个 cgroup,cgroup 中包含 core 和 controller 的配置信息。

进程组织规则

在默认的 domain 模式下:

  • 一个进程的所有线程属于同一个 cgroup
  • 父进程 fork() 出的子进程自动继承父进程的 cgroup
  • 进程可以迁移,但必须携带其所有线程一同迁移

No Internal Process Constraint(无内部进程约束)

重要规则:如果一个 cgroup 下有子 cgroup,则该 cgroup 不能包含任何进程。

这意味着每个 cgroup 只能扮演一种角色:

  • 内部节点(父节点):仅用于组织子 cgroup,不存放进程
  • 叶子节点:存放进程,但不能有子 cgroup

此设计避免了父子节点进程之间的资源抢占冲突。

控制器(Controller)的层次化行为

  • 控制器可在任意 cgroup 上启用或禁用
  • 控制器的效果是层次化的:嵌套 cgroup 中启用控制器时,总是进一步限制资源
  • 靠近根节点的限制不能被子节点的设置覆盖

二、Cgroup v1 与 v2 的核心差异

2.1 架构差异

特性Cgroup v1Cgroup v2
层次结构每个 controller 可有独立的树全局单一树结构
进程归属一个进程可同时属于多个 controller 的不同组一个进程只能位于树中的一个位置
资源协调各 controller 独立管理,难以协调CPU、内存、IO 在同一位置统一管理

v1 的问题:由于每个 controller 有独立的树,一个进程的 PID 可能同时在 A_group 和 B_group 中,导致管理复杂。当需要跨控制器协调(如因 IO 压力降低 CPU 频率)时难以实现。

v2 的改进:全系统只有一棵树,进程只能出现在一个位置,所有资源在同一节点进行核算和记录。

2.2 控制器授权机制

v2 引入了显式的资源分发控制:子节点必须通过父节点的 cgroup.subtree_control 明确授权,才能使用对应的控制器。

# 查看根 cgroup 可用的控制器
❯ cat /sys/fs/cgroup/cgroup.subtree_control
cpuset cpu io memory hugetlb pids rdma misc

# 子 cgroup 默认不继承控制器,需要显式启用

2.3 新增特性

更精细化的内存管理

  • 内存控制器性能显著提升
  • 支持 PSI(Pressure Stall Information)监控
  • 引入 memory.reclaim 接口主动回收内存

PSI(压力停滞信息)

PSI 能够实时统计因 CPU、内存或 IO 不足导致进程停滞的时间比例,为资源调度决策提供数据支持。


三、基本操作

3.1 挂载 Cgroup v2

cgroup v2 使用单一层次结构:

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

# 或在 /etc/fstab 中配置
none /sys/fs/cgroup cgroup2 defaults 0 0

3.2 挂载选项

选项说明
nsdelegate将 cgroup 命名空间视为委托边界,减少容器对特权的依赖
favordynmods降低动态 cgroup 修改的延迟
memory_localevents仅在当前 cgroup 填充 memory.events
memory_recursiveprot递归应用内存保护到整个子树
memory_hugetlb_accounting将 HugeTLB 内存计入内存控制器
pids_localevents恢复 v1 风格的 pids.events:max 行为

3.3 nsdelegate 详解

nsdelegate 选项对容器安全具有重要意义:

nsdelegate: 将 cgroup 命名空间视为委托边界。该选项仅在系统级 init 命名空间挂载时设置。
在非 init 命名空间挂载中,该选项被忽略。

对容器的影响:

  1. 隔离边界:容器进程被限制在自己的 Cgroup Namespace 中
  2. 虚拟根节点:容器内 /sys/fs/cgroup 呈现为以容器自身为根的视图,而非宿主机的全局视图
  3. 权限委托:容器内的进程拥有在其”虚拟根”下创建子目录、分配资源的权限,无需宿主机特权干预

这有效减少了 Docker 等容器运行时对特权操作的依赖。


四、线程模式

4.1 设计背景

默认的 domain 模式要求同一进程的所有线程属于同一个 cgroup。然而某些场景(如 JVM)需要对单个进程内的不同线程进行差异化资源分配。

线程模式(threaded mode)应运而生,支持以线程粒度进行资源控制。

4.2 控制器分类

类型说明支持的控制器
domain controllers不支持线程模式,只能以进程为单位操作memory, io, hugetlb, rdma
threaded controllers支持线程模式,可对单个线程操作cpu, cpuset, perf_event, pids

4.3 启用线程模式

# 将 cgroup 标记为线程子树的根
echo threaded > cgroup.type

# 在父节点启用线程控制器
echo "+cpu +cpuset" > cgroup.subtree_control

4.4 线程模式特性

特性说明
线程分散同一进程的线程可以分布在子树的不同 cgroup 中
无内部进程约束豁免不受”无内部进程约束”限制
非叶节点控制器非叶子 cgroup 可以启用线程控制器

4.5 设计权衡

线程模式是一种折衷设计:

  • 统一管理内存等难以细粒度控制的资源
  • 在 CPU、IO 等支持细粒度控制的资源上提供灵活分配
  • 在保护系统性能的前提下提供可扩展的配置选项

五、关键接口文件

cgroup 文件系统通常挂载在 /sys/fs/cgroup,下文介绍核心接口文件。

5.1 核心控制文件

cgroup.type

读写单值文件,指示 cgroup 的当前类型:

值说明
domain普通有效域 cgroup(默认)
domain threaded作为线程子树根的线程域 cgroup
domain invalid处于无效状态的 cgroup
threaded线程子树的成员 cgroup

cgroup.procs

读写换行分隔的 PID 列表,列出属于该 cgroup 的所有进程。

写入条件:

  1. 对目标 cgroup.procs 有写权限
  2. 对源和目标 cgroup 的共同祖先的 cgroup.procs 有写权限

cgroup.threads

与 cgroup.procs 类似,但操作的是线程 ID (TID)。

约束:只能在同一资源域内移动线程。

cgroup.controllers

只读空格分隔文件,显示该 cgroup 可用的所有控制器。

cgroup.subtree_control

读写空格分隔文件,控制从该 cgroup 向其子节点分发哪些控制器。

# 启用控制器
echo "+cpu +memory" > cgroup.subtree_control

# 禁用控制器
echo "-io" > cgroup.subtree_control

cgroup.events

只读键值文件,包含状态信息:

条目说明
populatedcgroup 或其后代是否包含活动进程 (0/1)
frozencgroup 是否被冻结 (0/1)

5.2 进程控制文件

文件类型说明
cgroup.freeze读写写入 “1” 冻结 cgroup 及所有后代中的进程
cgroup.kill只写写入 “1” 终止 cgroup 及所有后代中的所有进程

5.3 统计信息文件

文件说明
cgroup.stat后代 cgroup 总数、死亡后代计数等
cgroup.max.descendants最大允许后代 cgroup 数量限制
cgroup.max.depth最大允许后代深度限制

5.4 PSI(压力停滞信息)

cpu.pressure / memory.pressure / io.pressure

显示因资源短缺导致的进程停滞时间。

输出格式:

some avg10=0.00 avg60=0.00 avg300=0.00 total=0
full avg10=0.00 avg60=0.00 avg300=0.00 total=0
指标含义
some至少有一个进程因等待资源而停滞的时间比例
full所有进程因等待资源而停滞的时间比例

诊断提示:如果 full 值较高,说明系统处于严重资源争用状态,所有进程都在等待资源,系统接近”假死”状态。

irq.pressure

显示硬中断/软中断处理带来的系统压力。

5.5 资源统计文件

memory.stat

详尽的内存使用明细,包括:

  • anonymous:匿名内存(堆、栈、mmap 私有段)
  • file:文件页缓存
  • kernel_stack:内核栈
  • sock:网络 socket 缓冲区
  • 等等

cpu.stat

累计 CPU 使用时间及被 throttled 的次数。

io.stat

每个磁盘设备的读写统计:

  • rbytes/wbytes:读/写字节数
  • rios/wios:读/写 IO 次数

memory.reclaim

主动触发内存回收:

# 尝试回收 1GB 内存
echo "1G" > memory.reclaim

六、实战示例

6.1 限制进程 CPU 使用

# 创建子 cgroup
mkdir /sys/fs/cgroup/limited

# 启用 CPU 控制器(一般情况下默认都会启用)
echo "+cpu" > /sys/fs/cgroup/cgroup.subtree_control

# 限制 CPU 使用为 50%(单核)
echo "50000 100000" > /sys/fs/cgroup/limited/cpu.max

# 将进程(PID 1234)加入
echo 1234 > /sys/fs/cgroup/limited/cgroup.procs

6.2 限制进程内存

# 创建子 cgroup
mkdir /sys/fs/cgroup/memory_limited

# 启用内存控制器(一般情况下默认都会启用)
echo "+memory" > /sys/fs/cgroup/cgroup.subtree_control

# 设置内存上限为 512MB
echo "512M" > /sys/fs/cgroup/memory_limited/memory.max

# 将进程加入
echo 1234 > /sys/fs/cgroup/memory_limited/cgroup.procs

# 查看内存使用情况
cat /sys/fs/cgroup/memory_limited/memory.stat

6.3 冻结进程

# 冻结 cgroup 中的所有进程
echo 1 > /sys/fs/cgroup/target/cgroup.freeze

# 解冻
echo 0 > /sys/fs/cgroup/target/cgroup.freeze

七、内核实现

7.1 核心数据结构关系

┌─────────────────────────────────────────────────────────────────────────┐
│                          核心数据结构关系                               │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│   task_struct (任务/线程)                                                │
│      │                                                                  │
│      └── cgroups → css_set                                             │
│                      │                                                  │
│         ┌────────────┼────────────┬───────────────┐                     │
│         ▼            ▼            ▼               ▼                     │
│    subsys[0]   subsys[1]    subsys[2]       tasks 链表                  │
│    (cpu_css)   (mem_css)    (io_css)       ├─ task_1                   │
│        │            │            │           ├─ task_2                   │
│        ▼            ▼            ▼           └─ task_3                   │
│   cpu_cgroup  mem_cgroup   io_cgroup                                    │
│        └────────────┼────────────┘                                      │
│                     ▼                                                 │
│                  cgroup                                              │
│                  (目录节点)                                            │
│                                                                          │
│   task_struct.cgroups → css_set.subsys[i] → CSS → cgroup              │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

关系说明:

  • task_struct.cgroups 指向一个 css_set
  • css_set.subsys[] 数组存储所有控制器的 CSS 指针
  • CSS (cgroup_subsys_state) 是所有控制器的基类
  • cgroup.subsys[] 数组存储该 cgroup 的所有控制器 CSS

7.2 cgroup_subsys_state(CSS)

cgroup_subsys_state 是所有控制器的”基类”,内存控制器、CPU 控制器、cpuset 控制器都是在此基础上衍生出来的,它们内部都会内嵌一个 CSS。

struct cgroup_subsys_state {
    /* PI: the cgroup that this css is attached to */
    struct cgroup *cgroup;

    /* PI: the cgroup subsystem that this css is attached to */
    struct cgroup_subsys *ss; // 指向不同控制器的指针

    /* reference count - access via css_[try]get() and css_put() */
    struct percpu_ref refcnt;

    /* siblings list anchored at the parent's ->children */
    struct list_head sibling;
    struct list_head children;

    /* flush target list anchored at cgrp->rstat_css_list */
    struct list_head rstat_css_node;

    /*
     * PI: Subsys-unique ID.  0 is unused and root is always 1.  The
     * matching css can be looked up using css_from_id().
     */
    int id;

    unsigned int flags;

    /*
     * Monotonically increasing unique serial number which defines a
     * uniform order among all csses.  It's guaranteed that all
     * ->children lists are in the ascending order of ->serial_nr and
     * used to allow interrupting and resuming iterations.
     */
    u64 serial_nr;

    /*
     * Incremented by online self and children.  Used to guarantee that
     * parents are not offlined before their children.
     */
    atomic_t online_cnt;

    /* percpu_ref killing and RCU release */
    struct work_struct destroy_work;
    struct rcu_work destroy_rwork;

    /*
     * PI: the parent css.  Placed here for cache proximity to following
     * fields of the containing structure.
     */
    struct cgroup_subsys_state *parent;
};

7.3 css_set(任务到 cgroup 的桥梁)

css_set 是 task 到 cgroup 之间的桥梁。它聚合了所有的控制器,一个 task 只对应一个 css_set,切换一次 css_set 就可以切换所有控制器——这就是 v2 相较于 v1 的提升。

css_set 字段分类:

// 1. 控制器聚合
struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
// 指向各控制器的 CSS,索引 = 控制器 ID

// 2. 任务管理
struct list_head tasks;      // 此 cset 中的所有任务
struct list_head mg_tasks;   // 正在迁移中的任务
struct list_head dying_tasks; // 正在退出的任务
int nr_tasks;                // 任务计数

// 3. cgroup 关联
struct list_head cgrp_links; // 指向关联的 cgroup
struct cgroup *dfl_cgrp;     // 默认 cgroup

// 4. 迁移相关
struct cgroup *mg_src_cgrp;  // 迁移源 cgroup
struct cgroup *mg_dst_cgrp;  // 迁移目标 cgroup
struct css_set *mg_dst_cset; // 目标 css_set

完整定义:

struct css_set {
    /*
     * Set of subsystem states, one for each subsystem. This array is
     * immutable after creation apart from the init_css_set during
     * subsystem registration (at boot time).
     */
    struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];

    /* reference count */
    refcount_t refcount;

    /*
     * For a domain cgroup, the following points to self.  If threaded,
     * to the matching cset of the nearest domain ancestor.  The
     * dom_cset provides access to the domain cgroup and its csses to
     * which domain level resource consumptions should be charged.
     */
    struct css_set *dom_cset;

    /* the default cgroup associated with this css_set */
    struct cgroup *dfl_cgrp;

    /* internal task count, protected by css_set_lock */
    int nr_tasks;

    /*
     * Lists running through all tasks using this cgroup group.
     * mg_tasks lists tasks which belong to this cset but are in the
     * process of being migrated out or in.  Protected by
     * css_set_lock, but, during migration, once tasks are moved to
     * mg_tasks, it can be read safely while holding cgroup_mutex.
     */
    struct list_head tasks;
    struct list_head mg_tasks;
    struct list_head dying_tasks;

    /* all css_task_iters currently walking this cset */
    struct list_head task_iters;

    /*
     * On the default hierarchy, ->subsys[ssid] may point to a css
     * attached to an ancestor instead of the cgroup this css_set is
     * associated with.  The following node is anchored at
     * ->subsys[ssid]->cgroup->e_csets[ssid] and provides a way to
     * iterate through all css's attached to a given cgroup.
     */
    struct list_head e_cset_node[CGROUP_SUBSYS_COUNT];

    /* all threaded csets whose ->dom_cset points to this cset */
    struct list_head threaded_csets;
    struct list_head threaded_csets_node;

    /*
     * List running through all cgroup groups in the same hash
     * slot. Protected by css_set_lock
     */
    struct hlist_node hlist;

    /*
     * List of cgrp_cset_links pointing at cgroups referenced from this
     * css_set.  Protected by css_set_lock.
     */
    struct list_head cgrp_links;

    /*
     * List of csets participating in the on-going migration either as
     * source or destination.  Protected by cgroup_mutex.
     */
    struct list_head mg_src_preload_node;
    struct list_head mg_dst_preload_node;
    struct list_head mg_node;

    /*
     * If this cset is acting as the source of migration the following
     * two fields are set.  mg_src_cgrp and mg_dst_cgrp are
     * respectively the source and destination cgroups of the on-going
     * migration.  mg_dst_cset is the destination cset the target tasks
     * on this cset should be migrated to.  Protected by cgroup_mutex.
     */
    struct cgroup *mg_src_cgrp;
    struct cgroup *mg_dst_cgrp;
    struct css_set *mg_dst_cset;

    /* dead and being drained, ignore for migration */
    bool dead;

    /* For RCU-protected deletion */
    struct rcu_head rcu_head;
};

7.4 cgroup(目录节点)

cgroup 结构体对应 /sys/fs/cgroup/ 下的一个目录。

cgroup 字段分类:

// 1. 层级结构
int level;                  // 深度,root = 0
struct cgroup *ancestors[]; // 所有祖先(柔性数组)
struct cgroup_root *root;   // 属于哪个层级根

// 2. 控制器管理
u16 subtree_control;        // 子节点可以启用哪些控制器
                          // 对应 cgroup.subtree_control 文件
struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
                          // 这个 cgroup 的每个控制器的 CSS

// 3. 任务统计
int nr_populated_csets;    // 有任务的 css_set 数量
                          // = 0 表示这个 cgroup 没有任务
struct list_head cset_links; // 关联的所有 css_set

// 4. 文件系统接口
struct kernfs_node *kn;     // kernfs 节点(目录)
struct cgroup_file procs_file;  // cgroup.procs 文件句柄
struct cgroup_file events_file; // cgroup.events 文件句柄

// 5. 统计信息
struct cgroup_base_stat bstat;      // 基本资源统计
struct cgroup_rstat_cpu *rstat_cpu; // per-CPU 递归统计
struct psi_group *psi;              // PSI (Pressure Stall Information)

// 6. 特性相关
struct cgroup_bpf bpf;              // eBPF 程序
struct cgroup_freezer_state freezer; // 冻结状态

完整定义:

struct cgroup {
    /* self css with NULL ->ss, points back to this cgroup */
    struct cgroup_subsys_state self;

    unsigned long flags;

    /*
     * The depth this cgroup is at.  The root is at depth zero and each
     * step down the hierarchy increments the level.  This along with
     * ancestors[] can determine whether a given cgroup is a
     * descendant of another without traversing the hierarchy.
     */
    int level;

    /* Maximum allowed descent tree depth */
    int max_depth;

    /*
     * Keep track of total numbers of visible and dying descent cgroups.
     * Dying cgroups are cgroups which were deleted by a user,
     * but are still existing because someone else is holding a reference.
     * max_descendants is a maximum allowed number of descent cgroups.
     */
    int nr_descendants;
    int nr_dying_descendants;
    int max_descendants;

    /*
     * Each non-empty css_set associated with this cgroup contributes
     * one to nr_populated_csets.  The counter is zero iff this cgroup
     * doesn't have any tasks.
     */
    int nr_populated_csets;
    int nr_populated_domain_children;
    int nr_populated_threaded_children;

    int nr_threaded_children;

    struct kernfs_node *kn;
    struct cgroup_file procs_file;
    struct cgroup_file events_file;

    struct cgroup_file psi_files[NR_PSI_RESOURCES];

    /*
     * The bitmask of subsystems enabled on the child cgroups.
     */
    u16 subtree_control;
    u16 subtree_ss_mask;

    /* Private pointers for each registered subsystem */
    struct cgroup_subsys_state __rcu *subsys[CGROUP_SUBSYS_COUNT];

    struct cgroup_root *root;

    struct list_head cset_links;
    struct list_head e_csets[CGROUP_SUBSYS_COUNT];

    struct cgroup *dom_cgrp;
    struct cgroup_rstat_cpu __percpu *rstat_cpu;
    struct list_head rstat_css_list;

    struct cgroup_base_stat last_bstat;
    struct cgroup_base_stat bstat;
    struct prev_cputime prev_cputime;

    struct list_head pidlists;
    struct mutex pidlist_mutex;

    wait_queue_head_t offline_waitq;
    struct work_struct release_agent_work;

    struct psi_group *psi;
    struct cgroup_bpf bpf;
    atomic_t congestion_count;
    struct cgroup_freezer_state freezer;

#ifdef CONFIG_BPF_SYSCALL
    struct bpf_local_storage __rcu  *bpf_cgrp_storage;
#endif

    /* All ancestors including self */
    struct cgroup *ancestors[];
};

7.5 进程迁移:从用户操作到内核代码

当你执行 echo 1234 > cgroup.procs 时,内核会执行以下调用链:

用户: echo 1234 > cgroup.procs
      ↓
kernfs_write()
      ↓
cgroup_procs_write()
      ↓
__cgroup_procs_write()
      ├─ cgroup_procs_write_start()   // 解析 PID,查找 task
      ├─ cgroup_attach_permissions()  // 权限检查
      ↓
cgroup_attach_task()
      ├─ cgroup_migrate_add_src()      // 找到源 css_set
      ├─ cgroup_migrate_prepare_dst()  // 准备目标 css_set
      ↓
cgroup_migrate()
      ├─ cgroup_migrate_add_task()     // 添加任务到迁移列表
      ↓
cgroup_migrate_execute()  ← 核心!

核心代码 (kernel/cgroup/cgroup.c:2517):

// 阶段1:调用各控制器的 can_attach 回调检查
for_each_subsys_mask(ss, ssid, mgctx->ss_mask) {
    if (ss->can_attach) {
        ret = ss->can_attach(tset);  // 回调!
        if (ret)
            goto out_cancel_attach;
    }
}

// 阶段2:移动任务(commit point)
list_for_each_entry(cset, &tset->src_csets, mg_node) {
    css_set_move_task(task, from_cset, to_cset, true);
    //                     ↑
    //        task->cgroups = to_cset  ← 就这一行改变任务所属!
}

// 阶段3:通知各控制器迁移完成
for_each_subsys_mask(ss, ssid, mgctx->ss_mask) {
    if (ss->attach) {
        ss->attach(tset);  // 回调!
    }
}

7.6 回调机制:以 pids 控制器为例

什么是回调函数?

普通函数调用:你直接调用函数

int result = add(1, 2);  // 你主动调用

回调函数:你把函数指针给别人,别人在合适的时候调用

// 你定义函数
bool my_check(struct task_struct *task) {
    return task->pid < 100;
}

// 把函数指针传给框架
framework.register_callback(my_check);

// 框架在适当时机调用你

pids 控制器的回调注册 (kernel/cgroup/pids.c:376):

struct cgroup_subsys pids_cgrp_subsys = {
    .css_alloc     = pids_css_alloc,
    .css_free      = pids_css_free,
    .can_attach    = pids_can_attach,      // ← 迁移前检查
    .cancel_attach = pids_cancel_attach,
    .can_fork      = pids_can_fork,        // ← fork 前检查
    .cancel_fork   = pids_cancel_fork,
    .release       = pids_release,
};

can_fork 回调实现 (kernel/cgroup/pids.c:238):

static int pids_can_fork(struct task_struct *task, struct css_set *cset)
{
    struct cgroup_subsys_state *css;
    struct pids_cgroup *pids;
    int err;

    css = task_css_check(current, pids_cgrp_id, true);
    pids = css_pids(css);

    // 尝试增加 PID 计数
    err = pids_try_charge(pids, 1);

    if (err) {
        // 超过 pids.max,拒绝 fork!
        pr_info("cgroup: fork rejected by pids controller in ");
        pr_cont_cgroup_path(css->cgroup);
        return -EAGAIN;  // 返回错误,fork 将失败
    }

    return 0;
}

框架调用回调 (kernel/cgroup/cgroup.c:6530):

int cgroup_can_fork(struct task_struct *child, ...)
{
    do_each_subsys_mask(ss, i, have_canfork_callback) {
        ret = ss->can_fork(child, kargs->cset);
        //    ↑
        //    通过函数指针调用!如果 ss 是 pids 控制器
        //    实际调用的是 pids_can_fork()
        if (ret)
            goto out_revert;
    }
}

实际效果:

# 设置 PID 限制
echo 100 > /sys/fs/cgroup/test/pids.max

# 创建 100 个进程
for i in $(seq 1 100); do sleep 100 & done

# 尝试创建第 101 个进程
sleep 100 &
bash: fork: Resource temporarily unavailable

# 查看内核日志
dmesg | tail
[...] cgroup: fork rejected by pids controller in /test

八、常见问题

8.1 容器技术基础

Docker 等容器技术主要依赖两个 Linux 内核特性:

特性作用
Namespace提供资源隔离(进程、网络、文件系统等)
Cgroup提供资源限制和监控

8.2 特权容器 vs 安全容器

类型特点
特权容器以特权模式运行,可访问宿主机设备,安全性较低
安全容器结合 Namespace、Cgroup、seccomp、AppArmor 等多重隔离,安全性更高

8.3 如何判断系统使用的是 cgroup v1 还是 v2?

# 检查 v2 是否挂载
mount | grep cgroup2

# 或检查文件系统类型
stat -f -c %T /sys/fs/cgroup

输出 cgroup2fs 表示使用 v2,tmpfs 表示使用 v1。


九、参考资料