CGROUPS - Linux手册页
Linux程序员手册 第7部分
更新日期: 2020-08-13
名称
cgroups-Linux控制组
说明
控制组(通常称为cgroup)是Linux内核功能,它允许将进程组织为分层组,然后可以限制和监视各种资源的使用。内核的cgroup接口通过称为cgroupfs的伪文件系统提供。分组是在核心cgroup内核代码中实现的,而资源跟踪和限制是在一组每个资源类型的子系统(内存,CPU等)中实现的。
Terminology
cgroup是绑定到通过cgroup文件系统定义的一组限制或参数的进程的集合。
子系统是内核组件,可修改cgroup中进程的行为。已经实现了各种子系统,从而可以做一些事情,例如限制cgroup可用的CPU时间和内存,考虑cgroup使用的CPU时间以及冻结和恢复cgroup中进程的执行。子系统有时也称为资源控制器(或简称为控制器)。
控制器的cgroup按层次结构排列。通过在cgroup文件系统内创建,删除和重命名子目录来定义此层次结构。在层次结构的每个级别,可以定义属性(例如,限制)。 cgroup提供的限制,控制和计费通常在定义属性的cgroup之下的整个子层次结构中有效。因此,例如,子级cgroup不能超过在层次结构中较高级别的cgroup上设置的限制。
Cgroups version 1 and version 2
cgroups实现的最初版本是在Linux 2.6.24中。随着时间的推移,已添加了各种cgroup控制器以允许管理各种类型的资源。但是,这些控制器的开发在很大程度上是不协调的,结果是控制器之间出现了许多不一致之处,并且cgroup层次结构的管理变得相当复杂。在内核源文件Documentation / admin-guide / cgroup-v2.rst(或Linux 4.17及更低版本中的Documentation / cgroup-v2.txt)中可以找到这些问题的详细说明。
由于从Linux 3.10开始的初始cgroups实现(cgroups版本1)存在问题,因此开始着手研究新的正交实现以解决这些问题。最初标记为实验性的,并且隐藏在-o__DEVEL__sane_behavior挂载选项的后面,新版本(cgroups版本2)最终在Linux 4.5发行后正式发布。以下文本描述了两个版本之间的差异。 cgroups v1中存在的文件cgroup.sane_behavior是此安装选项的遗留物。该文件始终报告为" 0",并且仅保留该文件是为了向后兼容。
尽管cgroups v2旨在替代cgroups v1,但是较旧的系统仍然存在(出于兼容性原因,不太可能删除)。当前,cgroups v2仅实现cgroups v1中可用的控制器的子集。实现这两个系统是为了使v1控制器和v2控制器都可以安装在同一系统上。因此,例如,可以使用版本2下受支持的那些控制器,同时还可以使用版本2尚不支持那些控制器的版本1控制器。这里唯一的限制是,不能同时在cgroups v1层次结构和cgroups v2层次结构中同时使用控制器。
CGROUPS VERSION 1
在cgroups v1下,可以将每个控制器安装在一个单独的cgroup文件系统上,该文件系统提供系统上进程的自己的层次结构。也可以将多个(甚至所有)cgroup v1控制器挂载到同一cgroup文件系统上,这意味着这些挂载的控制器管理相同的进程层次结构。
对于每个已安装的层次结构,目录树都将镜像控制组层次结构。每个控件组均由目录表示,其每个子控件cgroup均表示为子目录。例如,/ user / joe / 1.session表示控制组1.session,它是cgroup joe的子级,而cgroup joe是/ user的子级。每个cgroup目录下都有一组可以读取或写入的文件,反映了资源限制和一些常规cgroup属性。
Tasks (threads) versus processes
在cgroups v1中,在流程和任务之间进行了区分。在此视图中,一个进程可以包含多个任务(从用户空间的角度来看,更通常称为线程,在本手册页的其余部分中称为"线程")。在cgroups v1中,可以独立地处理进程中线程的cgroup成员资格。
在某些情况下,cgroups v1在不同cgroup之间拆分线程的功能导致了问题。例如,对于内存控制器来说,这毫无意义,因为进程的所有线程共享一个地址空间。由于这些问题,在最初的cgroups v2实现中删除了在进程中独立操作线程的cgroup成员资格的能力,然后以更有限的形式恢复了该能力(请参阅下面"线程模式"的讨论)。
Mounting v1 controllers
使用cgroups需要使用CONFIG_CGROUP选项构建的内核。此外,每个v1控制器都有一个关联的配置选项,必须采用该配置选项才能使用该控制器。
为了使用v1控制器,必须将其安装在cgroup文件系统上。此类挂载的通常位置是在/ sys / fs / cgroup挂载的tmpfs(5)文件系统下。因此,可以按以下方式安装cpu控制器:
mount -t cgroup -o cpu none /sys/fs/cgroup/cpu
可以将多个控制器针对相同的层次结构进行挂载。例如,此处的cpu和cpuacct控制器是针对单个层次结构共同安装的:
mount -t cgroup -o cpu,cpuacct none /sys/fs/cgroup/cpu,cpuacct
共同安装的控制器的作用是,所有共同安装的控制器的进程都在同一cgroup中。分别安装控制器可以使一个进程位于一个控制器的cgroup / foo1中,而另一个进程位于/ foo2 / foo3。
可以将所有v1控制器按相同的层次结构共存:
mount -t cgroup -o all cgroup /sys/fs/cgroup
(通过省略-o all可以达到相同的结果,因为如果未明确指定任何控制器,这是默认设置。)
无法针对多个cgroup层次结构安装同一控制器。例如,无法针对一个层次结构安装cpu和cpuacct控制器,而无法针对另一层次结构单独安装cpu控制器。可以使用完全相同的一组共同安装的控制器创建多个安装点。但是,在这种情况下,所有结果都是提供了相同层次结构视图的多个安装点。
请注意,在许多系统上,v1控制器会自动安装在/ sys / fs / cgroup下。特别是,systemd(1)自动创建此类安装点。
Unmounting v1 controllers
可以使用umount(8)命令卸载已装入的cgroup文件系统,如以下示例所示:
umount /sys/fs/cgroup/pids
但请注意:cgroup文件系统仅在不繁忙时才被挂载,也就是说,它没有子cgroup。如果不是这种情况,那么umount(8)的唯一作用就是使安装不可见。因此,为了确保真正删除安装点,必须首先删除所有子cgroup,而这仅在将所有成员进程从这些cgroup移到根cgroup之后才能执行。
Cgroups version 1 controllers
每个cgroups版本1控制器均由内核配置选项(下面列出)控制。另外,cgroups功能的可用性由CONFIG_CGROUPS内核配置选项控制。
- cpu(since Linux 2.6.24; CONFIG_CGROUP_SCHED)
- 当系统繁忙时,可以保证Cgroup的" CPU共享"数量最少。如果CPU不忙,这不会限制cgroup的CPU使用率。有关更多信息,请参阅Documentation / scheduler / sched-design-CFS.rst(或Linux 5.2及更低版本中的Documentation / scheduler / sched-design-CFS.txt)。
- 在Linux 3.2中,此控制器已扩展为提供CPU"带宽"控制。如果内核配置有CONFIG_CFS_BANDWIDTH,则在每个调度周期(通过cgroup目录中的文件定义)内,可以为分配给cgroup中进程的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 / cgroup-v1 / cpuacct.rst(或Linux 5.2及更低版本中的Documentation / cgroup-v1 / cpuacct.txt)中找到更多信息。
- cpuset(since Linux 2.6.24; CONFIG_CPUSETS)
- 此cgroup可用于将cgroup中的进程绑定到一组指定的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)
- 内存控制器支持报告和限制cgroup使用的进程内存,内核内存和交换。
- 可以在内核源文件Documentation / admin-guide / cgroup-v1 / memory.rst(或Linux 5.2及更低版本中的Documentation / cgroup-v1 / memory.txt)中找到更多信息。
- devices(since Linux 2.6.26; CONFIG_CGROUP_DEVICE)
- 这支持控制哪些进程可以创建(mknod)设备以及打开它们以进行读取或写入。可以将策略指定为允许列表和拒绝列表。强制执行层次结构,因此新规则不得违反目标或祖先cgroup的现有规则。
- 可以在内核源文件Documentation / admin-guide / cgroup-v1 / devices.rst(或Linux 5.2及更低版本中的Documentation / cgroup-v1 / devices.txt)中找到更多信息。
- freezer(since Linux 2.6.28; CONFIG_CGROUP_FREEZER)
- 冷冻机cgroup可以挂起和恢复(恢复)cgroup中的所有进程。冻结cgroup / A还会导致其子级(例如,/ A / B中的进程)被冻结。
- 可以在内核源文件Documentation / admin-guide / cgroup-v1 / freezer-subsystem.rst(或Linux 5.2及更低版本中的Documentation / cgroup-v1 / freezer-subsystem.txt)中找到更多信息。
- net_cls(since Linux 2.6.29; CONFIG_CGROUP_NET_CLASSID)
- 这会将为cgroup指定的classid放在cgroup创建的网络数据包上。然后,可以在防火墙规则中使用这些classid,并使用tc(8)来调整流量。这仅适用于离开cgroup的数据包,不适用于到达cgroup的流量。
- 可以在内核源文件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 cgroup通过对存储层次结构中的叶节点和中间节点进行节流和上限形式的IO控制,来控制和限制对指定块设备的访问。
- 有两种策略。第一个是使用CFQ实现的基于时间的按比例加权磁盘分区。这对于使用CFQ的叶节点有效。第二个是节流策略,它指定设备上的I / O速率上限。
- 可以在内核源文件Documentation / admin-guide / cgroup-v1 / blkio-controller.rst(或Linux 5.2及更低版本中的Documentation / cgroup-v1 / blkio-controller.txt)中找到更多信息。
- perf_event(since Linux 2.6.39; CONFIG_CGROUP_PERF)
- 该控制器允许对cgroup中分组的一组进程进行性能监视。
- 可以在内核源文件中找到更多信息。
- net_prio(since Linux 3.3; CONFIG_CGROUP_NET_PRIO)
- 这样可以为每个网络组指定cgroup的优先级。
- 可以在内核源文件Documentation / admin-guide / cgroup-v1 / net_prio.rst(或Linux 5.2及更低版本中的Documentation / cgroup-v1 / net_prio.txt)中找到更多信息。
- hugetlb(since Linux 3.5; CONFIG_CGROUP_HUGETLB)
- 这支持限制cgroup使用大页面。
- 可以在内核源文件Documentation / admin-guide / cgroup-v1 / hugetlb.rst(或Linux 5.2及更低版本中的Documentation / cgroup-v1 / hugetlb.txt)中找到更多信息。
- pids(since Linux 4.3; CONFIG_CGROUP_PIDS)
- 该控制器允许限制cgroup(及其后代)中可能创建的进程数。
- 可以在内核源文件Documentation / admin-guide / cgroup-v1 / pids.rst(或Linux 5.2和更低版本中的Documentation / cgroup-v1 / pids.txt)中找到更多信息。
- rdma(since Linux 4.11; CONFIG_CGROUP_RDMA)
- RDMA控制器允许限制每个cgroup使用RDMA / IB特定的资源。
- 可以在内核源文件Documentation / admin-guide / cgroup-v1 / rdma.rst(或Linux 5.2和更低版本中的Documentation / cgroup-v1 / rdma.txt)中找到更多信息。
Creating cgroups and moving processes
一个cgroup文件系统最初包含一个单个根cgroup," /",所有进程都属于该根cgroup。通过在cgroup文件系统中创建目录来创建新的cgroup:
mkdir /sys/fs/cgroup/cpu/cg1
这将创建一个新的空cgroup。
通过将进程的PID写入cgroup的cgroup.procs文件,可以将其移至该cgroup:
echo $$ > /sys/fs/cgroup/cpu/cg1/cgroup.procs
一次只能将一个PID写入该文件。
将值0写入cgroup.procs文件会使写入过程移至相应的cgroup。
将PID写入cgroup.procs时,进程中的所有线程都立即移入新的cgroup。
在层次结构中,一个进程可以恰好是一个cgroup的成员。将进程的PID写入cgroup.procs文件会自动将其从以前是其成员的cgroup中删除。
可以读取cgroup.procs文件以获得作为cgroup成员的进程的列表。返回的PID列表不保证顺序正确。也不能保证没有重复。 (例如,当从列表中读取时,可能会回收PID。)
在cgroups v1中,可以通过将单个线程的线程ID(即clone(2)和gettid(2)返回的内核线程ID)写入cgroup目录中的任务文件来将其移至另一个cgroup。可以读取该文件以发现属于cgroup成员的线程集。
Removing cgroups
要删除cgroup,它必须首先没有子cgroup,并且不包含(非僵尸)进程。只要是这种情况,就可以简单地删除相应的目录路径名。请注意,不能并且不需要删除cgroup目录中的文件。
Cgroups v1 release notification
两个文件可用于确定cgroup变空时内核是否提供通知。当cgroup不包含子cgroup和成员进程时,将被视为空。
每个cgroup层次结构的根目录中的特殊文件release_agent可用于注册程序的路径名,当层次结构中的cgroup变为空时可以调用该程序的路径名。调用release_agent程序时,将提供新的空cgroup的路径名(相对于cgroup挂载点)作为唯一的命令行参数。 release_agent程序可能会删除cgroup目录,或者也许用一个进程重新填充它。
release_agent文件的默认值为空,这意味着不调用任何释放代理。
装入cgroup文件系统时,也可以通过mount选项指定release_agent文件的内容:
mount -o release_agent=pathname ...
当特定的cgroup变空时是否调用release_agent程序由相应cgroup目录中notify_on_release文件中的值确定。如果此文件包含值0,则不会调用release_agent程序。如果它包含值1,则调用release_agent程序。根cgroup中此文件的默认值为0。创建新cgroup时,此文件中的值将从父cgroup中的相应文件继承。
Cgroup v1 named hierarchies
在cgroups v1中,可以挂载没有附加控制器的cgroup层次结构:
mount -t cgroup -o none,name=somename none /some/mount/point
可以安装此类层次结构的多个实例。每个层次结构必须具有唯一的名称。这种层次结构的唯一目的是跟踪流程。 (请参阅下面的发行通知讨论。)这方面的一个示例是name = systemd cgroup层次结构,systemd(1)使用该层次结构来跟踪服务和用户会话。
从Linux 5.0开始,通过指定cgroup_no_v1 = named,可以使用cgroup_no_v1内核引导选项(如下所述)禁用cgroup v1命名层次结构。
CGROUPS VERSION 2
在cgroups v2中,所有已安装的控制器都位于一个统一的层次结构中。尽管(不同的)控制器可以同时安装在v1和v2层次结构下,但是不可能同时在v1和v2层次结构下同时安装相同的控制器。
这里总结了cgroups v2中的新行为,并在某些情况下在以下小节中进行了详细说明。
- 1.
- Cgroups v2提供了一个统一的层次结构,所有控制器均根据该层次结构进行安装。
- 2.
- 不允许"内部"过程。除根cgroup外,进程只能驻留在叶节点(本身不包含子cgroup的cgroup)中。详细信息比这更微妙,下面将进行描述。
- 3.
- 必须通过文件cgroup.controllers和cgroup.subtree_control指定活动的cgroup。
- 4.
- 任务文件已被删除。此外,cpuset控制器使用的cgroup.clone_children文件已被删除。
- 5.
- cgroup.events文件提供了一种用于通知空cgroup的改进机制。
有关更多更改,请参阅内核源代码中的Documentation / admin-guide / cgroup-v2.rst文件(或Linux 4.17及更低版本中的Documentation / cgroup-v2.txt)。
上面列出的一些新行为在Linux 4.14中增加了"线程模式"(如下所述),随后进行了修改。
Cgroups v2 unified hierarchy
在cgroups v1中,能够针对不同的层次结构安装不同的控制器的功能旨在为应用程序设计提供极大的灵活性。但是,实际上,灵活性没有预期的有用,并且在许多情况下增加了复杂性。因此,在cgroups v2中,所有可用的控制器都是按单个层次结构安装的。可用的控制器会自动挂载,这意味着使用以下命令挂载cgroup v2文件系统时不必(或不可能)指定控制器:
mount -t cgroup2 none /mnt/cgroup2
仅当当前未通过针对cgroup v1层次结构的安装使用cgroup v2控制器时,该控制器才可用。或者,换句话说,不可能针对v1层次结构和统一的v2层次结构使用相同的控制器。这意味着可能需要先卸载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文件系统时可以指定以下选项(挂载-o):
- nsdelegate(since Linux 4.15)
- 将cgroup名称空间视为委托边界。有关详细信息,请参见下文。
- memory_localevents(since Linux 5.2)
- memory.events应该仅显示cgroup本身的统计信息,而不显示任何后代cgroup的统计信息。这是Linux 5.2之前的行为。从Linux 5.2开始,默认行为是将后代cgroup的统计信息包括在memory.events中,并且此mount选项可用于恢复为旧行为。该选项是系统范围的,只能在初始安装名称空间中进行安装或通过重新安装进行修改;在非初始名称空间中,它将被静默忽略。
Cgroups v2 controllers
内核源文件Documentation / admin-guide / cgroup-v2.rst(或Linux 4.17及更低版本中的Documentation / cgroup-v2.txt)中记录的以下控制器在cgroups版本2中受支持:
- cpu(since Linux 4.15)
- 这是版本1 cpu和cpuacct控制器的后继产品。
- cpuset(since Linux 5.0)
- 这是版本1 cpuset控制器的后继产品。
- freezer(since Linux 5.2)
- 这是版本1冷冻机控制器的后继产品。
- hugetlb(since Linux 5.6)
- 这是版本1 Hugetlb控制器的后继产品。
- io(since Linux 4.5)
- 这是版本1 blkio控制器的后继产品。
- memory(since Linux 4.5)
- 这是版本1内存控制器的后继产品。
- perf_event(since Linux 4.11)
- 这与版本1 perf_event控制器相同。
- pids(since Linux 4.5)
- 这与版本1 pids控制器相同。
- rdma(since Linux 4.11)
- 这与版本1 rdma控制器相同。
没有直接等效于cgroups版本1中的net_cls和net_prio控制器。相反,已对iptables(8)添加了支持,以允许挂接到cgroup v2路径名的eBPF过滤器基于每个cgroup做出有关网络流量的决策。
v2设备控制器不提供接口文件。而是通过将eBPF(BPF_CGROUP_DEVICE)程序附加到v2 cgroup来控制设备控制。
Cgroups v2 subtree control
v2层次结构中的每个cgroup包含以下两个文件:
- cgroup.controllers
- 此只读文件显示此cgroup中可用的控制器的列表。该文件的内容与父cgroup中cgroup.subtree_control文件的内容匹配。
- cgroup.subtree_control
- 这是cgroup中活动(启用)的控制器的列表。此文件中的控制器集是此cgroup的cgroup.controllers中的控制器的子集。通过将字符串写入包含空格分隔的控制器名称的文件来修改活动控制器的集合,每个名称前均带有" +"(启用控制器)或"-"(禁用控制器),如以下示例所示:
echo '+pids -memory' > x/y/cgroup.subtree_control
- 尝试启用cgroup.controllers中不存在的控制器会导致在写入cgroup.subtree_control文件时出现ENOENT错误。
由于cgroup.subtree_control中的控制器列表是这些cgroup.controllers的子集,因此,永远不能在该cgroup下的子树中重新启用已在层次结构中的一个cgroup中禁用的控制器。
cgroup的cgroup.subtree_control文件确定在子cgroup中执行的控制器的集合。当父cgroup的cgroup.subtree_control文件中存在控制器(例如pids)时,则会在该cgroup的子代中自动创建相应的控制器接口文件(例如pids.max),并可用于执行子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中,即-具有非空cgroup.subtree_control文件。因此,一个cgroup可能同时具有成员进程和子cgroup,但是在可以为该cgroup启用控制器之前,必须将成员进程移出cgroup(例如,可能移入子cgroup)。
在Linux 4.14中增加了"线程模式"(如下所述)后,在某些情况下放宽了"无内部进程"规则。
Cgroups v2 cgroup.events file
v2层次结构中的每个非根cgroup都包含一个只读文件cgroup.events,其内容是键值对(用换行符分隔,键和值用空格分隔),提供有关cgroup的状态信息:
$ cat mygrp/cgroup.events populated 1 frozen 0
以下密钥可能会出现在此文件中:
- populated
- 如果此cgroup或其任何后代具有成员进程,则此密钥的值为1,否则为0。
- frozen(since Linux 5.2)
- 如果此cgroup当前处于冻结状态,则此键的值为1;否则,则为0。
可以监视cgroup.events文件,以便在其键之一的值更改时接收通知。可以使用inotify(7)(将更改作为IN_MODIFY事件通知)或poll(2)(通过返回revents字段中的POLLPRI和POLLERR位来通知更改)来进行此类监视。
Cgroup v2 release notification
Cgroups v2提供了一种新的机制,用于在cgroup变空时获取通知。删除了cgroups v1 release_agent和notify_on_release文件,并替换为cgroup.events文件中的填充键。该键的值为0,表示cgroup(及其子代)不包含(非僵尸)成员进程,或者为1,表示cgroup(或其子代之一)包含成员进程。
与cgroups v1 release_agent机制相比,cgroups v2 release-notification机制具有以下优点:
- *
- 由于单个进程可以监视多个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(since 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
在cgroup的上下文中,委派意味着将cgroup层次结构的某些子树的管理传递给非特权用户。 Cgroups v1根据cgroup层次结构中的文件权限为委派提供支持,但包含规则比v2严格(如下所述)。 Cgroups v2通过显式设计支持包含的委派。本节中讨论的重点是cgroups v2中的委派,并在此过程中指出了cgroups v1的一些区别。
为了描述授权,需要一些术语。委托人是拥有父级cgroup的特权用户(即root)。委托者是非特权用户,将被授予在该父cgroup下管理某些子层次结构(称为委托子树)所需的权限。
为了执行委派,委托者通常通过将对象的所有权更改为委托者的用户ID,使委托者可写某些目录和文件。假设我们要委派以(例如)/ dlgt_grp为根的层次结构,并且该cgroup下还没有任何子cgroup,则将以下内容的所有权更改为委托人的用户ID:
- /dlgt_grp
- 更改子树根的所有权意味着,在子树下创建的任何新cgroup(及其包含的文件)也将由委托人拥有。
- /dlgt_grp/cgroup.procs
- 更改此文件的所有权意味着委托人可以将进程移到委托子树的根中。
- /dlgt_grp/cgroup.subtree_control(cgroups v2 only)
- 更改此文件的所有权意味着委托者可以启用控制器(在/dlgt_grp/cgroup.controllers中存在),以便进一步在子树中的较低级别重新分配资源。 (作为更改此文件的所有权的一种替代方法,委托者可以将选定的控制器添加到此文件中。)
- /dlgt_grp/cgroup.threads(cgroups v2 only)
- 如果委派线程子树,则必须更改此文件的所有权(请参见下面的"线程模式"说明)。这允许委托人将线程ID写入文件。 (委派域子树时,也可以更改此文件的所有权,但是目前这没有用,因为如下所述,无法通过将其线程ID写入cgroup来在域cgroup之间移动线程。文件。)
- 在cgroups v1中,应该委派的相应文件是任务文件。
委托人不应更改dlgt_grp中任何控制器接口文件(例如pids.max,memory.high)的所有权。从委派的子树上方的下一层使用这些文件,以便将资源分配到子树中,并且委派者不应具有更改分配给委派的子树的资源的权限。
另请参阅NOTES中有关/ sys / kernel / cgroup / delegate文件的讨论,以获取有关cgroups v2中其他可分发文件的信息。
执行上述步骤后,委托人可以在委托子树中创建子cgroup(cgroup子目录及其包含的文件将由委托人拥有),并在子树中的cgroup之间移动进程。如果dlgt_grp / cgroup.subtree_control中存在某些控制器,或者该文件的所有权已传递给委托人,则委托人还可以控制将相应资源进一步重新分配到委托子树中。
Cgroups v2 delegation: nsdelegate and cgroup namespaces
从Linux 4.13开始,有第二种方法可以在cgroups v2层次结构中执行cgroup委派。这是通过使用nsdelegate挂载选项挂载或重新挂载cgroup v2文件系统来完成的。例如,如果已经安装了cgroup v2文件系统,则可以使用nsdelegate选项重新安装它,如下所示:
mount -t cgroup2 -o remount,nsdelegate \ none /sys/fs/cgroup/unified
该安装选项的作用是使cgroup名称空间自动成为委托边界。更具体地说,以下限制适用于cgroup命名空间中的进程:
- *
- 写入名称空间根目录中的控制器接口文件将失败,并显示错误EPERM。 cgroup命名空间内的进程仍可以写入cgroup命名空间的根目录中的可代理文件,例如cgroup.procs和cgroup.subtree_control,并且可以在根目录下创建子层次结构。
- *
- 试图跨名称空间边界迁移进程的尝试被拒绝(错误ENOENT)。 cgroup命名空间内的进程仍可以(遵循下面描述的包含规则)在命名空间根目录下的子层次结构内的cgroup之间移动进程。
将cgroup命名空间定义为委托边界的能力使cgroup命名空间更加有用。为了理解为什么,假设我们已经有一个cgroup层次结构,已经使用上述较旧的委派技术委派给了非特权用户cecilia。进一步假设塞西莉亚想在现有的委托层次结构中进一步委托一个子层次结构。 (例如,委派的层次结构可能与由塞西莉亚运行的非特权容器相关联。)即使使用了cgroup命名空间,由于两个层次结构均由非特权用户塞西莉亚拥有,因此可以执行以下非法操作:
- *
- 下级层次结构中的进程可能会更改该层次结构根目录中的资源控制器设置。 (这些资源控制器设置旨在允许从父cgroup执行控制;不应允许子cgroup内部的进程对其进行修改。)
- *
- 如果上级层次结构中的cgroup是可见的,则下级层次结构中的进程可以将进程移入或移出下级层次结构。
使用nsdelegate挂载选项可防止这两种可能性。
nsdelegate挂载选项仅在初始挂载名称空间中执行时才有效。在其他安装名称空间中,该选项将被静默忽略。
注意:在某些系统上,systemd(1)自动挂载cgroup v2文件系统。为了试验nsdelegate操作,使用以下命令行选项引导内核可能会很有用:
cgroup_no_v1=all systemd.legacy_systemd_cgroup_controller
这些选项导致内核在禁用cgroups v1控制器的情况下启动(这意味着控制器在v2层次结构中可用),并告诉systemd(1)不要挂载和使用cgroup v2层次结构,以便可以手动设置v2层次结构启动后安装所需的选件。
Cgroup delegation containment rules
一些委托包含规则确保委托人可以在委托子树内的cgroup之间移动进程,但不能将进程从委托子树外部移入子树,反之亦然。仅当满足以下所有条件时,非特权进程(即委托人)才能将"目标"进程的PID写入cgroup.procs文件中:
- *
- 编写者对目标cgroup中的cgroup.procs文件具有写权限。
- *
- 编写者对源cgroup和目标cgroup的最近公共祖先中的cgroup.procs文件具有写许可权。请注意,在某些情况下,最接近的公共祖先可能是源cgroup或目标cgroup本身。对于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
在cgroup v1中不存在的cgroup v2施加的限制包括以下内容:
- *
- 没有线程粒度控制:进程的所有线程必须在同一cgroup中。
- *
- 没有内部进程:一个cgroup不能在子cgroup上同时具有成员进程和运动控制器。
添加这两个限制是因为缺少这些限制已导致cgroups v1中出现问题。特别是,cgroups v1允许cgroup成员资格的线程级粒度的功能对于某些控制器没有意义。 (一个显着的例子是内存控制器:由于线程共享一个地址空间,因此在不同的内存cgroup之间拆分线程是没有意义的。)
尽管在cgroups v2中做出了最初的设计决策,但对于某些控制器(尤其是cpu控制器)仍存在用例,对于这些用例而言,控制的线程级粒度是有意义且有用的。为了适应这些用例,Linux 4.14为cgroups v2添加了线程模式。
线程模式允许以下操作:
- *
- 创建线程子树,其中进程的线程可分布在树内的cgroup上。 (一个线程子树可能包含多个多线程进程。)
- *
- 线程控制器的概念,它可以在线程子树中的cgroup之间分配资源。
- *
- 放宽了"无内部进程规则",以便在线程子树中,cgroup既可以包含成员线程,又可以对子cgroup进行资源控制。
通过添加线程模式,每个非根cgroup现在都包含一个新文件cgroup.type,该文件公开了cgroup的"类型",并且在某些情况下可以用来更改它。该文件包含以下类型值之一:
- domain
- 这是一个普通的v2 cgroup,可提供过程粒度控制。如果某个进程是此cgroup的成员,则该进程的所有线程(根据定义)都在同一cgroup中。这是默认的cgroup类型,其行为与在初始cgroups v2实现中为cgroup提供的行为相同。
- threaded
- 此cgroup是线程子树的成员。可以将线程添加到此cgroup,并可以为cgroup启用控制器。
- domain threaded
- 这是域cgroup,用作线程化子树的根。此cgroup类型也称为"线程根"。
- domain invalid
- 这是处于"无效"状态的线程子树中的cgroup。不能将进程添加到cgroup,也不能为cgroup启用控制器。使用此cgroup唯一可以做的事情(除了删除它)是通过将线程化的字符串写入cgroup.type文件将其转换为线程化的cgroup。
- 在创建线程子树期间(而不是内核直接将线程根下的所有cgroup都立即转换为线程类型)存在此"临时"类型的理由是,允许将来对线程模式模型进行扩展
Threaded versus domain controllers
通过添加线程模式,cgroups v2现在可以区分两种类型的资源控制器:
- *
- 线程控制器:这些控制器支持线程粒度的资源控制,并且可以在线程子树中启用,结果是相应的控制器接口文件出现在线程子树的cgroup中。从Linux 4.19开始,以下控制器采用了线程处理:cpu,perf_event和pids。
- *
- 域控制器:这些控制器仅支持进程粒度以进行资源控制。从域控制器的角度来看,进程的所有线程始终位于同一cgroup中。无法在线程子树中启用域控制器。
Creating a threaded subtree
有两种途径可以创建线程子树。第一条途径如下:
- 1.
- We write the string
threaded
to the
cgroup.typefile of a cgroup
y/zthat currently has the type
domain.This has the following effects:
- *
- cgroup y / z的类型变为线程化。
- *
- 父cgroup的类型y成为域线程。父cgroup是线程子树的根(也称为"线程根")。
- *
- y下所有其他尚未被线程化的cgroup(因为它们在新的线程根下已经存在的线程子树中)被转换为类型域无效。以后在y下创建的所有cgroup的类型域也将无效。
- 2.
- 我们将线程化的字符串写入y下的每个域无效cgroup,以便将它们转换为线程化的类型。此步骤的结果是,线程根下的所有线程现在都具有线程类型,并且线程子树现已完全可用。向这些cgroup中的每一个写入线程的要求有些繁琐,但允许将来对线程模式模型进行扩展。
创建线程子树的第二种方法如下:
- 1.
- In an existing cgroup,
z,
that currently has the type
domain,we (1) enable one or more threaded controllers and
(2) make a process a member of
z.(These two steps can be done in either order.)
This has the following consequences:- *
- z的类型成为域线程。
- *
- x的所有尚未成为线程类型的子代cgroup都将转换为域无效类型。
- 2.
- 如前所述,我们通过将线程化的字符串写入y下的每个域无效cgroup来使线程化子树可用,以便将它们转换为线程化类型。
上述创建线程子树的途径的后果之一是,线程根cgroup只能是线程(且域无效)cgroup的父代。线程根cgroup不能是域cgroup的父级,并且线程cgroup不能具有域cgroup的同级对象。
Using a threaded subtree
在线程子树中,可以在类型更改为线程的每个子组中启用线程控制器。这样,相应的控制器接口文件就会出现在该cgroup的子级中。
通过将进程的PID写入树内cgroup之一中的cgroup.procs文件,可以将其移入线程子树。这具有使相应cgroup的进程成员中的所有线程都具有作用,并使该进程成为线程子树的成员的作用。然后,通过将线程的线程ID(请参阅gettid(2))写入子树内不同cgroup中的cgroup.threads文件中,可以将进程的线程分布在整个线程子树中。进程的线程必须全部驻留在同一线程子树中。
与写入cgroup.procs一样,在写入cgroup.threads文件时也会应用一些包含规则:
- *
- 编写者必须对目标cgroup中的cgroup.threads文件具有写许可权。
- *
- 编写者必须对源cgroup和目标cgroup的公共祖先中的cgroup.procs文件具有写许可权。 (在某些情况下,公共祖先可以是源cgroup或目标cgroup本身。)
- *
- 源cgroup和目标cgroup必须在同一线程子树中。 (在线程子树之外,通过将其线程ID写入另一个域cgroup中的cgroup.threads文件来移动线程的尝试失败,并显示错误EOPNOTSUPP。)
cgroup.threads文件存在于每个cgroup(包括域cgroup)中,可以被读取以发现cgroup中存在的线程集。读取此文件时获得的一组线程ID不能保证是有序的或没有重复的。
线程根目录中的cgroup.procs文件显示了作为线程子树成员的所有进程的PID。子树中其他cgroup中的cgroup.procs文件不可读。
不能在线程子树中启用域控制器。没有控制器接口文件出现在线程根目录下的cgroup内。从域控制器的角度来看,线程子树是不可见的:线程子树中的多线程进程作为驻留在线程根cgroup中的进程出现在域控制器中。
在线程子树中,"无内部进程"规则不适用:cgroup可以在子cgroup上包含成员进程(或线程)和运动控制器。
Rules for writing to cgroup.type and creating threaded subtrees
写入cgroup.type文件时,有许多规则适用:
- *
- 只能写入线程字符串。换句话说,唯一可能的显式转换是将域cgroup转换为线程类型。
- *
- The effect of writing
threaded
depends on the current value in
cgroup.type,as follows:
- *
- 域或域线程化:通过上述第一种途径开始创建线程化子树(其根是该cgroup的父级);
- *
- 域无效:将这个cgroup(位于线程子树中)转换为可用(即线程)状态;
- *
- 线程化:无效("无操作")。
- *
- 如果父级的类型是域无效的,则无法写入cgroup.type文件。换句话说,必须以自顶向下的方式将线程子树的cgroup转换为线程状态。
为了创建以cgroup x为根的线程子树,还必须满足一些约束:
- *
- x的后代cgroup中不能有成员进程。 (cgroup x本身可以具有成员进程。)
- *
- x的cgroup.subtree_control文件中可能没有启用任何域控制器。
如果违反了上述任何约束,则尝试将线程写入cgroup.type文件失败,并显示错误ENOTSUP。
The domain threaded cgroup type
根据上述途径,在以下两种情况下,cgroup的类型都可以更改为域线程:
- *
- 线程化的字符串被写入子cgroup。
- *
- 在cgroup内启用了线程控制器,并使进程成为cgroup的成员。
如果上述条件不再成立,则域线程cgroup x可以还原为类型域-即,如果删除了x的所有线程子cgroup,并且x不再启用线程控制器或不再具有成员流程。
当域线程化的cgroup x恢复为类型domain时:
- *
- x的所有不在低级线程子树中的域无效后代都将还原为类型域。
- *
- 任何较低级别的线程子树中的根cgroup都将还原为线程的类型域。
Exceptions for the root cgroup
v2层次结构的根cgroup受到特殊对待:它可以是域cthread组和线程cgroup的父级。如果将线程化的字符串写入根cgroup子级之一的cgroup.type文件中,则
- *
- 该cgroup的类型变为线程化。
- *
- 该cgroup中不属于较低级别线程子树的任何后代的类型将更改为域无效。
请注意,在这种情况下,没有cgroup的类型成为域线程。 (通常,根cgroup可以视为类型更改为线程的cgroup的线程根。)
对根cgroup进行这种特殊处理的目的是允许将使用cpu控制器的线程化cgroup放置在层次结构中尽可能高的位置,以最小化遍历cgroup层次结构的(小)成本。
The cgroups v2 cpu controller and realtime threads
从Linux 4.19开始,cgroups v2 cpu控制器不支持对实时线程的控制(特别是在SCHED_FIFO,SCHED_RR中的任何策略下调度的线程,在SCHED_DEADLINE中进行了描述;请参见sched(7))。因此,仅当所有实时线程都在根cgroup中时,才可以在根cgroup中启用cpu控制器。 (如果非根cgroup中有实时线程,则将字符串+ cpu写入(2)cgroup.subtree_control文件失败,并显示错误EINVAL。)
在某些系统上,systemd(1)将某些实时线程放置在v2层次结构的非根cgroup中。在此类系统上,必须先将这些线程移至根cgroup,然后才能启用cpu控制器。
备注
通过fork(2)创建的子进程继承其父级的cgroup成员资格。进程的cgroup成员资格在execve(2)中保留。
clone3(2)CLONE_INTO_CGROUP标志可用于创建子进程,该子进程从与父进程不同的版本2 cgroup开始其生命。
/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
- The fields in this file are, from left to right:
- 1.
- 控制器的名称。
- 2.
- The unique ID of the cgroup hierarchy on which this controller is mounted.
If multiple cgroups v1 controllers are bound to the same hierarchy,
then each will show the same hierarchy ID in this field.
The value in this field will be 0 if:
- a)
- 控制器未安装在cgroups v1层次结构上;
- b)
- 控制器绑定到cgroups v2单一统一层次结构;要么
- c)
- 控制器被禁用(见下文)。
- 3.
- 使用此控制器的此层次结构中的控制组数。
- 4.
- 如果启用了此控制器,则此字段包含值1;如果已禁用(通过cgroup_disable内核命令行引导参数),则该字段包含值0。
- /proc/[pid]/cgroup(since Linux 2.6.24)
- 该文件描述了具有相应PID的过程所属的控制组。对于cgroup版本1和版本2层次结构,显示的信息有所不同。
- 对于该进程所属的每个cgroup层次结构,都有一个条目包含三个冒号分隔的字段:
hierarchy-ID:controller-list:cgroup-path
- 例如:
5:cpuacct,cpu,cpuset:/daemons
- The colon-separated fields are, from left to right:
- 1.
- 对于cgroups版本1层次结构,此字段包含可以与/ proc / cgroups中的层次结构ID匹配的唯一层次结构ID号。对于cgroups版本2层次结构,此字段包含值0。
- 2.
- 对于cgroups版本1层次结构,此字段包含以逗号分隔的绑定到层次结构的控制器的列表。对于cgroups版本2层次结构,此字段为空。
- 3.
- 该字段包含进程所属层次结构中控制组的路径名。此路径名是相对于层次结构的安装点的。
/sys/kernel/cgroup files
- /sys/kernel/cgroup/delegate(since Linux 4.15)
- 此文件导出可分发的cgroup v2文件的列表(每行一个)(即,其所有权应更改为委托人的用户ID)。将来,可委派文件的集合可能会更改或增长,并且此文件为内核提供了一种方法,以通知内核必须通知哪些文件必须委托给用户空间应用程序。从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
- The entries that can appear in this file are:
- memory_localevents(since Linux 5.2)
- 内核支持memory_localevents挂载选项。
- nsdelegate(since Linux 4.15)
- 内核支持nsdelegate挂载选项。
另外参见
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),名称空间(7),sched(7),user_namespaces(7)
内核源文件Documentation / admin-guide / cgroup-v2.rst。
出版信息
这个页面是Linux手册页项目5.08版的一部分。有关项目的说明、有关报告错误的信息以及此页面的最新版本,请访问https://www.kernel.org/doc/man-pages/。