cgroup文档

cgroupv2是Linux内核中的一个机制,用于分层组织进程并控制资源分配。它由核心和控制器组成,支持线程级别的粒度控制。资源分配包括权重、边界、保护和分配模式。控制器如CPU、内存和IO管理着各自的资源,每个控制器有特定的接口文件用于设置和监控。cgroupv2还引入了命名空间的概念,允许进程看到受限的cgroup视图,增强了隔离性和资源管理的灵活性。
摘要由CSDN通过智能技术生成

这是关于cgroup v2的设计、接口和约定的权威文档。它描述了cgroup的所有用户和可见方面,包括核心和特定的控制器行为。所有未来的变化都必须反映在本文件中。v1的文档可在Documentation/admin guide/cgroup-v1/index.rst下找到。

1 介绍

术语

“cgroup”代表“对照组”,从不大写。单数形式用于指定整个特征,也用作“cgroup controllers”中的限定符。当明确提及多个单独的对照组时,使用复数形式“cgroups”。

1.1 什么是cgroup?

cgroup是一种分层组织过程并以可控和可配置的方式沿分层分布系统资源的机制。

cgroup主要由核心和控制器两部分组成。cgroup核心主要负责分层组织流程。cgroup控制器通常负责沿着层次结构分配特定类型的系统资源,尽管存在用于资源分配以外的目的的实用程序控制器。

c组形成了一个树状结构,系统中的每个进程都属于一个且仅属于一个cgroup。一个进程的所有线程都属于同一个cgroup。在创建时,所有进程都被放在父进程当时所属的cgroup中。一个进程可以被迁移到另一个cgroup。进程的迁移并不影响已经存在的后继进程。

根据某些结构限制,控制器可以有选择地在一个cgroup上启用或禁用。所有的控制器行为都是分层的–如果一个控制器在一个c组上被启用,它将影响所有属于cgroup的进程,包括cgroup的子层级。当一个控制器在一个嵌套的c组上被启用时,它总是进一步限制资源分配。在层次结构中靠近根部的限制不能被更远的地方所覆盖。

2 基本操作

2.1 挂载

与v1不同,cgroup v2只有单一的层次结构。cgroup v2的层次结构可以用以下mount命令挂载:

# mount -t cgroup2 none $MOUNT_POINT

cgroup2文件系统有一个神奇的数字0x63677270(“cgrp”)。所有支持v2并且没有绑定到v1层级的控制器都会自动绑定到v2层级并显示在根部。在v2层级中没有被激活使用的控制器可以被绑定到其他层级中。这允许以完全向后兼容的方式将v2层次与传统的v1多层次混合。

只有在控制器在其当前层次结构中不再被引用后,才可以跨层次结构移动控制器。因为每个cgroup的控制器状态是异步销毁的,而且控制器可能会有挥之不去的引用,所以在上一个层次结构的最后卸载之后,控制器可能不会立即出现在v2层次结构中。同样,一个控制器应该被完全禁用,才能从统一的层次结构中移出,禁用的控制器可能需要一些时间才能被其他层次结构使用;此外,由于控制器之间的依赖性,其他控制器可能也需要被禁用。

虽然对开发和手动配置很有用,但在生产中强烈反对在v2和其他层次之间动态移动控制器。建议在系统启动后开始使用控制器之前决定层次结构和控制器关联。

在过渡到v2的过程中,系统管理软件可能仍然会自动挂载v1的cgroup文件系统,因此在启动时劫持了所有的控制器,然后才有可能进行人工干预。为了使测试和实验更容易,内核参数cgroup_no_v1=允许禁用v1版的控制器,使它们在v2版中始终可用。

cgroup v2目前支持以下挂载选项。

  • nsdelegate 将cgroup命名空间视为委托边界。这个选项是系统范围内的,只能在挂载时设置或通过从初始命名空间重新挂载来修改。在非初始命名空间的挂载中,挂载选项被忽略。详情请参考委托部分。
  • favordynmods 减少动态cgroup修改的延迟,如任务迁移和控制器开启/关闭,代价是使fork和exit等热路径操作更加昂贵。创建一个cgroup,启用控制器,然后用CLONE_INTO_CGROUP播种的静态使用模式不受此选项影响。
  • memory_localevents 只用当前cgroup的数据来填充memory.events,而不是任何子树。这是传统的行为,没有这个选项的默认行为是包括子树计数。这个选项是系统范围内的,只能在挂载时设置或通过从init命名空间重新挂载来修改。在非初始命名空间的挂载中,挂载选项被忽略。
  • memory_recursiveprot 递归地将memory.min和memory.low保护应用于整个子树,而不需要明确地向下传播到叶子cgroups。这使得整个子树相互保护,同时保留了这些子树内的自由竞争。这本应是默认行为,但这是一个挂载选项,以避免依赖原始语义的设置倒退(例如,在更高的树层指定虚假的高 "旁路 "保护值)。

