cgroup统一的层次结构
本文档描述了统一层次结构所做的更改及其基本原理。它最终将被合并到主cgroup文档中。
目录
- 背景
- 基本操作
- 底座
- cgroup.subtree_control
- cgroup.controllers
- 结构约束
- 自上而下的
- 没有内部任务。
- 其他改动
- [Un]populated Notification
- 其他核心变化
- 每控制器更改
- blkio
- cpuset
- memory
- 计划变更
- 资源管制上限
1.背景
Cgroup允许任意数量的层次结构,每个层次结构可以托管任意数量的控制器。虽然这似乎提供了高度的灵活性,但在实践中并不是很有用。
例如,由于每个控制器只有一个实例,在所有层次中都有用的实用类型控制器,如freezer,只能在一个层次中使用。一旦层次结构被填充,控制器就无法移动,这一事实加剧了这个问题。另一个问题是,绑定到层次结构的所有控制器都必须具有完全相同的层次结构视图。不可能根据特定的控制器改变粒度。
在实践中,这些问题严重限制了哪些控制器可以放在相同的层次结构中,大多数配置都求助于将每个控制器放在自己的层次结构中。只有密切相关的控制器,如cpu和cpuacct控制器,才应该放在相同的层次结构中。这通常意味着,每当需要层次结构管理操作时,用户层最终会管理多个相似的层次结构,在每个层次结构上重复相同的步骤。
不幸的是,支持多个层次结构的成本很高。cgroup核心的内部实现非常复杂,但更重要的是,对多层结构的支持限制了cgroup的一般使用方式以及控制器可以做什么。
层次结构的数量没有限制,这意味着一个任务的c组成员关系不能用有限的长度描述。密钥可能包含任意数量的条目,并且长度是无限的,这使得它非常难以处理,并导致添加仅用于标识成员的控制器,这反过来加剧了原始问题。
此外,由于控制器不能对其他控制器所处的层次结构形状有任何期望,因此每个控制器必须假设所有其他控制器都在完全正交的层次结构上运行。这使得控制器之间无法相互协作,或者至少非常麻烦。
在大多数用例中,没有必要将控制器放在彼此完全正交的层次结构上。通常需要的是根据特定控制器具有不同粒度级别的能力。换句话说,当从特定控制器查看时,层次结构可能从叶子到根折叠。例如,给定的配置可能不关心超过某个级别的内存如何分布,同时仍然希望控制CPU周期如何分布。
统一层次结构是cgroup接口的下一个版本。它旨在通过拥有更多的结构来解决上述问题,同时为大多数用例保留足够的灵活性。在此过程中还解决了其他各种通用和特定于控制器的接口问题。
2.基本操作
2-1.挂载
目前,可以使用以下mount命令挂载统一的层次结构。请注意,这仍在开发中,并计划很快进行更改。
mount -t cgroup -o __develop__sane_behavior cgroup $MOUNT_POINT
所有没有绑定到其他层次结构的控制器都会自动绑定到统一层次结构,并显示在统一层次结构的根节点。只在统一层次结构的根启用的控制器可以随时绑定到其他层次结构。这允许以完全向后兼容的方式混合统一的层次结构和传统的多个层次结构。
常见挂载位置:
例子:linux 5.15.0-125-generic
$ mount -l | grep cgroup
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)
2-2.cgroup.subtree_control
统一层次结构上的所有cgroup都有一个“cgroup.subtree_control”文件,用于管理在cgroup的子节点上启用哪些控制器。让我们假设如下所示的层次结构。
root - A - B - C
\ D
root的“cgroup.subtree_control"文件确定A、B、C和D上的哪些控制器是启用的。这与直接子级的控制器用于分配父级的资源这一事实相吻合。事实上,很自然地会假设子组件的资源控制旋钮属于父组件。启用“cgroup”中的控制器。Subtree_control”文件声明了cgroup中各个资源的分配将受到控制。请注意,这意味着控制器启用状态在兄弟节点之间共享。
读取时,该文件包含一个以空格分隔的当前启用的控制器列表。对文件的写入应该包含一个以空格分隔的控制器列表,并带有‘+’或‘-’前缀(不带引号)。前缀为“+”的控制器被启用,而前缀为“-”的控制器被禁用。如果一个控制器被多次列出,最后一个条目优先。特定的操作是自动执行的——要么全部成功,要么全部失败。
例子:linux 5.15.0-125-generic
/sys/fs/cgroup$ cat cgroup.subtree_control
cpuset cpu io memory pids
/sys/fs/cgroup$ cat user.slice/cgroup.subtree_control
memory pids
/sys/fs/cgroup$ cat system.slice/cgroup.subtree_control
cpuset cpu io memory pids
2-3.cgroup.controllers
只读的”cgroup.controllers”文件包含一个以空格分隔的控制器列表,可以在c组的“cgroup.subtree_control”中启用。
在根cgroup中,它列出了没有绑定到其他层次结构的控制器,并且随着控制器与其他层次结构的绑定和不绑定,内容发生变化。
在非根cgroups中,该文件的内容等于父cgroup的“cgroup.subtree_control”文件内容,因为只有从父节点启用的控制器才能在其子节点中使用。
例子:linux 5.15.0-125-generic
/sys/fs/cgroup$ cat cgroup.controllers
cpuset cpu io memory hugetlb pids rdma misc
3.结构性限制
3-1.自上而下
因为嵌套不受控制的资源是没有意义的,所有非根目录下的“cgroup.subtree_control”文件只能包含在父目录下的“cgroup.subtree_control”文件中启用的控制器。一个控制器只有在父控制器处于启用状态时才能启用,而一个控制器不能在一个或多个子控制器处于启用状态时被禁用。
3-2.无内部任务
cgroup面临的一个长期存在的问题是属于父进程cgroup的任务与其子进程cgroup之间的竞争。这本质上是令人讨厌的,因为两种不同类型的实体竞争,并且没有一致的明显方法来处理它。不同的控制器做不同的事情。
cpu控制器将任务和cgroup视为等价物,并将nice等级映射到cgroup权重。这在某些情况下是可行的,但当应该为孩子分配特定比例的CPU周期和内部任务数量波动时(这些比例随着竞争实体数量的波动而不断变化),这就失败了。还有其他问题。从nice程度到权重的映射并不明显或通用,还有许多其他的控制根本无法用于任务。
blkio控制器隐式地为每个cgroup创建一个隐藏的叶节点来托管任务。隐藏的叶子有它自己的带有“leaf_”前缀的所有旋钮的副本。虽然这允许对内部任务进行同等的控制,但它有严重的缺点。它总是添加一个不必要的额外嵌套层,使接口混乱,并显著复杂化实现。
内存控制器目前没有办法控制内部任务和子cgroup之间发生的事情,行为也没有明确定义。有人曾尝试添加临时行为和旋钮,以根据特定的工作负载定制行为。继续这个方向将导致问题,从长远来看,这些问题将极其难以解决。
多个控制器与内部任务斗争,并想出了不同的方法来处理它;不幸的是,现在使用的所有方法都有严重的缺陷,而且,不同的行为使得cgroup作为一个整体高度不一致。
很明显,这是需要从cgroup核心适当地以统一的方式解决的问题,这样控制器就不需要担心它,并且cgroup作为一个整体显示出一致和逻辑的行为。为了实现这一点,统一的层次结构强制执行以下结构约束:
- 除了根目录,只有不包含任何任务的cgroup才会在其“cgroup.subtree_control”文件中启用控制器。
结合其他属性,这可以保证,当控制器查看启用了它的层次结构部分时,任务总是只在叶子上。这就排除了子cgroup与父cgroup的内部任务竞争的情况。
有两点需要注意。首先,根cgroup不受限制。Root包含任务和匿名资源消耗,不能与任何其他cgroup关联,需要大多数控制器进行特殊处理。如何管理根cgroup中的资源消耗取决于每个控制器。
其次,如果在cgroup的“cgroup.subtree_control”文件中没有启用的控制器,则限制不会生效。这一点很重要,否则就不可能创建一个迁移的cgroup的子节点。为了控制一个cgroup的资源分配,cgroup必须先创建子组,然后在“cgroup.subtree_control”文件中启用控制器,并将其所有任务转移到子组。
4.其他变更
4-1.迁移通知 [Un]populated Notification
cgroup的用户通常需要一种方法来确定cgroup的子层次结构何时变为空,以便清理它。Cgroup目前为其提供了release_agent;不幸的是,这种机制存在很多问题。
- 它通过fork和执行指定为release_agent的用户层二进制文件来通知。这是一种早已弃用的通知传递方法。与更大的基础设施集成非常笨重、缓慢和繁琐。
- 在根节点有一个监控点。没有办法委托管理子树。
- 事件不是递归的。当一个cgroup没有任何任务或子cgroup时,它会触发。内部节点的事件只有在所有子节点都被删除后才会触发。这再次使得子树的管理无法委托。
- 事件从内核端过滤。notify_on_release文件用于订阅或抑制发布事件。这是不必要的复杂,这样做可能是因为事件传递本身的开销很大。
统一层次结构实现了一个接口文件“cgroup.populated”,该文件可以用于监控cgroup的子层次结构中是否有任务。如果cgroup及其后代中没有进程,其值为0;否则,1。Poll和[id]notify将会在值改变时触发事件。
这明显更轻、更简单,并且允许子层次的委托管理——子层次监控可以通过简单地将自己或另一个进程放入子层次中来阻止进一步的传播,并从那里监控它感兴趣的事件,而不会干扰树的更高部分的监控。
在统一的层次结构中,不再支持release_agent机制,也不存在“release_agent”和“notify_on_release”接口文件。
例子:linux 5.15.0-125-generic
/sys/fs/cgroup/user.slice$ cat cgroup.events
populated 1
frozen 0
4-2.其他核心变化
- 不允许任何挂载选项。
- 不允许重新挂载。
- 不允许重命名(2)。
- “tasks”文件被移除。一切都应该在过程粒度上。请使用“cgroup.procs”文件。
- “cgroup.procs”文件未排序。pid将是唯一的,除非它们在读取之间被循环使用。
- “cgroup.clone_children"文件被删除。
4-3.每个控制器的变化
4-3-1.blkio
blk-throttle 改为更适当的层次。
4-3-2.cpuset
- 在cpu热插拔之后,进程保存在空的cpusets中,并获得最近的非空祖先的cpu掩码,而不是移动到非空祖先。
- 可以将任务移动到空的容器中,并再次使用最近的非空祖先的cpu掩码。
4-3-3.memory
默认情况下,use_hierarchy是打开的,并且该标志的cgroup文件没有创建。
5.计划中的变更
5.1资源控制的CAP
对于所有资源控制相关的旋钮,是统一的层次结构将需要的功能(7),但这还有待决定。
进程组织操作——子cgroup的创建和子层次结构中进程的迁移,可以通过改变对cgroup目录和“cgroup.procs”接口文件的所有权和/或权限来授权;但是,所有影响资源控制的操作(写入“cgroup.subtree_control”文件或任何特定于控制器的旋钮)都需要显式的CAP特权。
这在某种程度上是为了防止cgroup接口无意中提升为非特权二进制文件使用的可编程API。Cgroup以各种方式暴露了系统的各个方面,这些方面没有被适当地抽象为普通程序直接使用。这是一个比系统调用更接近sysctl旋钮的管理界面。即使是基于文件系统路径的基本访问模型,也不适合直接使用。没有办法以无竞争的方式访问“my cgroup”,也没有办法使迁移到另一个cgroup的多个操作原子化。
另一个方面是,无论好坏,cgroup接口经过的审查要比非特权用户层的普通接口少得多。好处是,cgroup能够暴露有用的特性,这些特性可能不适合在合理的时间范围内进行一般消费。它在内部细节和用户层可见接口之间提供了一条相对较短的路径。当然,这种捷径也有很高的风险。我们讨论通用内核api是有原因的。它可能最终泄漏内部细节,导致内核被锁定在一个无法以合理方式维护的契约中,从而造成巨大的痛苦。
此外,由于其特定的性质,cgroup及其控制器往往不会引起广泛的开发人员的关注。Cgroup短暂的历史已经充满了严重错误的接口设计、不必要的内部细节承诺和暴露、各种功能的破碎和危险的实现。
保持cgroup作为管理界面既有利于它的角色,又必须考虑到它的性质。某些cgroup特性对于非特权访问可能是有意义的。如果认为是合理的,则必须进一步抽象并实现为一个不同的接口,可以是系统调用,也可以是进程私有的文件系统,并通过所有用于一般使用的接口都必须经过的审查。
要求CAP并不是一个完整的解决方案,但它应该对在非特权程序中使用cgroup起到重要的威慑作用。
标签:control,文件,控制器,subtree,层次结构,cgroup,linux From: https://www.cnblogs.com/fanguang/p/18619694