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。


九、参考资料