CGROUPS 介绍

发布时间: 更新时间: 总字数:15775 阅读时间:32m 作者: IP上海 分享 网址

cgroups - Linux control groups

From

apt install groff -y
OUT_DIR=/tmp/

for i in `find /usr/share/man/ -name '*.gz'`; do
    dname=`dirname $i`
    mkdir -p $OUT_DIR/$dname
    zcat $i | groff -mandoc -Thtml > $OUT_DIR/$i.html
done
  • zcat /usr/share/man/man7/cgroups.7.gz | groff -mandoc -Thtml > cgroups.html
  • man cgroups

说明

控制组(通常被称为 cgroups)是 Linux 内核的一项功能,它允许将进程组织到分级组中,然后限制和监控其对各类资源的使用。内核的 cgroup 接口通过一个名为 cgroupfs 的伪文件系统提供。分组在核心 cgroup 内核代码中实现,而资源跟踪和限制则在一组按资源类型划分的子系统(内存、CPU 等)中实现。

术语

cgroups 是一组进程的集合,这些进程与通过 cgroups 文件系统定义的一组限制或参数绑定。

_subsystem_是一个内核组件,用于修改 cgroups 中进程的行为。目前已实现了多种子系统,可以做到限制 cgroups 可用的 CPU 时间和内存量、计算 cgroups 使用的 CPU 时间,以及 冻结和恢复 cgroups 中进程的执行。子系统有时也被称为资源控制器(或简称控制器)。

控制器的 cgroups 按_层次结构_排列。这种层次结构是通过在 cgroup 文件系统中创建、删除和重命名子目录来定义的。在层次结构的每一级,都可以定义属性(如限制)。 cgroups 提供的限制、控制和核算通常对定义属性的 cgroups 下的整个子层次结构都有效。因此,举例来说,在层次结构中较高层级的 cgroups 上设置的限制不能被下一级的 cgroups 超越。

Cgroups version 1 and version 2

cgroups 实现的最初版本是在 Linux 2.6.24 中发布的。随着时间的推移,各种 cgroups 控制器被添加进来,以便管理各种类型的资源。然而,这些控制器的开发在很大程度上并不协调,结果是控制器之间出现了许多不一致的地方,cgroups 层次结构的管理也变得相当复杂。关于这些问题的详细描述,请参阅内核源文件 Documentation/admin-guide/cgroup-v2.rst(或 Linux 4.17 及更早版本中的 Documentation/cgroup-v2.txt)。

由于最初的 cgroups 实现(cgroups 版本 1)存在问题,从 Linux 3.10 开始,我们开始着手开发一种新的、正交的实现来弥补这些问题。新版本(cgroups 版本 2)最初被标记为实验版本,并隐藏在_-o __DEVEL__sane_behavior_挂载选项之后,最终随着 Linux 4.5 的发布而正式推出。两个版本之间的差异将在下文中说明。cgroups v1 中的文件 cgroup.sane_behavior 是该挂载选项的遗留物。该文件总是报告 0,保留它只是为了向后兼容。

尽管 cgroups v2 的目的是替代 cgroups v1,但旧系统仍继续存在(出于兼容性原因,不太可能被删除)。目前,cgroups v2 只实现了 cgroups v1 中可用控制器的一个子集。这两个系统的实现使 v1 控制器和 v2 控制器都可以安装在同一系统上。因此,举例来说,既可以使用版本 2 支持的控制器,也可以在版本 2 尚不支持版本 1 控制器的情况下使用版本 1 控制器。这里唯一的限制是,一个控制器不能同时在 cgroups v1 层次结构和 cgroups v2 层次结构中使用。

CGROUPS VERSION 1

在 cgroups v1 下,每个控制器都可以挂载到一个单独的 cgroups 文件系统中,该系统对系统中的进程进行分级组织。也可以针对同一 cgroups 文件系统挂载多个(甚至所有)cgroups v1 控制器,这意味着挂载的控制器管理相同的进程层次结构。

对于每个已挂载的层次结构,目录树反映了控制组的层次结构。每个控制组由一个目录表示,其每个子控制 cgroups 由一个子目录表示。例如,/user/joe/1.session 表示控制组 1.session,它是 cgroups joe 的子目录,而 cgroups joe 又是 /user 的子目录。每个 cgroup 目录下都有一组可读写的文件,反映了资源限制和一些一般的 cgroup 属性。

Tasks (threads) versus processes

在 cgroups v1 中,进程和任务是有区别的。在这种观点中,一个进程可以由多个任务(从用户空间的角度看,通常称为线程,在本手册的其余部分中也称为线程)组成。 本手册的其余部分也这样称呼)。在 cgroups v1 中,可以独立操作进程中线程的 cgroup 成员资格。

cgroups v1 可以在不同的 cgroups 中分割线程,这在某些情况下会造成问题。 问题。例如,这对_memory_控制器毫无意义,因为进程中的所有线程共享一个地址空间。由于这些问题,在最初的 cgroups v2 实现中,删除了独立操纵进程中线程的 cgroup 成员资格的功能。 最初的 cgroups v2 实现中取消了独立操作进程中线程的 cgroup 成员资格的功能,后来又以更有限的形式恢复了这一功能(请参阅下文关于线程模式的讨论)。

Mounting v1 controllers

使用 cgroups 时,内核必须包含 CONFIG_CGROUP 选项。此外,每个 v1 控制器都有一个相关的配置选项,必须设置该选项才能使用该控制器。

要使用 v1 控制器,必须将其挂载到 cgroups 文件系统中。这种挂载通常位于_/sys/fs/cgroup_挂载的 tmpfs(5)文件系统下。因此,可以按如下方式安装 cpu 控制器:

mount -t cgroup -o cpu none /sys/fs/cgroup/cpu

可以根据同一层次结构计算多个控制器。例如,这里的 cpucpuacct 控制器被计算在一个层次结构中:

mount -t cgroup
-o cpu,cpuacct none /sys/fs/cgroup/cpu,cpuacct

组合控制器的效果是,进程在所有组合控制器中都处于同一个 cgroups 中。单独挂载控制器允许一个进程在一个控制器的 cgroups /foo1 中,而在另一个控制器的 cgroups _/foo2/foo3_中。

可以根据同一层次结构挂载所有 v1 控制器:

mount -t cgroup -o all cgroup /sys/fs/cgroup

(省略 -o all 也能达到同样的效果,因为在没有明确指定控制器的情况下,它是默认设置。)