2.2 组织进程和线程

2.3 进程

理论上说,只有根cgroup存在,所有进程都属于它。可以通过创建一个子目录来创建一个子c组:

# mkdir $CGROUP_NAME

一个特定的cgroup可以有多个子cgroup,形成一个树状结构。每个cgroup组有一个可读可写的接口文件 “cgroup.procs”。读取时,它每行列出属于该cgroup的所有进程的PIDs。PIDs是没有顺序的,如果进程被移到另一个cgroup然后又被移回来,或者PID在读取时被回收,同一个PID可能出现多次。

一个进程可以通过将其PID写入目标cgroup的 "cgroup.procs "文件而被迁移到一个cgroup中。在一次write(2)调用中,只能迁移一个进程。如果一个进程是由多个线程组成的,写入任何一个线程的PID都会迁移该进程的所有线程。

当一个进程分叉一个子进程时,新的进程会诞生在分叉进程在操作时所属的c组中。在退出后,一个进程保持与退出时所属的c组相关联,直到它被收割;然而,一个僵尸进程不会出现在 "cgroup.procs "中,因此不能被转移到另一个c组。

一个没有任何子代或活进程的c组可以通过删除目录来销毁。请注意,一个没有任何子代、只与僵尸进程相关的c组被认为是空的,可以被删除:

# rmdir $CGROUP_NAME

"/proc/$PID/cgroup "列出一个进程的cgroup成员。如果系统中使用的是传统的cgroup,这个文件可能包含多行,每个层次都有一个。cgroup v2的条目总是以 "0::$PATH "的格式出现:

# cat /proc/842/cgroup
...
0::/test-cgroup/test-cgroup-nested

如果进程成为僵尸,并且它所关联的c组随后被删除,"(删除)"将被附加到路径上:

# cat /proc/842/cgroup
...
0::/test-cgroup/test-cgroup-nested (deleted)

2.4 线程

cgroup v2 支持控制器子集的线程粒度,以支持需要在一组进程的线程中进行分层资源分配的用例。默认情况下,一个进程的所有线程都属于同一个cgroup,它也作为资源域来承载非特定于某个进程或线程的资源消耗。线程模式允许线程分布在一个子树上,同时仍然保持它们的共同资源域。

支持线程模式的控制器被称为线程控制器。不支持的称为域控制器。

将一个分组标记为线程化,使其作为一个线程化分组加入其父级的资源域。父组可能是另一个线程化的cgroup,其资源域在层次结构中更靠前。线程化子树的根,也就是离它最近的未被线程化的祖先,被称为线程化域或线程根,可以互换,并作为整个子树的资源域。

在一个线程子树内,一个进程的线程可以放在不同的c组中,并且不受没有内部进程的约束–线程控制器可以在非叶子c组上启用,无论它们中是否有线程。

由于线程域cgroup承载了子树的所有域资源消耗,无论其中是否有进程,它都被认为有内部资源消耗,并且不能有填充的非线程的子cgroup。因为根cgroup不受没有内部进程的约束,所以它既可以作为一个线程域,也可以作为域cgroup的父级。

cgroup的当前操作模式或类型显示在 "cgroup.type "文件中,它表明cgroup是一个普通域,一个作为线程子树的域,还是一个线程cgroup。

在创建时,一个cgroup总是一个域cgroup,可以通过在 "cgroup.type "文件中写入 "threaded "来使其成为线程。该操作是单方向的:

# echo threaded > cgroup.type

一旦线程化,c组就不能再成为一个域了。要启用线程模式,必须满足以下条件。

  • 由于cgroup将加入父方的资源域。父级必须是一个有效的(线程)域或一个线程的cgroup。
  • 当父级是一个无线程的域时,它必须没有任何启用的域控制器或填充的域子。根目录不受此要求影响。

从拓扑结构来看,一个c组可能处于无效状态。请考虑以下拓扑结构:

A (threaded domain) - B (threaded) - C (domain, just created)

C被创建为一个域,但并没有连接到一个可以承载子域的父域。C不能被使用,直到它被变成一个线程的cgroup。"cgroup.type "文件在这些情况下会报告 “域(无效)”。由于无效的拓扑结构而导致的操作失败,使用EOPNOTSUPP作为错误代码。

当一个域的c组中的一个子c组成为线程域,或者当c组中有进程时,在 "cgroup.subtree_control "文件中启用了线程控制器,该域就变成了一个线程域。当条件清除时,一个线程域会恢复为一个正常的域。

读取时,"cgroup.threads "包含cgroup中所有线程ID的列表。除了操作是按线程而不是按进程,"cgroup.threads "的格式和行为方式与 "cgroup.procs "相同。虽然 "cgroup.threads "可以在任何cgroup中被写入,但由于它只能在同一线程域内移动线程,它的操作被限制在每个线程子树内。

