控制组 - Arch Linux 中文维基

跳转到内容

# 控制组

来自 Arch Linux 中文维基

相关文章

- Linux Containers
- systemd-nspawn
- Docker
- limits.conf

翻译状态：

- 本文（或部分内容）译自 Cgroups，最近一次同步于 2026-01-20，若英文版本有所更改，则您可以帮助同步与翻译更改的内容。
- 您可以在 ArchWiki 的对应页面找到本文翻译的原始修订历史。
- 本文可能与英文原文存在出入。

控制组 (或者通常被简写为 cgroups) 是一项 Linux 内核提供的特性， 用于管理，限制和审核一组进程。Cgroups 可以操作一个进程的集合或者子集（例如，集合中由不同用户启动的进程），这使得 cgroups 和其它类似工具，如 nice(1) 命令和 `/etc/security/limits.conf` 相比更为灵活。

可以使用以下方式使用控制组：

- 在 systemd 单元文件中使用指令来制定服务和切片的限制；
- 通过直接访问 `cgroup` 文件系统；
- 通过 `cgcreate`，`cgexec` 和 `cgclassify` （libcgroupAUR 和 libcgroup-gitAUR 包的一部分） 等工具;
- 使用“规则应用守护程序”来自动移动特定的用户/组/命令到另一个组中（`/etc/cgrules.conf` 和 `cgconfig.service`）（libcgroupAUR 和 libcgroup-gitAUR 包的一部分）；以及
- 通过其它软件例如 Linux 容器 (LXC) 虚拟化。

对于 Arch Linux 来说，systemd 是首选的也是最简单的调用和配置 cgroups 的方法，因为它是默认安装的一部分。

## 安装

确保你已经安装了这些用于自动处理 cgroups 的包中的至少一个：

- systemd包——用于控制 systemd 服务的资源使用。
- libcgroupAUR，libcgroup-gitAUR——一系列独立的工具（`cgcreate`, `cgclassify`，通过 `cgconfig.conf` 实现可持久化配置）。

## 和 Systemd 一同使用

### 层级

现有的 cgroup 层级可以通过 `systemctl status` 或者 `systemd-cgls` 命令查看。

    $ systemctl status

    ● myarchlinux
        State: running
         Jobs: 0 queued
       Failed: 0 units
        Since: Wed 2019-12-04 22:16:28 UTC; 1 day 4h ago
       CGroup: /
               ├─user.slice
               │ └─user-1000.slice
               │   ├─user@1000.service
               │   │ ├─gnome-shell-wayland.service
               │   │ │ ├─ 1129 /usr/bin/gnome-shell
               │   │ ├─gnome-terminal-server.service
               │   │ │ ├─33519 /usr/lib/gnome-terminal-server
               │   │ │ ├─37298 fish
               │   │ │ └─39239 systemctl status
               │   │ ├─init.scope
               │   │ │ ├─1066 /usr/lib/systemd/systemd --user
               │   │ │ └─1067 (sd-pam)
               │   └─session-2.scope
               │     ├─1053 gdm-session-worker [pam/gdm-password]
               │     ├─1078 /usr/bin/gnome-keyring-daemon --daemonize --login
               │     ├─1082 /usr/lib/gdm-wayland-session /usr/bin/gnome-session
               │     ├─1086 /usr/lib/gnome-session-binary
               │     └─3514 /usr/bin/ssh-agent -D -a /run/user/1000/keyring/.ssh
               ├─init.scope
               │ └─1 /sbin/init
               └─system.slice
                 ├─systemd-udevd.service
                 │ └─285 /usr/lib/systemd/systemd-udevd
                 ├─systemd-journald.service
                 │ └─272 /usr/lib/systemd/systemd-journald
                 ├─NetworkManager.service
                 │ └─656 /usr/bin/NetworkManager --no-daemon
                 ├─gdm.service
                 │ └─668 /usr/bin/gdm
                 └─systemd-logind.service
                   └─654 /usr/lib/systemd/systemd-logind

### 找到进程的控制组

一个进程所属的 cgroup 组可以在 `/proc/PID/cgroup` 找到。

