控制组 - 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

分類：​

- 內核
- 虛擬化

控制组
新增話題