线程域cgroup作为整个子树的资源域,虽然线程可以分散在子树上,但所有进程都被认为是在线程域cgroup中。线程域cgroup中的 "cgroup.procs "包含子树中所有进程的PID,在子树中是不可读的。然而,"cgroup.procs "可以从子树的任何地方被写入,以将匹配进程的所有线程迁移到cgroup中。

只有线程子树中的线程控制器可以被启用。当一个线程控制器在一个线程子树中被启用时,它只核算和控制与c组及其子孙线程相关的资源消耗。所有不与特定线程绑定的消耗都属于线程域cgroup。

因为线程子树不受任何内部进程的约束,所以线程控制器必须能够处理非叶子c组中的线程和其子c组之间的竞争。每个线程控制器都定义了如何处理这种竞争。

2.5 [未]填充的通知

每个非根的cgroup都有一个 "cgroup.events "文件,其中包含 "populated "字段,表示cgroup的子层级中是否有活进程。如果cgroup及其子层中没有活进程,其值为0;否则为1。轮询和[id]通知事件在值发生变化时被触发。。例如,这可以用来在一个给定的子层次的所有进程退出后启动一个清理操作。填充的状态更新和通知是递归的。考虑以下子层次,括号中的数字代表每个c组中的进程数:

A(4) - B(0) - C(1)
            \ D(0)

A、B和C的 "填充 "字段将为1,而D为0。在C的一个进程退出后,B和C的 "填充 "字段将翻转为 “0”,文件修改事件将在两个c组的 "cgroup.events "文件中产生。

2.6 控制控制器

2.7 启用和禁用

每个cgroup都有一个 "cgroup.controllers "文件,其中列出了所有可供cgroup启用的控制器:

# cat cgroup.controllers
cpu io memory

默认情况下,没有启用任何控制器。可以通过写入 "cgroup.subtree_control "文件来启用和禁用控制器:

# echo "+cpu +memory -io" > cgroup.subtree_control

只有在 "cgroup.controllers "中列出的控制器可以被启用。当如上指定多个操作时,要么全部成功,要么失败。如果在同一个控制器上指定了多个操作,最后一个操作是有效的。

在c组中启用一个控制器,表明目标资源在其紧邻的子组中的分配将被控制。考虑一下下面的子结构。启用的控制器列在括号里:

A(cpu,memory) - B(memory) - C()
                          \ D()

由于A启用了 "cpu "和 “memory”,A将控制CPU周期和内存分配给它的子女,在这种情况下是B。由于B启用了 “memory”,但没有启用 “CPU”,C和D将在CPU周期上自由竞争,但它们对B可用内存的分割将受到控制。

由于控制器调节目标资源分配到c组的子组,启用它在子c组中创建控制器的接口文件。在上面的例子中,在B上启用 "cpu "将在C和D中创建以 "cpu. "为前缀的控制器接口文件。同样,在B上禁用 "memory "将从C和D中删除以 "memory. "为前缀的控制器接口文件。这意味着控制器接口文件–任何不以 "cgroup. "开头的文件都是由父组而不是c组本身拥有。

2.8 自上而下的约束

资源是自上而下分配的,只有当资源从父级分配到它时,cgroup才能进一步分配资源。这意味着所有非根目录的 "cgroup.subtree_control "文件只能包含在父目录的 "cgroup.subtree_control "文件中被启用的控制器。只有当父代启用了控制器,才能启用该控制器;如果一个或多个子代启用了控制器,则不能禁用该控制器。

2.9 无内部流程约束

非root cgroup只有在自己没有任何进程的情况下才能将域资源分配给他们的子群。换句话说,只有不包含任何进程的域c组才能在其 "cgroup.subtree_control "文件中启用域控制器。

这保证了当一个域控制器在查看层次结构中启用了它的部分时,进程总是只在叶子上。这就排除了子代c组与父代的内部进程竞争的情况。

根c组不受此限制。根包含进程和匿名的资源消耗,不能与任何其他c组相关联,需要大多数控制器的特殊处理。如何管理根c组的资源消耗由每个控制器决定(关于这个主题的更多信息,请参考控制器章节的非规范信息部分)。

请注意,如果cgroup的 "cgroup.subtree_control "中没有启用的控制器,那么这个限制就不会碍事了。这一点很重要,否则就不可能创建一个已填充的cgroup的子代。为了控制cgroup的资源分配,cgroup必须在 "cgroup.subtree_control "文件中启用控制器之前创建子代,并将其所有进程转移到子代。

2.10 代表

2.11 授权模式