例如，找到 shell 进程的 cgroup:

    $ cat /proc/self/cgroup

    0::/user.slice/user-1000.slice/session-3.scope

### 查看控制组的系统资源使用情况

`systemd-cgtop` 命令可以用于查看控制组的资源使用情况：

    $ systemd-cgtop

    Control Group                            Tasks   %CPU   Memory  Input/s Output/s
    user.slice                                 540  152,8     3.3G        -        -
    user.slice/user-1000.slice                 540  152,8     3.3G        -        -
    user.slice/u…000.slice/session-1.scope     425  149,5     3.1G        -        -
    system.slice                                37      -   215.6M        -        -

### 自定义控制组

systemd.slice(5) systemd 单元文件可以用于自定义一个 cgroup 配置。单元文件必须放在 systemd 目录下，例如 `/etc/systemd/system/`。可以指定的资源控制选项文档可以在 systemd.resource-control(5) 找到。

这是一个只允许使用 CPU 的 30% 的切片单元例子：

    /etc/systemd/system/my.slice

    [Slice]
    CPUQuota=30%

记得 daemon-reload 来应用 `.slice` 文件的更改。

### 在 Systemd 服务中使用

#### 单元文件

资源可以直接在服务定义或者 drop-in 文件中指定：

    [Service]
    MemoryMax=1G

这个例子将内存使用限制在 1 GB。

#### 使用切片将单元分组

一个服务可以在指定切片下运行：

    [Service]
    Slice=my.slice

### 以 root 用户的身份使用

`systemd-run` 可以用于在特定切片下运行命令。

    # systemd-run --slice=my.slice command

`--uid=username` 选项可以以特定用户的身份运行命令。

    # systemd-run --uid=username --slice=my.slice command

`--shell` 选项可以在指定切片下启动一个 shell。

### 以非特权用户的身份使用

非特权用户可以在特定条件下将提供给他们的服务分成若干 cgroups。

必须使用 Cgroups v2 才能允许非 root 用户管理 cgroup 资源。

#### 控制器种类

并非所有系统资源都可以由用户控制。

Controller | Can be controlled by user | Options
--- | --- | ---
cpu | 需要委派 | CPUAccounting, CPUWeight, CPUQuota, AllowedCPUs, AllowedMemoryNodes
io | 需要委派 | IOWeight, IOReadBandwidthMax, IOWriteBandwidthMax, IODeviceLatencyTargetSec
memory | 是 | MemoryLow, MemoryHigh, MemoryMax, MemorySwapMax
pids | 是 | TasksMax
rdma | 否 | ?
eBPF | 否 | IPAddressDeny, DeviceAllow, DevicePolicy

注意：eBPF 在技术上不是控制器，但使用它实现的 systemd 选项只允许 root 设置。

#### 用户委派

为了让用户控制 CPU 和 IO 资源的使用，需要委派给用户。这可以使用 drop-in 文件来完成。

假如你的 UID 是 1000：

    /etc/systemd/system/user@1000.service.d/delegate.conf

    [Service]
    Delegate=cpu cpuset io

重启并确认用户会话下的切片有了 CPU 和 IO 控制器。

    $ cat /sys/fs/cgroup/user.slice/user-1000.slice/cgroup.controllers

    cpuset cpu io memory pids

#### 用户定义的切片

用户切片文件可以放置在 `~/.config/systemd/user/`。

可以这样在特定切片下运行命令：

    $ systemd-run --user --slice=my.slice command

你也可以在切片里运行你的登陆 shell：

    $ systemd-run --user --slice=my.slice --shell

### 运行时调整

cgroups 资源可以在运行时使用 `systemctl set-property` 命令进行调整。选项语法与 systemd.resource-control(5) 中相同。

警告：除非传递了 `--runntime` 选项，否则调整将永久性生效。系统范围的调整保存在 `/etc/systemd/systemd/system.control/`，用户范围内的保存在 `.config/systemd/user.control/`
注意：并非所有资源更改都会立即生效。例如，更改 TaskMax 只会在生成新进程时生效。

例如，切断所有用户会话的 Internet 访问：

    $ systemctl set-property user.slice IPAddressDeny=any

## 与 libcgroup 和 cgroup 虚拟文件系统一起使用

