控制组 (或者通常被简写为 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 |
用户委派
为了让用户控制 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/
例如,切断所有用户会话的 Internet 访问:
$ systemctl set-property user.slice IPAddressDeny=any
与 libcgroup 和 cgroup 虚拟文件系统一起使用
与使用 systemd 管理相比,cgroup 虚拟文件系统要更更接近底层。"libgroup" 提供了一个库和一些使管理更容易的实用程序,因此我们也将在这里使用它们。
使用更接近底层的方式的原因很简单:systemd 不为 cgroups 中的“每个接口文件”提供接口,也不应该期望它在未来的任何时间点提供它们。从它们中读取以获取有关 cgroup 资源使用的其他信息是完全无害的。
在使用非 Systemd 工具之前...
一个 cgroup 应该只由一组程序来写入,以避免竟态条件,即“单一写入规则”。这不是由内核强制执行的,但遵循此建议可以防止难以调试的问题发生。为了让 systemd 停止管理某些子控制组,请参阅 Delegate=
属性。否则,systemd 可能覆盖你设置的内容。
创建专用组
Delegate=
设置的组(要委派所有权限,设置 Delegate=yes
)。cgroups 允许你创建“专用”组。您甚至可以授予创建自定义组的权限给常规用户。 groupname
是 cgroup 名称:
# cgcreate -a user -t user -g memory,cpu:groupname
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 文件读取属性。
生成和移动进程
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
管理组属性
一个新的子目录 /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 目录下的文件来查找更多可以调节的设置或统计信息。
可持久化组配置
如果您希望在引导时创建 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 系统上,根 cgroup 由 systemd 管理,任何非 systemd 进行的更改都违反了这条规则(这并没有被未强制执行,因此只是建议),除非在相关的服务或作用域单元上设置了
在 systemd v258 之前,可以使用内核参数 SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE=1 systemd.unified_cgroup_hierarchy=0
来强制使用 cgroup-v1 启动(第一个参数在 v256 中加入 以增加使用 cgroup-v1 的难度)。然而,此功能现已被移除。了解这一点仍然有价值,因为有些软件喜欢在不告知您的情况下将 systemd.unified_cgroup_hierarchy=0
放入您的内核命令行,导致整个系统崩溃。