一个cgroup可以通过两种方式被委托。首先,将目录及其 “cgroup.procs”、"cgroup.threads "和 "cgroup.subtree_control "文件的写入权授予权限较低的用户。第二,如果设置了 "nsdelegate "挂载选项,在创建命名空间时自动挂载到一个cgroup命名空间。

因为给定目录中的资源控制接口文件控制着父方资源的分配,所以不应该允许被委托人对其进行写入。对于第一种方法,这可以通过不授予对这些文件的访问来实现。对于第二种方法,内核拒绝从命名空间内部写到命名空间根上除 "cgroup.procs "和 "cgroup.subtree_control "以外的所有文件。

两种委托类型的最终结果是相等的。一旦委托,用户可以在目录下建立子层次,在其内部按其认为合适的方式组织进程,并进一步分配其从父方收到的资源。所有资源控制器的限制和其他设置都是分层次的,无论在被委托的子hierarchy中发生什么,都无法逃脱父方施加的资源限制。

目前,cgroup对委托的子层次中的cgroup数量或嵌套深度没有任何限制;但是,这在将来可能会被明确限制。

2.12 授权遏制

委托的子层次是包含的,即流程不能被被委托人移入或移出该子层次。

对于授权给一个权限较低的用户,这是通过要求具有非root euid的进程通过将目标进程的PID写入 "cgroup.procs "文件而将其迁移到cgroup中的以下条件来实现的。

  • 写作者必须有写 "cgroup.procs "文件的权限。

  • 写作者必须有写入源组和目的组的共同祖先的 "cgroup.procs "文件的权限。

上述两个约束条件确保受委托人可以在被委托的子层次中自由迁移进程,但它不能从子层次之外拉入或推出。

举个例子,我们假设c组C0和C1已经委托给用户U0,他在C0下创建了C00、C01,在C1下创建了C10,如下所示,C0和C1下的所有进程都属于U0:

~~~~~~~~~~~~~ - C0 - C00
~ cgroup    ~      \ C01
~ hierarchy ~
~~~~~~~~~~~~~ - C1 - C10

我们还假设U0想把目前在C10中的一个进程的PID写入 "C00/cgroup.procs "中。U0对该文件有写入权限;然而,源c组C10和目的c组C00的共同祖先在委托点之上,U0不会对其 "cgroup.procs "文件有写入权限,因此写入将被-EACCES拒绝。

对于向命名空间的委托,遏制是通过要求源c组和目的c组都可以从尝试迁移的进程的命名空间到达来实现的。如果任何一个都不能到达,那么迁移就会被拒绝,并给出-ENOENT。

2.13 准则

2.14 一次性组织和控制

在c组之间迁移进程是一个相对昂贵的操作,而且诸如内存等有状态的资源不会与进程一起迁移。这是一个明确的设计决定,因为在同步成本方面,迁移和各种热路径之间往往存在固有的权衡。

因此,不鼓励频繁地在c组之间迁移进程作为应用不同资源限制的手段。一旦启动,应根据系统的逻辑和资源结构将工作负载分配给一个c组。对资源分配的动态调整可以通过界面文件改变控制器的配置来实现。

2.15 避免名称冲突

一个c组的界面文件和它的子c组占据了同一个目录,有可能创建与界面文件相冲突的子c组。

所有cgroup核心接口文件的前缀是 “cgroup.”,每个控制器的接口文件的前缀是控制器名称和一个点。控制器的名字由小写字母和’‘组成,但绝不会以’'开头,所以它可以作为避免碰撞的前缀字符。另外,接口文件名不会以工作负载分类中经常使用的术语开头或结尾,如作业、服务、片断、单元或工作负载。

cgroup没有做任何事情来防止名字的碰撞,避免这种碰撞是用户的责任。

3 资源分配模式

cgroup控制器根据资源类型和预期的使用情况,实施几种资源分配方案。本节描述了使用中的主要方案以及它们的预期行为。

3.1 权重

父方资源的分配方式是将所有活动子女的权重相加,并给予每个子女与其权重之比相匹配的分数。由于目前只有能够使用资源的子女参与分配,这是对工作的保护。由于其动态性质,这种模式通常用于无状态资源。

所有的权重都在[1, 10000]范围内,默认为100。这允许在足够精细的颗粒度上实现两个方向的对称乘法偏差,同时保持在直观的范围内。

只要权重在范围内,所有的配置组合都是有效的,没有理由拒绝配置变更或流程迁移。

"cpu.weight "将CPU周期按比例分配给活跃的儿童,是这种类型的一个例子。

3.2 边界

子女只能消耗配置好的资源量。限制可以被过度消耗–子代的限制之和可以超过父代的可用资源量。

限制的范围是[0, max],默认为 “max”,也就是noop。

由于限制可以被过度承诺,所有的配置组合都是有效的,没有理由拒绝配置变更或流程迁移。