与使用 systemd 管理相比，cgroup 虚拟文件系统要更更接近底层。"libgroup" 提供了一个库和一些使管理更容易的实用程序，因此我们也将在这里使用它们。

使用更接近底层的方式的原因很简单：systemd 不为 cgroups 中的“每个接口文件”提供接口，也不应该期望它在未来的任何时间点提供它们。从它们中读取以获取有关 cgroup 资源使用的其他信息是完全无害的。

### 在使用非 Systemd 工具之前...

一个 cgroup 应该只由一组程序来写入，以避免竟态条件，即“单一写入规则”。这不是由内核强制执行的，但遵循此建议可以防止难以调试的问题发生。为了让 systemd 停止管理某些子控制组，请参阅 `Delegate=` 属性。否则，systemd 可能覆盖你设置的内容。

### 创建专用组

警告：手动创建“专用”组不会使其被 systemd 管理。除了测试用途之外，不应该这样做；在生产环境中，应当使用 systemd 创建具有适当 `Delegate=` 设置的组（要委派所有权限，设置 `Delegate=yes`）。

cgroups 允许你创建“专用”组。您甚至可以授予创建自定义组的权限给常规用户。 `groupname` 是 cgroup 名称：

    # cgcreate -a user -t user -g memory,cpu:groupname

注意：自 cgroup v2 以来，“memory,cpu”部分是无用的。所有可用的控制器都将包含在内，不会有任何提醒，因为它们都直接处于根控制组之下。要加快打字速度，请使用 `cpu` 或 `\*`。

现在，您的用户可以调整 `groupname` 组中的所有设置：

    $ ls -l /sys/fs/cgroup/groupname

    total 0
    -r--r--r-- 1 root root 0 Jun 20 19:38 cgroup.controllers
    -r--r--r-- 1 root root 0 Jun 20 19:38 cgroup.events
    -rw-r--r-- 1 root root 0 Jun 20 19:38 cgroup.freeze
    --w------- 1 root root 0 Jun 20 19:38 cgroup.kill
    -rw-r--r-- 1 root root 0 Jun 20 19:38 cgroup.max.depth
    -rw-r--r-- 1 root root 0 Jun 20 19:38 cgroup.max.descendants
    -rw-r--r-- 1 root root 0 Jun 20 19:38 cgroup.pressure
    -rw-r--r-- 1 root root 0 Jun 20 19:38 cgroup.procs
    -r--r--r-- 1 root root 0 Jun 20 19:38 cgroup.stat
    -rw-r--r-- 1 root root 0 Jun 20 19:38 cgroup.subtree_control
    -rw-r--r-- 1 root root 0 Jun 20 19:38 cgroup.threads
    -rw-r--r-- 1 root root 0 Jun 20 19:38 cgroup.type
    -rw-r--r-- 1 root root 0 Jun 20 19:38 cpu.idle
    -rw-r--r-- 1 root root 0 Jun 20 19:38 cpu.max
    -rw-r--r-- 1 root root 0 Jun 20 19:38 cpu.max.burst
    -rw-r--r-- 1 root root 0 Jun 20 19:38 cpu.pressure
    -r--r--r-- 1 root root 0 Jun 20 19:38 cpu.stat
    -r--r--r-- 1 root root 0 Jun 20 19:38 cpu.stat.local
    -rw-r--r-- 1 root root 0 Jun 20 19:38 cpu.uclamp.max
    -rw-r--r-- 1 root root 0 Jun 20 19:38 cpu.uclamp.min
    -rw-r--r-- 1 root root 0 Jun 20 19:38 cpu.weight
    -rw-r--r-- 1 root root 0 Jun 20 19:38 cpu.weight.nice
    -rw-r--r-- 1 root root 0 Jun 20 19:38 io.pressure
    -rw-r--r-- 1 root root 0 Jun 20 19:38 irq.pressure
    -r--r--r-- 1 root root 0 Jun 20 19:38 memory.current
    -r--r--r-- 1 root root 0 Jun 20 19:38 memory.events
    -r--r--r-- 1 root root 0 Jun 20 19:38 memory.events.local
    -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.high
    -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.low
    -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.max
    -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.min
    -r--r--r-- 1 root root 0 Jun 20 19:38 memory.numa_stat
    -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.oom.group
    -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.peak
    -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.pressure
    --w------- 1 root root 0 Jun 20 19:38 memory.reclaim
    -r--r--r-- 1 root root 0 Jun 20 19:38 memory.stat
    -r--r--r-- 1 root root 0 Jun 20 19:38 memory.swap.current
    -r--r--r-- 1 root root 0 Jun 20 19:38 memory.swap.events
    -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.swap.high
    -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.swap.max
    -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.swap.peak
    -r--r--r-- 1 root root 0 Jun 20 19:38 memory.zswap.current
    -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.zswap.max
    -rw-r--r-- 1 root root 0 Jun 20 19:38 memory.zswap.writeback
    -r--r--r-- 1 root root 0 Jun 20 19:38 pids.current
    -r--r--r-- 1 root root 0 Jun 20 19:38 pids.events
    -r--r--r-- 1 root root 0 Jun 20 19:38 pids.events.local
    -rw-r--r-- 1 root root 0 Jun 20 19:38 pids.max
    -r--r--r-- 1 root root 0 Jun 20 19:38 pids.peak

