跳至內容

本站近期可能因网络攻击出现服务故障,导致无法联网阅读。建议用户安装 arch-wiki-docs-zh-cnCNRepo 或者 arch-wiki-docs-zh-twCNRepo 离线文档包备用,或者使用经由 Cloudflare CDN 的替代版本aw.lilydjwg.me

出自 Arch Linux 中文维基

控制組 (或者通常被簡寫為 cgroups) 是一項 Linux 內核提供的特性, 用於管理,限制和審核一組進程。Cgroups 可以操作一個進程的集合或者子集(例如,集合中由不同用戶啟動的進程),這使得 cgroups 和其它類似工具,如 nice(1) 命令和 /etc/security/limits.conf 相比更為靈活。

可以使用以下方式使用控制組:

  • systemd 單元文件中使用指令來制定服務和切片的限制;
  • 通過直接訪問 cgroup 文件系統;
  • 通過 cgcreatecgexeccgclassifylibcgroupAURlibcgroup-gitAUR 包的一部分) 等工具;
  • 使用「規則應用守護程序」來自動移動特定的用戶/組/命令到另一個組中(/etc/cgrules.confcgconfig.service)(libcgroupAURlibcgroup-gitAUR 包的一部分);以及
  • 通過其它軟體例如 Linux 容器 (LXC) 虛擬化。

對於 Arch Linux 來說,systemd 是首選的也是最簡單的調用和配置 cgroups 的方法,因為它是默認安裝的一部分。

安裝

確保你已經安裝了這些用於自動處理 cgroups 的包中的至少一個:

  • systemd —— 用於控制 systemd 服務的資源使用。
  • libcgroupAURlibcgroup-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\*

Now all the tunables in the group groupname are writable by your user:

現在,您的用戶可以調整 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 的新子組,可以運行:

$ cgcreate -g cpu:groupname/foo

使用控制組

正如前文所提到的,在任何時候,只「應該」有一個程序寫入 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 放入您的內核命令行,導致整個系統崩潰。

另請參閱