"io.max "限制了一个c组在一个IO设备上可以消耗的最大BPS和/或IOPS,是这种类型的一个例子。

3.3 保护措施

只要一个c组的所有祖先的使用量都在其保护水平之下,该c组就会被保护到资源的配置量。保护可以是硬保证,也可以是尽力而为的软界限。保护也可以是过度承诺的,在这种情况下,在子代中只保护到父代可用的数量。

保护的范围是[0, max],默认为0,也就是noop。

由于保护可以被过度承诺,所有的配置组合都是有效的,没有理由拒绝配置变更或流程迁移。

"memory.low "实现了最佳努力的内存保护,是这种类型的一个例子。

3.4 分配

一个c组被独家分配一定数量的有限资源。分配不能过度–子代的分配量之和不能超过父代的可用资源量。

分配的范围是[0, max],默认为0,也就是没有资源。

由于分配不能被过度承诺,一些配置组合是无效的,应该被拒绝。另外,如果该资源是执行进程所必须的,那么进程的迁移可能会被拒绝。

"cpu.rt.max "硬性分配了实时片,是这种类型的一个例子。

4 接口文件

4.1 格式

所有接口文件应尽可能采用以下格式之一:

New-line separated values
(when only one value can be written at once)

      VAL0\n
      VAL1\n
      ...

Space separated values
(when read-only or multiple values can be written at once)

      VAL0 VAL1 ...\n

Flat keyed

      KEY0 VAL0\n
      KEY1 VAL1\n
      ...

Nested keyed

      KEY0 SUB_KEY0=VAL00 SUB_KEY1=VAL01...
      KEY1 SUB_KEY0=VAL10 SUB_KEY1=VAL11...
      ...

对于一个可写的文件,写的格式一般应与读的格式一致;然而,控制器可能允许省略后面的字段,或为大多数常见的使用情况实施限制性的快捷方式。

对于平面和嵌套的键控文件,每次只能写一个键的值。对于嵌套键控文件,子键对可以以任何顺序指定,而且不一定要指定所有键对。

4.2 协议

  • 单一特征的设置应包含在一个文件中。

  • 根c组应该免于资源控制,因此不应该有资源控制接口文件。

  • 默认的时间单位是微秒。如果使用不同的单位,必须有一个明确的单位后缀。

  • 每份数量应使用百分比小数,至少有两个数字的小数部分,如13.40。

  • 如果一个控制器实现了基于权重的资源分配,其接口文件应命名为 “权重”,其范围为[1, 10000],默认为100。这些值的选择是为了让两个方向都有足够的和对称的偏向,同时保持直观(默认是100%)。

  • 如果一个控制器实现了绝对资源保证和/或限制,接口文件应分别命名为 "min "和 “max”。如果一个控制器实现了最佳努力资源保证和/或限制,接口文件应分别命名为 "低 "和 “高”。

    在上述四个控制文件中,无论是读还是写,都应使用特殊标记 "max "来表示向上的无穷大。

  • 如果一个设置有一个可配置的默认值和键入的特定重写,默认条目应该用 "default "键入,并作为文件中的第一个条目出现。

    可以通过写 "default V A L " 或 " VAL "或" VAL""VAL "来更新默认值。

    当写入更新一个特定的覆盖项时,"default "可以被用作表示移除覆盖项的值。读取时,以 "default "为值的覆盖条目不得出现。

    例如,一个以整数值的主要设备号:次要设备号为关键的设置可能看起来像这样:

    # cat cgroup-example-interface-file
    default 150
    8:0 300
    

​ 默认值可以通过以下方式更新:

# echo 125 > cgroup-example-interface-file

​ 或者

# echo "default 125" > cgroup-example-interface-file

覆盖可以通过以下方式设置:

# echo "8:16 170" > cgroup-example-interface-file

并通过以下方式进行清理:

# echo "8:0 default" > cgroup-example-interface-file
# cat cgroup-example-interface-file
default 125
8:16 170

对于频率不是很高的事件,应该创建一个界面文件 “events”,列出事件的键值对。每当一个应注意的事件发生时,应该在该文件上生成文件修改事件。

4.3 核心接口文件

所有cgroup核心文件都以 "cgroup "为前缀。

cgroup.type先跳

5 控制器

5.1 CPU

cpu "控制器调节CPU周期的分配。这个控制器为正常调度策略实现了权重和绝对带宽限制模型,为实时调度策略实现了绝对带宽分配模型。

在上述所有模型中,周期分布只定义在时间基础上,它不考虑任务执行的频率。对利用率箝制的支持(可选)允许提示schedutil cpufreq治理器关于最小期望频率,它应该总是由CPU提供,以及最大期望频率,它不应该被CPU超过。