无法在多个 cgroups hierarchy 下挂载同一个控制器。例如,不能同时针对一个层次结构挂载 cpucpuacct 控制器,而单独针对另一个层次结构挂载 cpu 控制器。可以使用完全相同的一组组合控制器创建多个挂载。不过,在这种情况下,结果只是多个挂载点提供了同一层次结构的视图。

请注意,在许多系统中,v1 控制器会自动挂载到 /sys/fs/cgroup 下;特别是 systemd(1) 会自动创建此类挂载。

Unmounting v1 controllers

可以使用 umount(8) 命令卸载已挂载的 cgroups 文件系统,如下例所示:

umount /sys/fs/cgroup/pids

但请注意:cgroups 文件系统只有在不繁忙(即没有子 cgroups)的情况下才会被卸载。如果不是这种情况,那么umount(8) 的唯一作用就是使挂载不可见。因此,要确保挂载真正被移除,必须首先移除所有子 cgroups,而这又只能在所有成员进程从这些 cgroups 移至根 cgroups 之后才能完成。

Cgroups version 1 controllers

Each of the cgroups version 1 controllers is governed by a kernel configuration option (listed below). Additionally, the availability of the cgroups feature is governed by the CONFIG_CGROUPS kernel configuration option.

cpu (since Linux 2.6.24; CONFIG_CGROUP_SCHED)

当系统繁忙时,可以保证 cgroups 拥有最低数量的 CPU 共享。如果 CPU 不忙,这不会限制 cgroups 的 CPU 使用量。有关详细信息,请参阅 Documentation/scheduler/sched-design-CFS.rst(或 Linux 5.2 及更早版本中的 Documentation/scheduler/sched-design-CFS.txt)。

在 Linux 3.2 中,该控制器被扩展为提供 CPU 带宽控制。如果内核配置了 CONFIG_CFS_BANDWIDTH,那么在每个调度周期内(通过 cgroups 目录中的文件定义),就可以定义分配给 cgroups 中进程的 CPU 时间上限。即使没有其他进程竞争 CPU,该上限也会适用。更多信息请参阅内核源文件 Documentation/scheduler/sched-bwc.rst(或 Linux 5.2 及更早版本中的 Documentation/scheduler/sched-bwc.txt)。

cpuacct (since Linux 2.6.24; CONFIG_CGROUP_CPUACCT)

这可以按进程组核算 CPU 使用情况。

更多信息请参阅内核源文件 Documentation/admin-guide/cgroups-v1/cpuacct.rst (或 Linux 5.2 及更早版本中的 Documentation/cgroups-v1/cpuacct.txt )。

cpuset (since Linux 2.6.24; CONFIG_CPUSETS)

该 cgroups 可用于将 cgroups 中的进程绑定到一组指定的 CPU 和 NUMA 节点上。

更多信息可参见内核源文件 Documentation/admin-guide/cgroup-v1/cpusets.rst(或 Linux 5.2 及更早版本中的 Documentation/cgroup-v1/cpusets.txt)。

memory (since Linux 2.6.25; CONFIG_MEMCG)

内存控制器支持报告和限制 cgroups 使用的进程内存、内核内存和交换内存。

更多信息请参阅内核源文件 Documentation/admin-guide/cgroups-v1/memory.rst(或 Linux 5.2 及更早版本中的 Documentation/cgroups-v1/memory.txt)。

devices (since Linux 2.6.26; CONFIG_CGROUP_DEVICE)

它支持控制哪些进程可以创建(mknod)设备以及打开设备进行读写。策略可指定为 allow-lists 和 deny-lists。由于执行的是 hierarchy,因此新规则不得违反目标或祖先 cgroups 的现有规则。

更多信息请参阅内核源文件 Documentation/admin-guide/cgroup-v1/devices.rst(或 Linux 5.2 及更早版本中的 Documentation/cgroup-v1/devices.txt)。

freezer (since Linux 2.6.28; CONFIG_CGROUP_FREEZER)

freezer cgroups 可以暂停和恢复(resume)cgroups 中的所有进程。冻结 cgroups /A 也会导致其子进程(例如 /A/B 中的进程)被冻结。

更多信息请参阅内核源文件 Documentation/admin-guide/cgroups-v1/freezer-subsystem.rst (或 Linux 5.2 及更早版本中的 Documentation/cgroups-v1/freezer-subsystem.txt)。

net_cls (since Linux 2.6.29; CONFIG_CGROUP_NET_CLASSID)

这将为 cgroups 指定的 classid 放在 cgroups 创建的网络数据包上。这些 classid 可以在防火墙规则中使用,也可以用 tc(8) 来塑造流量。这只适用于离开 cgroups 的数据包,不适用于到达 cgroups 的流量。

更多信息请参阅内核源文件 Documentation/admin-guide/cgroup-v1/net_cls.rst(或 Linux 5.2 及更早版本中的 Documentation/cgroup-v1/net_cls.txt)。

blkio (since Linux 2.6.33; CONFIG_BLK_CGROUP)

blkio cgroups 通过对存储层次结构中的叶节点和中间节点应用节流和上限形式的 IO 控制,来控制和限制对指定块设备的访问。

有两种策略可供选择。第一种是使用 CFQ 实现的基于时间比例权重的磁盘划分。这对使用 CFQ 的叶子节点有效。第二种是节流策略,用于指定设备的 I/O 速率上限。

更多信息请参阅内核源文件 Documentation/admin-guide/cgroups-v1/blkio-controller.rst (或 Linux 5.2 及更早版本中的 Documentation/cgroups-v1/blkio-controller.txt)。

perf_event (since Linux 2.6.39; CONFIG_CGROUP_PERF)

该控制器允许对 cgroups 中的进程集进行_perf_监控。

更多信息请参见内核源文件

net_prio (since Linux 3.3; CONFIG_CGROUP_NET_PRIO)

这样就可以为每个网络接口指定 cgroups 的优先级。

更多信息请参阅内核源文件 Documentation/admin-guide/cgroups-v1/net_prio.rst(或 Linux 5.2 及更早版本中的 Documentation/cgroups-v1/net_prio.txt)。

hugetlb (since Linux 3.5; CONFIG_CGROUP_HUGETLB)

这支持限制 cgroups 使用超大页面。

更多信息请参阅内核源文件 Documentation/admin-guide/cgroup-v1/hugetlb.rst (或 Linux 5.2 及更早版本中的 Documentation/cgroup-v1/hugetlb.txt )。