cgroups 是有层次的，此您可以创建尽任意多的子组。如果普通用户想要创建名为 `foo` 的新子组并且分配内存和 CPU 控制器，可以运行：

    $ cgcreate -g memory,cpu:groupname/foo

注意： 资源以自上而下的方式分配，且一个 cgroup 仅当从父组获得某项资源后，才能进一步分配该资源。这意味着，只有在父组将控制器分配给某个子组时，该子组才能使用此控制器进行资源限制或将其分配给其子组。

### 使用控制组

正如前文所提到的，在任何时候,只“应该”有一个程序写入 cgroup。这不会影响非写入操作，包括在组内生成新进程、将进程移动到另一个组或从 cgroup 文件读取属性。

#### 生成和移动进程

注意：在 cgroup v2 中，将组资源分配给子组的 cgroup 的内部不能有进程，反之亦然。这是减少混乱的有意的限制！

libcgroup 包含一个简单的工具用于在 cgroup 中运行新进程。如果普通用户想在我们之前的 `groupname/foo` 下运行一个 `bash` shell：

    $ cgexec -g cpu:groupname/foo bash

在 shell 内部,我们可以确认它属于哪个 cgroup：

    $ cat /proc/self/cgroup

    0::/groupname/foo

这会使用 `/proc/$PID/cgroup`，一个存在于每个进程中的文件。手动写入文件也会导致 cgroup 发生变化。

要将所有 'bash' 命令移动到此组：

    $ pidof bash

    13244 13266

    $ cgclassify -g cpu:groupname/foo `pidof bash`

    $ cat /proc/13244/cgroup

    0::/groupname/foo

如果不想使用 `cgclassify`，内核提供了在 cgroups 之间移动进程的另外两种方法。这两个是等价的:

    $ echo 0::/groupname/foo > /proc/13244/cgroup
    $ echo 13244 > /sys/fs/cgroup/groupname/foo/cgroup.procs

注意：在最后一个命令中，一次只能写入一个 PID,因此必须对需要移动的每个进程重复此操作。

#### 管理组属性

一个新的子目录 `/sys/fs/cgroup/group/foo` 将在 `groupname/foo` 创建时创建。这些文件可以读取和写入以更改组的属性。（再次提醒，除非委派完成，否则不建议写入这些文件!）

让我们试着看看我们组中所有的进程占用了多少内存：

    $ cat /sys/fs/cgroup/groupname/foo/memory.current

    1536000

要限制组中所有进程使用的 RAM （不包括交换空间）,请运行以下命令：

    $ echo 10000000 > /sys/fs/cgroup/groupname/foo/memory.max

要更改此组的 CPU 优先级（默认值为 100）:

    $ echo 10 > /sys/fs/cgroup/groupname/foo/cpu.weight

您可以通过列出 cgroup 目录下的文件来查找更多可以调节的设置或统计信息。

### 可持久化组配置

注意：systemd ≥ 205 提供了在单元文件中管理 cgroups 的更好方法。以下内容仍然有效，但不应用于新设置。