警告:cgroup2还不支持对实时进程的控制,只有当所有的RT进程都在根cgroup中时才能启用cpu控制器。请注意,在系统启动过程中,系统管理软件可能已经将RT进程放到了非根c组中,在cpu控制器启用之前,这些进程可能需要被移到根c组中。

5.2 CPU接口文件

所有的时间长度都是以微秒为单位。

cpu.stat 以下省略

5.3 内存

内存 "控制器调节内存的分配。内存是有状态的,同时实现了限制和保护模型。由于内存使用和回收压力之间的交织以及内存的有状态性,分配模型相对复杂。

虽然不是完全无懈可击,但一个给定的c组的所有主要内存使用都被跟踪,这样就可以在合理的范围内计算和控制总内存消耗。目前,以下类型的内存使用被跟踪。

  • 用户区内存 - 页面缓存和匿名内存。
  • 内核数据结构,如dentries和inodes。
  • TCP套接字缓冲区。

上述名单将来可能会扩大,以便更好地覆盖。

5.4 内存接口文件

所有的内存量都是以字节为单位。如果写了一个不与PAGE_SIZE对齐的值,当读回时,该值可能被四舍五入为最接近的PAGE_SIZE倍数。

Memory.current 后面省略

5.5 使用指南

"memory.high "是控制内存使用的主要机制。在高限上过度投入(高限之和>可用内存),让全局内存压力根据使用情况分配内存是一个可行的策略。

因为违反高限值不会触发OOM杀手,而是对违规的c组进行节流,所以管理代理有充分的机会进行监控并采取适当的行动,如授予更多的内存或终止工作负载。

确定一个c组是否有足够的内存并非易事,因为内存的使用并不能说明工作负载是否能从更多的内存中受益。例如,一个将从网络收到的数据写入文件的工作负载可以使用所有可用的内存,但也可以用少量的内存进行操作。衡量内存压力–工作负载由于缺乏内存而受到多大的影响–对于确定工作负载是否需要更多的内存是必要的;不幸的是,内存压力监测机制还没有实现。

5.6 内存所有权

一个内存区域被记入实例化它的c组,并一直记入该c组,直到该区域被释放。将一个进程迁移到一个不同的c组并不会将它在前一个c组中实例化的内存使用转移到新的c组中。

一个内存区域可能被属于不同c组的进程使用。该区域将被记入哪个c组是不确定的;然而,随着时间的推移,该内存区域很可能最终被记入一个有足够内存空间的c组,以避免高回收压力。

如果一个cgroup清扫了相当数量的内存,并且预计会被其他cgroup反复访问,那么使用POSIX_FADV_DONTNEED来放弃属于受影响文件的内存区域的所有权以确保正确的内存所有权可能是有意义的。

5.7 IO

io "控制器调节IO资源的分配。这个控制器实现了基于权重和绝对带宽或IOPS限制的分配;然而,基于权重的分配只有在使用cfq-iosched时才可用,而对于blk-mq设备,这两种方案都不可用。

5.8 IO接口文件

Io.stat以下省略

5.9 回写

页缓存通过缓冲写入和共享mmaps被弄脏,并通过回写机制异步写入支持的文件系统。回写位于内存和IO域之间,通过平衡脏化和写IO来调节脏内存的比例。

io控制器与内存控制器一起,实现了对页缓存回写IO的控制。内存控制器定义了计算和维护脏内存比率的内存域,io控制器定义了为内存域写出脏页的io域。全系统和每个组的脏内存状态都会被检查,这两种状态中更严格的被执行。

cgroup回写需要底层文件系统的明确支持。目前,cgroup回写在ext2、ext4、btrfs、f2fs和xfs上实现。在其他文件系统中,所有的回写IO都归于根cgroup。

在内存和回写管理中存在固有的差异,这影响了对c组所有权的跟踪方式。内存是按页追踪的,而回写是按节点追踪的。为了回写的目的,一个节点被分配给一个c组,所有从该节点写入脏页的IO请求都归于该c组。

由于内存的c组所有权是按页追踪的,可能会有一些页面与不同的c组相关联,而不是与inode相关联。这些被称为外来页。回写器不断地跟踪外来页,如果一个特定的外来c组在一定时间内成为多数,则将节点的所有权切换到该c组。

虽然这种模式对于大多数用例来说是足够的,在这些用例中,即使主要的写入组随时间变化,一个给定的节点主要是由一个单一的cgroup所占用,但是对于多个cgroup同时向一个节点写入的用例,则不能很好地支持。在这种情况下,很大一部分IO可能会被错误地归属。由于内存控制器在第一次使用时分配了页面所有权,并且在页面被释放之前不会更新它,即使回写严格遵循页面所有权,多个c组弄脏重叠的区域也不会像预期的那样工作。建议避免这种使用模式。

影响回写行为的sysctl旋钮适用于cgroup回写,如下所示。

以下省略

5.10 IO延迟