pids (since Linux 4.3; CONFIG_CGROUP_PIDS)

该控制器允许限制在 cgroups(及其子进程)中创建的进程数量。

更多信息请参阅内核源文件 Documentation/admin-guide/cgroup-v1/pids.rst(或 Linux 5.2 及更早版本中的 Documentation/cgroup-v1/pids.txt)。

rdma (since Linux 4.11; CONFIG_CGROUP_RDMA)

RDMA 控制器允许限制每个 cgroups 的 RDMA/IB 特定资源的使用。

更多信息请参阅内核源文件 Documentation/admin-guide/cgroups-v1/rdma.rst(或 Linux 5.2 及更早版本中的 Documentation/cgroups-v1/rdma.txt)。

Creating cgroups and moving processes

cgroup 文件系统最初只包含一个根 cgroup(’/’),所有进程都属于该 cgroup。在 cgroup 文件系统中创建一个目录,即可创建一个新的 cgroup:

mkdir /sys/fs/cgroup/cpu/cg1

这将创建一个新的空 cgroup。

进程可通过将其 PID 写入 cgroups 的 cgroup.procs 文件:

echo $$ << /sys/fs/cgroup/cpu/cg1/cgroup.procs

一次只能向该文件写入一个 PID。

cgroup.procs 文件写入值 0 会导致写入的进程被移到相应的 cgroup。 将 PID 写入 cgroup.procs 时,进程中的所有线程都会被一次性移入新的 cgroup。

在一个 hierarchy 中,一个进程只能是一个 cgroups 的成员。将进程的 PID 写入_cgroup.procs_文件会自动将其从以前所属的 cgroup 中移除。 通过读取 cgroup.procs 文件,可以获得 cgroup 成员进程的列表。返回的 PID 列表不保证顺序。也不保证没有重复。(例如,从列表中读取时,PID 可能会被回收)。

在 cgroups v1 中,单个线程可以通过将其线程 ID(即由 clone(2) 和 gettid(2) 返回的内核线程 ID)写入 cgroup 目录中的 tasks 文件来移动到另一个 cgroup。读取该文件可以发现 cgroups 成员的线程集。

Removing cgroups

要删除一个 cgroup,首先它必须没有子 cgroup,并且不包含(非僵尸)进程。在这种情况下,只需删除相应的目录路径名即可。请注意,cgroups 目录中的文件不能也无需删除。

Cgroups v1 release notification

有两个文件可用于确定 cgroups 变为空时内核是否提供通知。当一个 cgroup 不包含任何子 cgroup 和成员进程时,该 cgroup 就会被认为是空的。

每个 cgroups 层次结构的根目录中都有一个特殊文件_release_agent_,可用于注册一个程序的路径名,当层次结构中的一个 cgroups 变为空时,该程序可能会被调用。调用 release_agent 程序时,新空 cgroup 的路径名(相对于 cgroup 挂载点)将作为唯一的命令行参数提供。release_agent 程序可能会删除 cgroups 目录,也可能会用进程重新填充该目录。

release_agent 文件的默认值为空,即不调用释放代理。

在挂载 cgroups 文件系统时,也可通过挂载选项指定 release_agent 文件的内容:

mount -o release_agent=pathname ...

当某个 cgroups 变为空时,是否调用 release_agent 程序取决于相应 cgroups 目录中 notify_on_release 文件的值。如果该文件中的值为 0,则不调用 release_agent 程序。如果该文件的值为 1,则会调用 release_agent 程序。在创建新的 cgroups 时,该文件中的值将从父 cgroups 中的相应文件继承。

Cgroup v1 named hierarchies

在 cgroups v1 中,可以挂载没有附加控制器的 cgroups hierarchy:

mount -t cgroups -o none,name=somename none /some/mount/point

可以挂载多个此类层次结构实例;每个层次结构必须有唯一的名称。此类 hierarchy 的唯一目的是跟踪进程。(请参阅下面关于发布通知的讨论。)systemd(1) 使用 name=systemd cgroups 层次结构来跟踪服务和用户会话,就是一个例子。

自 Linux 5.0 起,cgroup_no_v1 内核启动选项(如下所述)可用于禁用 cgroup v1 命名层次结构,方法是指定 cgroup_no_v1=named

CGROUPS VERSION 2

在 cgroups v2 中,所有安装的控制器都位于一个统一的层次结构中。虽然(不同的)控制器可以同时挂载在 v1 和 v2 层次结构下,但同一控制器不可能同时挂载在 v1 和 v2 层次结构下。

cgroups v2 中的新行为在此进行了总结,在某些情况下将在以下小节中进行详细说明。

Cgroups v2 提供了一个统一的层次结构,所有控制器都根据该层次结构进行mounted.
不允许使用 "内部" 进程。除 除根 cgroup 外,进程只能驻留在叶节点(本身不包含子 cgroup 的 cgroup)中。叶节点(本身不包含子 cgroups 的 cgroups)中。cgroups)。细节比这更微妙、 详情如下。
必须通过 _cgroup.controllers_ 和 _cgroup.subtree_control_ 文件指定活动 cgroup。
删除了 _tasks_ 文件。此外,还删除了 _cgroup.clone_children_ 文件,该文件由 _cpuset_ 控制器使用。
_cgroup.events_ 文件提供了一种改进的空 cgroup 通知机制。

有关更多更改,请参阅内核源代码中的_Documentation/admin-guide/cgroups-v2.rst_文件(或 Linux 4.17 及更早版本中的_Documentation/cgroups-v2.txt_文件)。

在 Linux 4.14 中添加了 “线程模式”(如下所述)后,上面列出的一些新行为也随之进行了修改。

Cgroups v2 unified hierarchy

在 cgroups v1 中,针对不同层次结构安装不同控制器的功能旨在为应用程序设计提供极大的灵活性。但在实践中,这种灵活性并没有预期的那么有用,在很多情况下还增加了复杂性。因此,在 cgroups v2 中,所有可用控制器都安装在一个层次结构中。可用控制器会自动加载,这意味着在使用以下命令加载 cgroups v2 文件系统时,没有必要(或不可能)指定控制器:

mount -t cgroup2 none /mnt/cgroup2