如果您希望在引导时创建 cgroup，则可以在 `/etc/cgconfig.conf` 中定义它们。这会导致在引导时启动一个服务以配置您的 cgroups。请参阅有关此文件语法的相关手册页；我们将不会说明如何使用真正已弃用的机制。

## 例子

### 限制进程使用的内存和 CPU

下面的示例显示一个 cgroup，它将指定的命令使用的内存限制为 2GB。

    $ systemd-run --scope -p MemoryMax=2G --user command

下面的示例显示一个命令使用的 CPU 限制为一个 CPU 核心的 20%。

    $ systemd-run --scope -p CPUQuota="20%" --user command

### Matlab

在 MATLAB 中进行大计算可能会使您的系统崩溃，因为Matlab没有任何保护以防止占用机器的所有内存或 CPU。以下示例显示一个将 Matlab 使用的资源限制为前 6 个 CPU 内核和 5 GB 内存的 cgroup。

#### Systemd 配置

    ~/.config/systemd/user/matlab.slice

    [Slice]
    AllowedCPUs=0-5
    MemoryHigh=6G

像这样启动 Matlab（请务必使用正确的路径）:

    $ systemd-run --user --slice=matlab.slice /opt/MATLAB/2012b/bin/matlab -desktop

## 文档

- 有关控制器以及特定开关和可调参数含义的信息，请参阅内核文档的 v2 版本（或安装 linux-docs包 包并查看 `/usr/src/linux/Documentation/cgroup` 目录）。
- Linux 手册页：cgroups(7)
- 详细完整的资源管理指南可在 Red Hat Enterprise Linux 文档中找到。

有关命令和配置文件，请参阅相关手册页，例如 cgcreate(1) 或 cgrules.conf(5)。

## 历史：cgroup v1

在 cgroup 的当前版本 v2 之前，存在一个称为 v1 的早期版本。V1 提供了更灵活的选项，包括非统一层级和线程粒度的管理。现在来看，这是个坏主意（参见 v2 的设计理由）：

- 尽管可以存在多个层级，并且进程可以绑定到多个层级，但一个控制器只能用于一个层级。这使得多个层级本质上毫无意义，通常的设置是将每个控制器绑定到一个层级（例如 `/sys/fs/cgroup/memory/`），然后将每个进程绑定到多个层级。这反过来使得像 `cgcreate` 这样的工具对于同步进程在多个层级中的成员关系变得至关重要。
- 线程粒度的管理导致 cgroup 被滥用作进程管理自身的一种方式。正确的方法是使用系统调用，而不是为了支持这种用法而出现的复杂接口。自我管理需要笨拙的字符串处理，并且本质上容易受到竞态条件的影响。

为了避免进一步的混乱，cgroup v2 在移除功能的基础上制定了两条关键设计规则：

- 如果一个 cgroup 拥有子 cgroup，则它不能附加进程（根 cgroup 除外）。这在 v2 中是强制执行的，有助于实现使下一条规则（单一写入规则）。
- 每个 cgroup 在同一时间应该只有一个进程管理它（单一写入规则）。这条规则并未在任何地方强制执行，但在大多数情况下都应遵守，以避免软件因争相管理同一个组而产生的冲突痛苦。
  - 在有 systemd 系统上，根 cgroup 由 systemd 管理，任何非 systemd 进行的更改都违反了这条规则（这并没有被未强制执行，因此只是建议），除非在相关的服务或作用域单元上设置了 `Delegate=` 选项，告知 systemd 不要干预其内部内容。

在 systemd v258 之前，可以使用内核参数 `SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE=1 systemd.unified_cgroup_hierarchy=0` 来强制使用 cgroup-v1 启动（第一个参数在 v256 中加入以增加使用 cgroup-v1 的难度）。然而，此功能现已被移除。了解这一点仍然有价值，因为有些软件喜欢在不告知您的情况下将 `systemd.unified_cgroup_hierarchy=0` 放入您的内核命令行，导致整个系统崩溃。

## 另请参阅

- systemd cgroups hacker guide
- cgroupv2: Linux's new unified control group system

分类：​

- 内核
- 虚拟化

控制组
添加话题