这是一个用于保护IO工作负载的cgroup v2控制器。您为一个组提供一个延迟目标,如果平均延迟超过该目标,控制器将节流任何延迟目标低于受保护工作负载的对等体。

这些限制只适用于层次结构中的同级。这意味着在下图中,只有A、B和C组会相互影响,D和F组会相互影响。G组将不会影响任何人:

因此,理想的配置方式是在A、B和C组中设置io.latency。一般来说,你不希望设置的值低于你的设备支持的latency。实验一下,找到最适合你的工作负载的值。从高于设备的预期延迟开始,观察工作负载组的io.stat中的avg_lat值,以了解你在正常操作中看到的延迟情况。使用avg_lat值作为实际设置的基础,设置为比io.stat中的值高10-15%。

5.11 IO延迟节制是如何工作的

io.latency是工作保护;所以只要每个人都达到他们的延迟目标,控制器就不会做任何事情。一旦一个组开始错过它的目标,它就会开始节制任何目标比它高的对等组。这种节流有两种形式:

  • 队列深度节制。这是一个组被允许拥有的未清偿的IO的数量。我们将相对快速地钳制,从没有限制开始,一直到每次1个IO。
  • 人为的延迟诱导。有一些类型的IO不能被节制,否则可能会对更高的优先级组产生不利影响。这包括交换和元数据IO。这些类型的IO被允许正常发生,但是它们被 "收费 "给原发组。如果始发组被节流,你会看到io.stat中的use_delay和delay字段增加。延迟值是指在这个组中运行的任何进程被增加了多少微秒。因为如果有大量的交换或元数据IO发生,这个数字可能会变得相当大,我们将单个延迟事件限制在1秒内。

一旦受害组开始重新达到其延迟目标,它将开始取消之前被节流的任何对等组。如果受害组只是停止了IO,全局计数器将适当地取消节流。

5.12 IO延时接口文件

以下省略

5.13 IO优先

只有一个属性可以控制I/O优先级cgroup策略的行为,即blkio.prio.class属性。该属性可以接受以下值:

以下省略

以下数值与I/O优先级策略相关:

no-change0
None-to-rt1
rt-to-be2
All-to-idle3

与每个I/O优先级类别相对应的数值如下:

IOPRIO_CLASS_NONE0
IOPRIO_CLASS_RT(real-time)1
IOPRIO_CLASS_BE(best effort)2
IOPRIO_CLASS_IDLE3

为一个请求设置I/O优先级的算法如下:

  • 将I/O优先级类别策略转化为数字。
  • 将请求的I/O优先级类别改为I/O优先级类别策略号和数字I/O优先级类别的最大值。

5.14 PID

进程号控制器用于允许cgroup在达到指定的限制后停止任何新任务的fork()'d或clone()d。

一个c组中的任务数量可能会以其他控制器无法阻止的方式耗尽,因此需要有自己的控制器。例如,叉子炸弹很可能在遇到内存限制之前耗尽任务数量。

注意,本控制器中使用的PID是指TID,即内核使用的进程ID。

5.15 PID 接口文件

以下省略

组织操作不被cgroup策略所阻止,所以有可能使pids.current>pids.max。这可以通过将限制设置为小于pids.current,或者将足够多的进程附加到c组,使pids.current大于pids.max来实现。然而,不可能通过fork()或clone()违反cgroup PID策略。如果创建一个新的进程会导致违反cgroup策略,这些函数将返回-EAGAIN。

5.16 Cpuset

cpuset "控制器提供了一种机制,用于限制任务的CPU和内存节点的放置,使其只限于任务当前cgroup中的cpuset接口文件中指定的资源。这在大型NUMA系统中特别有价值,在这些系统中,将任务放置在适当大小的子集上,并仔细放置处理器和内存,以减少跨节点内存访问和争用,可以提高整个系统的性能。

cpuset "控制器是分层次的。这意味着该控制器不能使用其父代中不允许的CPU或内存节点。

5.17 Cpuset接口文件

以下省略

5.18 设备控制器

设备控制器管理对设备文件的访问。它包括创建新的设备文件(使用mknod),以及对现有设备文件的访问。

Cgroup v2设备控制器没有接口文件,是在cgroup BPF之上实现的。为了控制对设备文件的访问,用户可以创建类型为BPF_PROG_TYPE_CGROUP_DEVICE的BPF程序,并用BPF_CGROUP_DEVICE标志将其附加到c组上。在试图访问一个设备文件时,相应的BPF程序将被执行,根据返回值,该尝试将以-EPERM成功或失败。

BPF_PROG_TYPE_CGROUP_DEVICE程序需要一个指向bpf_cgroup_dev_ctx结构的指针,该结构描述了设备访问尝试:访问类型(mknod/读/写)和设备(类型、主要和次要号码)。如果程序返回0,则尝试失败,并带有-EPERM,否则就会成功。