cgroups v2 控制器只有在当前未通过针对 cgroups v1 层次结构的挂载使用时才可用。或者换一种说法,不可能同时针对 v1 层次结构和统一的 v2 层次结构使用同一个控制器。由于 systemd(1) 默认情况下会大量使用某些 v1 控制器,因此在某些情况下,禁用选定的 v1 控制器启动系统会更简单。为此,请在内核启动命令行中指定 cgroup_no_v1=list 选项;list 是一个以逗号分隔的要禁用的控制器名称列表,或者用 all 来禁用所有 v1 版控制器。(systemd(1)可正确处理这种情况,它将退回到无指定控制器的运行状态)。

请注意,在许多现代系统上,systemd(1) 会在启动过程中自动将 cgroup2 文件系统挂载到 /sys/fs/cgroup/unified 目录下。

Cgroups v2 mount options

挂载组 v2 文件系统时,可以指定以下选项 (_mount -__): nsdelegate(自 Linux 4.15 起)

将 cgroup 命名空间视为委托边界。详见下文。

memory_localevents (since Linux 5.2)

memory.events 应该只显示 cgroups 本身的统计数据,而不显示任何后代 cgroups 的统计数据。这是 Linux 5.2 之前的行为。从 Linux 5.2 开始,默认行为是在 memory.events 中包含后代 cgroups 的统计信息,可以使用此挂载选项恢复到传统行为。此选项适用于整个系统,只能在初始挂载命名空间的挂载时设置或通过重挂载修改;在非初始命名空间中,此选项将被静默忽略。

Cgroups v2 controllers

内核源文件 Documentation/admin-guide/cgroup-v2.rst (或 Linux 4.17 及更早版本中的 Documentation/cgroup-v2.txt)中记录的以下控制器在 cgroups 版本 2 中受支持: cpu(自 Linux 4.15 起)

这是版本 1 cpucpuacct 控制器的后续。

cpuset (since Linux 5.0)

This is the successor of the version 1 cpuset controller.

这是第一版 cpuset 控制器的后续版本

freezer (since Linux 5.2)

This is the successor of the version 1 freezer controller.

hugetlb (since Linux 5.6)

This is the successor of the version 1 hugetlb controller.

io (since Linux 4.5)

This is the successor of the version 1 blkio controller.

memory (since Linux 4.5)

This is the successor of the version 1 memory controller.

perf_event (since Linux 4.11)

This is the same as the version 1 perf_event controller.

pids (since Linux 4.5)

This is the same as the version 1 pids controller.

rdma (since Linux 4.11)

This is the same as the version 1 rdma controller.

cgroups 第 1 版中的 net_clsnet_prio 控制器没有直接对应的功能。相反,iptables(8) 增加了支持,允许挂钩 cgroups v2 路径名的 eBPF 过滤器按组对网络流量做出决定。

v2 devices 控制器不提供接口文件,而是通过在 v2 cgroups 上附加 eBPF (BPF_CGROUP_DEVICE) 程序来控制设备。

Cgroups v2 subtree control

v2 层次结构中的每个 cgroup 都包含以下两个文件: cgroup.controllers 这个只读文件显示了该 cgroup 中可用的控制器列表。该文件的内容与父 cgroups 中 cgroup.subtree_control 文件的内容一致。

cgroup.subtree_control

这是 cgroups 中_active_(enabled)的控制器列表。此文件中的控制器集是此 cgroups 的 cgroup.controllers 中控制器集的子集。向该文件写入包含以空格为分隔符的控制器名称的字符串可修改活动控制器集,每个名称前都有 “+"(启用控制器)或”-"(禁用控制器),如下例所示:

echo '+pids -memory' > x/y/cgroup.subtree_control

在向_cgroup.subtree_control_文件写入内容时,如果试图启用_cgroup.controllers_中不存在的控制器,则会导致ENOENT错误。

由于_cgroup.subtree_control_中的控制器列表是_cgroup.controllers_中控制器的子集,因此在层次结构中某个 cgroup 中被禁用的控制器永远无法在该 cgroup 下面的子树中重新启用。

cgroup 的 cgroup.subtree_control 文件决定了在 child cgroup 中使用的控制器集。当父 cgroup 的_cgroup.subtree_control_文件中出现控制器(如_pids_)时,相应的控制器接口文件(如_pids.max_)就会自动在该 cgroup 的子 cgroup 中创建,并可用于在子 cgroup 中实施资源控制。

Cgroups v2 no internal processes rule

Cgroups v2 执行所谓的 “无内部进程” 规则。粗略地说,这条规则意味着除根 cgroup 外,进程只能驻留在叶节点(本身不包含子 cgroup 的 cgroup)中。这就避免了在属于 cgroup A 的进程和属于 A 的子 cgroup 的进程之间如何分配资源的问题。

例如,如果存在 cgroup /cg1/cg2,那么进程可以驻留在 /cg1/cg2,但不能驻留在 /cg1。这样做是为了避免在 cgroups v1 中,/cg1 及其子 cgroups 中的进程之间的资源委托出现歧义。cgroups v2 中推荐的方法是为任何非叶子 cgroup 创建一个名为 leaf 的子目录,该子目录应包含进程,但不包含子 cgroup。因此,以前应放入 /cg1 的进程现在应放入 /cg1/leaf。这样做的好处是可以明确 /cg1/leaf 中的进程与 /cg1 的其他子进程之间的关系。

事实上,“无内部进程” 规则比上面所说的更微妙。更准确地说,这条规则是一个(非根)cgroup 不能同时(1)拥有成员进程,以及(2)将资源分配给子 cgroup&mdash;也就是说,拥有一个非空的_cgroup.subtree_control_文件。因此,一个 cgroup 有可能同时拥有成员进程和子 cgroup,但在为该 cgroup 启用控制器之前,成员进程必须移出该 cgroup(例如,可能移入子 cgroup)。 随着 Linux 4.14 新增了 “线程模式”(如下所述),“无内部进程” 规则在某些情况下被放宽。

Cgroups v2 cgroup.events file

v2 层次结构中的每个非根 cgroup 都包含一个只读文件 cgroup.events,其内容是键值对(以换行符分隔,键和值以空格分隔),提供有关 cgroup 的状态信息:

$ catmygrp/cgroup.events
populated 1
frozen 0

该文件中可能出现以下键值: 如果此 cgroup 或其任何子进程有成员进程,则此键的值为 1,否则为 0。

frozen (since Linux 5.2)

如果当前 cgroup 已冻结,则该键的值为 1;如果未冻结,则该键的值为 0。