BPF_PROG_TYPE_CGROUP_DEVICE程序的例子可以在内核源代码树中的tools/testing/selftests/bpf/progs/dev_cgroup.c中找到。

5.19 RDMA

rdma "控制器调节RDMA资源的分配和核算。

5.20 RDMA接口文件

以下省略

5.21 HugeTLB

HugeTLB控制器允许限制每个控制组的HugeTLB使用量,并在页面故障时执行控制器的限制。

5.22 HugeTLB接口文件

以下省略

5.23 Misc

Miscellaneous cgroup为标量资源提供了资源限制和跟踪机制,这些资源不能像其他cgroup资源那样被抽象化。控制器是由CONFIG_CGROUP_MISC配置选项启用的。

一个资源可以通过include/linux/misc_cgroup.h文件中的枚举misc_res_type{}添加到控制器中,并通过kernel/cgroup/misc.c文件中的misc_res_name[]添加相应名称。资源的提供者必须在使用该资源之前通过调用misc_cg_set_capacity()设置其容量。

一旦容量被设置,那么资源的使用情况就可以通过charge和uncharge API来更新。所有与misc控制器交互的API都在include/linux/misc_cgroup.h中。

5.24 Misc接口文件

Miscellaneous 控制器提供3个接口文件。如果两个杂项资源(res_a和res_b)被注册,那么:

以下省略

5.25 迁移和所有权

杂项标量资源被记入首先使用它的c组,并保持记入该c组,直到该资源被释放。将一个进程迁移到一个不同的c组并不会将费用转移到该进程所迁移的目标c组。

5.26 其他

5.27 perf_event

perf_event 控制器,如果没有安装在传统的层次结构上,会自动在 v2 层次结构上启用,这样 perf 事件就可以一直由 cgroup v2 路径过滤。在 v2 层次结构被填充后,该控制器仍可被移到传统层次结构上。

5.28 非规范性信息

本节包含的信息并不被认为是稳定的内核API的一部分,所以可能会有变化。

5.29 CPU控制器根部的cgroup进程行为

当在根c组中分配CPU周期时,该c组中的每个线程都被视为在根c组的一个单独的子c组中托管。这个子组的权重取决于其线程的好坏程度。

关于这个映射的细节,请看kernel/sched/core.c文件中的sched_prio_to_weight数组(这个数组中的值应该被适当地缩放,所以中性–漂亮的0–值是100而不是1024)。

5.30 IO控制器根部cgroup进程的行为

根c组进程被托管在一个隐含的叶子节点中。当分配IO资源时,这个隐含的子节点被考虑在内,就像它是根c组的一个正常的子c组,权重值为200。

6 Namespace

6.1 基础知识

cgroup 命名空间提供了一种机制来虚拟化 “/proc/$PID/cgroup” 文件和 cgroup 挂载的视图。CLONE_NEWCGROUP 克隆标志可以与 clone(2) 和 unshare(2) 一起使用来创建一个新的 cgroup 命名空间。在 cgroup 命名空间内运行的进程将使其"/proc/$PID/cgroup "输出被限制在 cgroupns 根目录下。cgroupns root是创建cgroup命名空间时该进程的cgroup。

如果没有cgroup命名空间,"/proc/$PID/cgroup “文件会显示一个进程的cgroup的完整路径。在一个容器设置中,一组c组和命名空间旨在隔离进程,”/proc/$PID/cgroup "文件可能会向被隔离的进程泄露潜在的系统级信息。比如说:

# cat /proc/self/cgroup
0::/batchjobs/container_id1

路径"/batchjobs/container_id1 "可以被认为是系统数据,不希望暴露给孤立的进程。可以用cgroup命名空间来限制这个路径的可见性。例如,在创建一个cgroup命名空间之前,人们会看到:

# ls -l /proc/self/ns/cgroup
lrwxrwxrwx 1 root root 0 2014-07-15 10:37 /proc/self/ns/cgroup -> cgroup:[4026531835]
# cat /proc/self/cgroup
0::/batchjobs/container_id1

在取消共享一个新的命名空间后,视图发生了变化:

# ls -l /proc/self/ns/cgroup
lrwxrwxrwx 1 root root 0 2014-07-15 10:35 /proc/self/ns/cgroup -> cgroup:[4026532183]# cat /proc/self/cgroup
0::/

当多线程进程中的某个线程取消共享其cgroup命名空间时,新的cgroupns会应用于整个进程(所有线程)。这对v2层次结构来说是很自然的;然而,对传统层次结构来说,这可能是意想不到的。

只要里面有进程或挂载,c组命名空间就一直存在。当最后一次使用消失时,c组命名空间就被销毁。cgroupns根和实际的cgroups仍然存在。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值