可以监控 cgroup.events 文件,以便在其中一个键的值发生变化时收到通知。可以使用 inotify(7) 或 poll(2)进行监控,inotify(7) 将变化通知为 IN_MODIFY 事件,poll(2) 则通过返回 revents 字段中的 POLLPRIPOLLERR 位来通知变化。

Cgroup v2 release notification

Cgroups v2 提供了在 cgroup 变为空时获取通知的新机制。cgroups v1 的 release_agentnotify_on_release 文件被删除,取而代之的是 cgroup.events 文件中的 populated 关键字。这个键的值要么是 0,表示 cgroup(及其后代)不包含(非僵尸)成员进程;要么是 1,表示 cgroup(或其后代之一)包含成员进程。

与 cgroups v1 release_agent 机制相比,cgroups v2 发布通知机制具有以下优势:

由于单个进程可以监控多个 _cgroup.events_ 文件(使用前面描述的技术),因此通知成本更低。相比之下,cgroups v1 机制需要为每个通知创建一个进程。
不同 cgroup 子层次的通知可委托给不同进程。相比之下,cgroups v1 机制只允许一个发布代理负责整个层次结构。

Cgroups v2 cgroup.stat file

v2 层次结构中的每个 cgroup 都包含一个只读的 cgroup.stat 文件(在 Linux 4.14 中首次引入),该文件由包含键值对的行组成。该文件目前包含以下键值对:nr_descendants 这是该 cgroup 下可见的(即存活的)后代 cgroup 的总数。

nr_dying_descendants

这是该 cgroup 下垂死子 cgroup 的总数。cgroup 在删除后会进入垂死状态。在 cgroup 被销毁之前,它将在资源释放期间(取决于系统负载)保持该状态,时间长短未定义。请注意,某些 cgroup 处于垂死状态是正常的,并不说明任何问题。

进程无法成为垂死 cgroup 的成员,垂死的 cgroup 也无法复活。

Limiting the number of descendant cgroups

v2 层次结构中的每个 cgroup 都包含以下文件,可用于查看和设置该 cgroup 下的子 cgroup 数量限制: cgroup.max.depth(自 Linux 4.14 起)

该文件定义了后代 cgroup 嵌套深度的限制。如果该文件中的值为 0,则表示不能创建子 cgroup。如果试图创建嵌套深度超过限制的子目录,则会失败(mkdir(2) 失败并显示 EAGAIN 错误)。

将字符串 max 写入该文件意味着没有限制。该文件的默认值是 max

cgroup.max.descendants (since Linux 4.14)

该文件定义了该 cgroup 可能拥有的实时后代 cgroup 的数量限制。如果试图创建超过限制所允许的子代,则会失败(mkdir(2) 失败并显示 EAGAIN 错误)。

将字符串 max 写入该文件意味着没有限制。该文件的默认值是 max

CGROUPS DELEGATION: DELEGATING A HIERARCHY TO A LESS PRIVILEGED USER

就 cgroups 而言,授权意味着将 cgroups 层次结构中某些子树的管理权交给非特权用户。Cgroups v1 支持基于 cgroups 层次结构中文件权限的授权,但其包含规则不如 v2 严格(如下所述)。Cgroups v2 通过明确的设计支持带有包含规则的授权。本节讨论的重点是 cgroups v2 中的委托,同时指出与 cgroups v1 的一些不同之处。

描述委托需要一些术语。授权者是拥有父 cgroups 的特权用户(如 root)。被_delegatee_是一个非特权用户,他将被授予管理该父 cgroups 下某些子层级(称为_delegated subtree_)所需的权限。

为了执行委托,delegater 通常会将对象的所有权更改为被委托者的用户 ID,从而使某些目录和文件可由被委托者写入。假设我们要委托根植于(例如)/dlgt_grp 的 hierarchy,并且该 cgroups 下还没有任何子 cgroups,则以下对象的所有权将被更改为被委托者的用户 ID:_/dlgt_grp_子树根目录所有权的更改意味着在该子树下创建的任何新 cgroups(及其包含的文件)也将由被委托者拥有。

/dlgt_grp/cgroup.procs

更改该文件的所有权意味着受权者可以将进程移至受权子树的根目录。

/dlgt_grp/cgroup.subtree_control (cgroups v2 only)

更改该文件的所有权意味着受委托者可以启用控制器(这些控制器存在于 /dlgt_grp/cgroups.controllers),以便进一步重新分配子树中较低层次的资源(作为更改该文件所有权的替代方法,委托者可以在该文件中添加选定的控制器)。

/dlgt_grp/cgroup.threads (cgroups v2 only)

如果委托的是线程子树(参见下文 “线程模式 “的描述),则有必要更改该文件的所有权。这允许被委托者将线程 ID 写入该文件。(在委托域子树时,该文件的所有权也可以改变,但目前这没有任何作用,因为如下所述,不可能通过将线程 ID 写入 cgroup.threads 文件来在域 cgroup 之间移动线程)。

在 cgroups v1 中,应该委托的相应文件是 tasks 文件。

委托者不应改变 dlgt_grp 中任何控制器接口文件(如 pids.maxmemory.high)的所有权。这些文件用于从被委托子树的上一级向子树分配资源,被委托者不应有权限更改分配到被委托子树的资源。

有关 cgroups v2 中更多可委托文件的信息,请参阅 NOTES 中有关 /sys/kernel/cgroup/delegate 文件的讨论。

执行上述步骤后,受权者可在受权子树内创建子 cgroup(cgroup 子目录及其包含的文件将归受权者所有),并在子树的 cgroup 之间移动进程。如果_dlgt_grp/cgroups.subtree_control_中存在某些控制器,或者该文件的所有权已传递给受权人,那么受权人还可以控制相应资源进一步重新分配到受权子树中。

Cgroups v2 delegation: nsdelegate and cgroup namespaces

从 Linux 4.13 开始,在 cgroups v2 层次结构中还有第二种执行 cgroup 授权的方法。方法是使用 nsdelegate mount 选项挂载或重新挂载 cgroups v2 文件系统。例如,如果 cgroups v2 文件系统已经挂载,我们可以使用 nsdelegate 选项重新挂载,具体操作如下:

mount -t cgroup2 -o remount,nsdelegate \
none /sys/fs/cgroup/unified

此挂载选项的作用是使 cgroup 命名空间自动成为委托边界。更具体地说,以下限制适用于 cgroup 命名空间内的进程:

向命名空间根目录下的控制器接口文件写入将失败,并显示 *EPERM* 错误。cgroup 命名空间内的进程仍可写入 cgroup 命名空间根目录下的可 delegater 文件,如 _cgroup.procs_ 和 _cgroup.subtree_control_,并可在根目录下创建子层次结构。
跨越命名空间边界迁移进程的尝试将被拒绝(错误信息为 *ENOENT*)。cgroup 名称空间内的进程仍可在名称空间根目录下的子层次结构内的 cgroup 之间移动进程(须遵守下文所述的包含规则)。

将 cgroup 命名空间定义为委托边界的功能使 cgroup 命名空间更加有用。为了理解原因,假设我们已经有了一个 cgroups 层次结构,该层次结构已通过上述旧的委托技术委托给非特权用户 cecilia。再假设 cecilia 想进一步委托现有委托层次结构下的一个子层次结构。(例如,委托的层次结构可能与 cecilia 运行的无特权容器相关联)。即使使用了 cgroup 命名空间,由于这两个层次结构都归无权限用户 cecilia 所有,也可能执行以下非法操作:

下级层次结构中的进程可以更改该层次结构根目录中的资源控制器设置。(这些资源控制器设置旨在允许从_parent_ cgroups 进行控制;不应允许子 cgroups 内的进程修改这些设置)。
如果上级层次结构中的 cgroups 以某种方式可见,则下级层次结构内部的进程可以将进程移入或移出下级层次结构。

使用 nsdelegate 挂载选项可以避免这两种可能性。

nsdelegate 挂载选项只有在初始挂载命名空间中执行时才有效;在其他挂载命名空间中,该选项将被静默忽略。

注意 在某些系统中,systemd(1) 会自动挂载 cgroup v2 文件系统。为了试用 nsdelegate 操作,使用以下命令行选项启动内核可能会有所帮助:

cgroup_no_v1=all systemd.legacy_systemd_cgroup_controller

这些选项会导致内核在启动时禁用 cgroups v1 控制器(这意味着控制器在 v2 层次结构中可用),并告诉 systemd(1) 不要挂载和使用 cgroups v2 层次结构,这样就可以在启动后使用所需的选项手动挂载 v2 层次结构。

Cgroup delegation containment rules

某些委托_包含规则_确保被委托者可以在委托子树内的 cgroups 之间移动进程,但不能将委托子树外的进程移动到子树内,反之亦然。只有当以下条件全部为真时,非特权进程(即被委托者)才能将 “目标 “进程的 PID 写入_cgroup.procs_文件:

写入者拥有目标 cgroups 中 _cgroup.procs_ 文件的写入权限。
写入者对源 cgroup 和目标 cgroup 最近的共同祖先中的 _cgroup.procs_ 文件有写入权限。请注意,在某些情况下,最近的共同祖先可能是源 cgroups 或目标 cgroups 本身。这一要求在 cgroups v1 层次结构中不强制执行,因此在 v1 层次结构中的包含性比在 v2 层次结构中不那么严格(例如,在 cgroups v1 层次结构中,拥有两个不同委托子层次结构的用户可以在两个层次结构之间移动进程)。
如果使用 _nsdelegate_ 选项挂载了 cgroup v2 文件系统,写入器必须能够从其 cgroup 命名空间中看到源 cgroup 和目标 cgroup。
在 cgroups v1 中:写入者(即受委托人)的有效 UID 与目标进程的真实用户 ID 或保存的 set-user-ID 相匹配。在 Linux 4.11 之前,这一要求也适用于 cgroups v2(这是从 cgroups v1 继承而来的历史要求,后来被认为没有必要,因为其他规则足以满足 cgroups v2 中的包含要求)。

注意:这些委托包含规则的一个后果是,无特权的被委托者不能将第一个进程放入委托子树;相反,委托者必须将第一个进程(被委托者拥有的进程)放入委托子树。

CGROUPS VERSION 2 THREAD MODE

cgroups v2 实施了 cgroups v1 中没有的以下限制:

_无线程粒度控制_:进程的所有线程必须在同一个 cgroups 中。
_无内部进程_:一个 cgroup 不能既有成员进程,又在子 cgroup 上行使控制器。

特别是,cgroups v1 允许线程级粒度的 cgroups 成员资格对某些控制器来说毫无意义。(一个明显的例子是_memory_控制器:由于线程共享一个地址空间,因此在不同的_memory_ cgroups中分割线程是毫无意义的)。 尽管在 cgroups v2 中做出了最初的设计决定,但某些控制器(尤其是 cpu 控制器)的使用情况表明,线程级的控制粒度是有意义和有用的。为了适应这种使用情况,Linux 4.14 为 cgroups v2 增加了_线程模式_。

线程模式允许以下操作:

创建_线程子树_,其中进程的线程可分散在树内的 cgroups 中(一个线程子树可包含多个多线程进程)。
_线程控制器_的概念,它可以在线程子树的 cgroups 中分配资源。
放宽了 "无内部进程规则",因此在线程子树中,cgroups 既可以包含成员线程,也可以对子 cgroups 进行资源控制。

随着线程模式的加入,每个非根 cgroups 现在都包含一个新文件 cgroup.type,该文件公开并在某些情况下可用于更改 cgroups 的 “类型”。该文件包含以下类型值之一:

_domain_ 这是一个普通的 v2 cgroup,提供进程粒度控制。如果一个进程是该 cgroups 的成员,那么该进程的所有线程(根据定义)都在同一个 cgroups 中。这是默认的 cgroups 类型,与最初的 cgroups v2 实现中提供的 cgroups 行为相同。

threaded

该 cgroup 是线程子树的成员。可以向该 cgroups 添加线程,也可以为该 cgroups 启用控制器。

domain threaded

这是一个域 cgroups,作为线程子树的根。这种 cgroups 类型也称为 “线程根”。

domain invalid

这是一个处于 “无效 “状态的线程子树内的 cgroup。进程无法添加到 cgroup,控制器也无法为 cgroup 启用。对该 cgroups 唯一能做的事情(除了删除它)就是通过在_cgroup.type_文件中写入_threaded字符串将其转换为_threaded cgroup。

在创建线程子树时存在这种 interim 类型(而不是内核简单地立即将线程根下的所有 cgroups 转换为 threaded 类型)的理由是允许将来对线程模式模型进行可能的扩展。

Threaded versus domain controllers

增加线程模式后,cgroups v2 现在可区分两种类型的资源控制器:

_Threaded_ 控制器:这些控制器支持资源控制的线程粒度,可以在线程子树内启用,结果是相应的控制器接口文件出现在线程子树的 cgroups 内。在 Linux 4.19 中,以下控制器是线程化的: _cpu_、_perf_event_ 和 _pids_。
_域_控制器:这些控制器只支持进程粒度的资源控制。从域控制器的角度看,进程的所有线程总是在同一个 cgroups 中。域控制器不能在线程子树内启用。

Creating a threaded subtree

创建线程子树有两种途径。第一种途径如下:

(1) 我们将字符串 _`threaded`_ 写入当前类型为 _domain_ 的 cgroups _y/z_ 的 _cgroup.type_ 文件。这样做的效果如下:

cgroups y/z 的类型变为 threaded

父 cgroups 的类型 _y_ 变为 _domain threaded_。父 cgroups 是线程子树(也称为 "线程根")的根。
_y_ 下的所有其他 cgroups,如果还不是 _threaded_ 类型(因为它们位于新线程根下已经存在的线程子树内),则会转换为 _domain invalid_ 类型。随后在 _y_ 下创建的任何 cgroups 也将使用 _domain invalid_ 类型。
(2)

我们将 threaded 字符串写入 y 下的每个 domain invalid cgroups,以便将它们转换为 threaded 类型。这一步的结果是,线程根下的所有线程现在都具有 threaded 类型,线程子树现在完全可用。要求将 threaded 写入每个 cgroups 有点麻烦,但却为将来扩展线程模式模型提供了可能。

创建线程子树的第二种方法如下:

(1) 在当前类型为_domain_的现有 cgroups _z_中,我们(1.1) 启用一个或多个线程控制器,(1.2) 使一个进程成为 _z_的成员。(这两个步骤可以按任意顺序进行):

The type of _z_ becomes _domain threaded_.
所有 _z_ 的后代 cgroups,如果还不是 _threaded_ 类型,则转换为 _domain invalid_ 类型。
(2)

与之前一样,我们通过向 z 下的每个 domain invalid cgroups 写入字符串 threaded,将它们转换为 threaded 类型,从而使线程子树可用。

上述创建线程子树路径的后果之一是,线程根 cgroup 只能是_threaded_(和_domain invalid_)cgroup 的父级。线程根 cgroups 不能成为 domain cgroups 的父级,而 threaded cgroups 不能有 domain cgroups 的同级。

Using a threaded subtree

在线程子树中,可以在每个类型已改为_threaded_的子群组中启用线程控制器;启用后,相应的控制器接口文件就会出现在该 cgroups 的子群组中。

将进程的 PID 写入子树中某个 cgroups 的_cgroup.procs_文件,就可以将进程移入线程子树。这样做的效果是使进程中的所有线程都成为相应 cgroups 的成员,并使进程成为线程子树的成员。然后,可以通过将线程 ID(参见 gettid(2))写入子树内部不同 cgroups 中的 cgroup.threads 文件,将进程的线程分散到线程子树中。进程的线程必须全部位于同一个线程子树中。

与写入_cgroup.procs_文件一样,写入_cgroup.threads_文件时也适用一些包含规则:

写入者必须拥有目标 cgroups 中 cgroup.threads 文件的写入权限。
写入者必须拥有源 cgroup 和目标 cgroup 共同祖先中 _cgroup.procs_ 文件的写入权限。(在某些情况下,共同祖先可能就是源 cgroup 或目标 cgroup 本身)。
源 cgroups 和目标 cgroups 必须在同一个线程子树中(在线程子树之外,试图通过将线程 ID 写入不同 _domain_ cgroups 中的 _cgroup.threads_ 文件来移动线程,会出现错误 *EOPNOTSUPP*)。

cgroup.threads_文件存在于每个 cgroups(包括_domain cgroups)中,读取该文件可以发现 cgroups 中的线程集。读取该文件时获得的线程 ID 不保证有序或无重复。

线程根目录下的_cgroup.procs_文件显示了作为线程子树成员的所有进程的 PID。子树中其他 cgroups 中的 cgroup.procs 文件不可读。

域控制器无法在线程子树中启用;线程根目录下的 cgroups 中不会出现控制器接口文件。从域控制器的角度看,线程子树是不可见的:在域控制器看来,线程子树内的多线程进程是驻留在线程根 cgroups 中的进程。

在线程子树内,“无内部进程 “规则并不适用:一个 cgroup 既可以包含成员进程(或线程),也可以在子 cgroup 上行使控制器。

Rules for writing to cgroup.type and creating threaded subtrees

在向_cgroup.type_文件写入内容时,有许多规则需要遵守:

只能写入 _`threaded`_ 字符串。换句话说,唯一可能的显式转换是将 _domain_ cgroups 转换为 _threaded_ 类型。

写入 _`threaded`_ 的效果取决于 _cgroup.type_ 中的当前值,如下所示:

_domain_ 或 _domain threaded_:通过上述第一种途径开始创建线程子树(其根是此 cgroups 的父);
_domain invalid_:将此 cgroups(位于线程子树内部)转换为可用状态(即_threaded_);
_threaded_: no effect (a `no-op`).

如果父文件的类型是_domain invalid_,我们就不能写入_cgroup.type_文件。换句话说,线程子树的 cgroups 必须以自上而下的方式转换为 threaded 状态。

要创建一个以 cgroups x 为根的线程子树,还必须满足一些约束条件:

在 _x_ 的后代 cgroups 中不能有成员进程。(cgroups _x_本身可以有成员进程)。
不得在 _x_ 的 _cgroup.subtree_control_ 文件中启用任何域控制器

如果违反了上述任何限制条件,则向 cgroup.type 文件写入 threaded 的尝试将失败,并出现 ENOTSUP 错误。

The domain threaded cgroup type

根据上述途径,在以下任一情况下,cgroups 的类型都可以变为_域线程_:

字符串 _`threaded`_ 被写入子 cgroups。
在 cgroups 内启用线程控制器,并将进程作为 cgroups 的成员。

如果上述条件不再成立—也就是说,如果 x 的所有 threaded 子 cgroup 被删除,并且 x 不再启用线程控制器或不再有成员进程,则 domain threaded cgroup x 可以恢复为 domain 类型。

当_domain threaded_ cgroups _x_恢复为_domain_类型时:

所有不在下级线程子树中的 _x_ 的 _domain invalid_ 后代都会恢复为 _domain_ 类型。
任何低级线程子树中的根 cgroup 都会恢复为_域线程_类型。

Exceptions for the root cgroup

v2 层次结构中的根 cgroups 被特殊对待:它既可以是 domain cgroups 的父级,也可以是 threaded cgroups 的父级。如果 threaded 字符串被写入根 cgroup 的一个子 cgroup 的 cgroup.type 文件,则

cgroups 的类型就变成了_threaded_。
该 cgroup 不属于下级线程子树的任何后代的类型都会变为 _domain invalid_。

请注意,在这种情况下,并不存在类型变为_域线程_的 cgroup。(从概念上讲,根 cgroup 可以被视为类型变为 threaded 的 cgroup 的线程根)。

对根 cgroup 进行这种特殊处理的目的是,允许将使用 cpu 控制器的线程 cgroup 放在层次结构中尽可能高的位置,以尽量减少遍历 cgroup 层次结构的(少量)成本。

The cgroups v2 cpu controller and realtime threads

在 Linux 4.19 中,cgroups v2 cpu 控制器不支持控制实时线程(特别是根据 SCHED_FIFOSCHED_RRSCHED_DEADLINE 描述的任何策略调度的线程;请参阅 sched(7))。因此,只有当所有实时线程都在根 cgroup 中时,才能在根 cgroup 中启用 cpu 控制器。(如果在非根 cgroups 中存在实时线程,那么向_cgroup.subtree_control_文件写入_+cpu_字符串的write(2)操作就会失败,并出现EINVAL错误)。

在某些系统上,systemd(1) 会将某些实时线程置于 v2 层次结构中的非根 cgroups 中。在这些系统上,必须先将这些线程移到根 cgroups 中,然后才能启用 cpu 控制器。

ERRORS

mount(2) 可能会出现以下错误:

*EBUSY* 尝试挂载 cgroups 版本 1 的文件系统时,既没有指定 _name=_ 选项(挂载已命名的层次结构),也没有指定控制器名称(或 _all_)。

NOTES

通过 fork(2) 创建的子进程会继承父进程的 cgroup 成员资格。进程的 cgroup 成员资格会在 execve(2) 中保留。

clone3(2)CLONE_INTO_CGROUP标志可用于创建一个子进程,该子进程从父进程的不同版本 2 cgroups 开始其生命。

/proc files

/proc/cgroups (since Linux 2.6.24)

该文件包含编译到内核中的控制器信息。该文件的内容示例(为便于阅读而重新格式化)如下:

#subsys_name
hierarchy num_cgroups enabled
cpuset 4 1 1
cpu 8 1 1
cpuacct 8 1 1
blkio 6 1 1
memory 3 1 1
devices 10 84 1
freezer 7 1 1
net_cls 9 1 1
perf_event 5 1 1
net_prio 9 1 1
hugetlb 0 1 0
pids 2 1 1

该文件中的字段从左到右依次为

[1] The name of the controller.
[2] 安装此控制器的 cgroups hierarchy 的唯一 ID。如果多个 cgroups v1 控制器绑定到同一层次结构,则每个控制器都将在此字段中显示相同的层次结构 ID。如果出现以下情况,此字段的值将为 0:

控制器未安装在 cgroups v1 层次结构上;
控制器绑定到 cgroups v2 单一统一层次结构;或
控制器被禁用(见下文)。
[3]

此 hierarchy 中使用此控制器的控制组数量。

[4] 如果控制器已启用,则该字段的值为 1;如果控制器已禁用(通过 _cgroup_disable_ 内核命令行启动参数),则该字段的值为 0。

/proc/pid/cgroup (since Linux 2.6.24)

该文件描述了具有相应 PID 的进程所属的控制组。对于版本 1 和版本 2 的 cgroups 层次结构,显示的信息有所不同。

进程所属的每个 cgroups 层次结构都有一个条目,包含三个以冒号分隔的字段:

hierarchy-ID:controller-list:cgroup-path

For example:

5:cpuacct,cpu,cpuset:/daemons

以冒号分隔的字段从左到右依次为

[1] 对于 cgroups 版本 1 层次结构,此字段包含唯一的层次结构 ID 号,可与 _/proc/cgroups_ 中的层次结构 ID 匹配。对于 cgroups 版本 2 层次结构,此字段包含值 0。
[2] 对于 cgroups 版本 1 层次结构,此字段包含以逗号分隔的绑定到层次结构的控制器列表。对于 cgroups 版本 2 层次结构,此字段为空。
[3] 该字段包含进程所属层次结构中控制组的路径名。该路径名相对于层次结构的挂载点。

/sys/kernel/cgroup files

/sys/kernel/cgroup/delegate (since Linux 4.15)

此文件导出可委托(即其所有权应更改为被委托者的用户 ID)的 cgroups v2 文件列表(每行一个)。将来,可委托的文件集可能会改变或增加,这个文件为内核提供了一种方法,让用户空间应用程序知道哪些文件必须委托。在 Linux 4.15 中,检查该文件时会看到以下内容:

$ *cat
/sys/kernel/cgroup/delegate*
cgroup.procs
cgroup.subtree_control
cgroup.threads

/sys/kernel/cgroup/features (since Linux 4.15)

随着时间的推移,内核提供的 cgroups v2 功能集可能会改变或增加,或者某些功能可能不会默认启用。该文件为用户空间应用程序提供了一种发现运行中的内核支持和启用哪些功能的方法。特性每行列出一个:

$ *cat /sys/kernel/cgroup/features*
nsdelegate
memory_localevents

该文件中可能出现的条目有 memory_localevents(自 Linux 5.2 起)

内核支持 memory_localevents 挂载选项。

nsdelegate (since Linux 4.15)

内核支持 nsdelegate 挂载选项。

memory_recursiveprot (since Linux 5.7)

内核支持 memory_recursiveprot 挂载选项。

SEE ALSO

prlimit(1), systemd(1), systemd-cgls(1), systemd-cgtop(1), clone(2), ioprio_set(2), perf_event_open(2), setrlimit(2), cgroup_namespaces(7), cpuset(7), namespaces(7), sched(7), user_namespaces(7)

The kernel source file Documentation/admin-guide/cgroup-v2.rst.

参考

  1. https://man7.org/linux/man-pages/man7/cgroups.7.html

Translations

Home Archives Categories Tags Statistics
本文总阅读量 次 本站总访问量 次 本站总访客数