AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / unix / 问题 / 466166
Accepted
Wildcard
Wildcard
Asked: 2018-09-01 18:07:45 +0800 CST2018-09-01 18:07:45 +0800 CST 2018-09-01 18:07:45 +0800 CST

Linux 内核中的 cpuset cgroup 继承语义“坏了”什么?

  • 772

引用2013 年 systemd 新控制组界面的公告(添加了重点):

请注意,当前作为单元属性公开的 cgroup 属性的数量是有限的。这将在稍后扩展,因为它们的内核接口已被清理。例如,由于内核逻辑的继承语义被破坏,cpuset 或 freezer 目前根本没有公开。此外,不支持在运行时将单元迁移到不同的切片(即更改运行单元的 Slice= 属性),因为内核当前缺少原子 cgroup 子树移动。

那么,内核逻辑的继承语义有什么问题cpuset(以及这种问题如何不适用于其他 cgroup 控制器,例如cpu)?

RedHat 网站上有一篇文章给出了一个未经验证的解决方案,说明如何在 RHEL 7 中使用 cgroup cpusets,尽管它们缺乏作为易于管理的 systemd 单元属性的支持......但这甚至是个好主意吗?上面加粗的引文是有关的。

换句话说,使用此处引用的 cgroup v1 cpuset 可能会遇到哪些“陷阱”(陷阱)?


我正在为此开始赏金。

回答此问题的可能信息来源(无特殊顺序)包括:

  1. cgroup v1 文档;
  2. 内核源代码;
  3. 试验结果;
  4. 现实世界的经验。

上面引用中粗体线的一个可能含义是,当一个新进程被分叉时,它不会与它的父进程保持在同一个 cpuset cgroup 中,或者它在同一个 cgroup 中但处于某种“未强制”状态因此它实际上可能运行在与 cgroup 允许的不同的 CPU 上。但是,这纯粹是我的猜测,我需要一个明确的答案。

systemd cgroups
  • 3 3 个回答
  • 3271 Views

3 个回答

  • Voted
  1. Sirio Balmelli
    2018-09-06T00:11:47+08:002018-09-06T00:11:47+08:00

    我对 cgroups 的了解还不够,无法给出明确的答案(而且我当然没有使用 2013 年的 cgroups 的经验!)但是在香草 Ubuntu 16.04 cgroups v1 上似乎有它的共同作用:

    我设计了一个小测试,强制分叉为一个不同的用户,使用一个sudo /bin/bash分离出来的孩子&——这个-H标志是额外的偏执狂,强制sudo在 root 的家庭环境中执行。

    cat <(whoami) /proc/self/cgroup >me.cgroup && \
    sudo -H /bin/bash -c 'cat <(whoami) /proc/self/cgroup >you.cgroup' & \
    sleep 2 && diff me.cgroup you.cgroup
    

    这产生:

    1c1
    < admlocal
    ---
    > root
    

    作为参考,这是我系统上 cgroup 挂载的结构:

    $ mount | grep group
    tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
    cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd)
    cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
    cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
    cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
    cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
    cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
    cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
    cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
    cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
    cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
    cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
    lxcfs on /var/lib/lxcfs type fuse.lxcfs (rw,nosuid,nodev,relatime,user_id=0,group_id=0,allow_other)
    $
    
    • 4
  2. Best Answer
    Wildcard
    2018-09-06T15:41:46+08:002018-09-06T15:41:46+08:00

    此处的内核错误跟踪器中记录了至少一个与 cpusets 相关的明确且未解决的问题:

    错误 42789 - cpuset cgroup:当一个 CPU 下线时,它会从所有 cgroup 的 cpuset.cpus 中删除,但当它上线时,它只会恢复到根 cpuset.cpus

    从票证中引用一条评论(我将超链接添加到实际提交,并删除 IBM 电子邮件地址以防垃圾邮件机器人):

    这是由 Prashanth Nageshappa 独立报告的......并在提交8f2f748b0656257153bcf0941df8d6060acc5ca6中修复,但随后由 Linus 恢复为提交4293f20c19f44ca66e5ac836b411d25e14b9f185。根据他的承诺,该修复导致其他地方出现倒退。

    修复提交(后来被恢复)很好地描述了这个问题:

    目前,在 CPU hotplug 期间,cpuset 回调会修改 cpuset 以反映系统的状态,并且这种处理是不对称的。也就是说,在 CPU 脱机时,该 CPU 将从所有 cpuset 中删除。但是,当它重新联机时,它只被放回根 cpuset。

    这会在挂起/恢复期间产生重大问题。在挂起期间,我们将所有非引导 CPU 脱机,而在恢复期间,我们将它们联机。这意味着,在恢复之后,所有 cpuset(根 cpuset 除外)将被限制为仅一个 CPU(引导 cpu)。但是挂起/恢复的全部目的是将系统恢复到尽可能接近挂起之前的状态。


    描述了相同的非对称热插拔问题,并进一步了解它与继承的关系,在:

    错误 188101 - cgroup 的 cpuset 中的进程调度无法正常工作。

    引用那张票:

    当容器的 cpuset(docker/lxc 都使用底层 cgroup)变为空时(由于 hotplug/hotunplug),那么在该容器中运行的进程可以在其最近的非空祖先的 cpuset 中的任何 cpu 上调度。

    但是,当正在运行的容器(docker/lxc)的 cpuset 通过更新正在运行的容器的 cpuset(通过使用 echo 方法)从空状态(将 cpu 添加到空 cpuset)变为非空时,在该容器中运行的进程仍然使用与其最近的非空祖先相同的cpuset。


    虽然 cpuset 可能存在其他问题,但以上内容足以理解和理解 systemd 没有公开或利用 cpuset “由于内核逻辑的继承语义被破坏”的声明。

    从这两个错误报告中,不仅 CPU 不会在恢复后重新添加回 cpuset,而且即使(手动)添加它们,该 cgroup 中的进程仍将在可能被 cpuset 不允许的 CPU 上运行。


    我发现来自 Lennart Poettering 的一条消息直接证实了这一点(添加了粗体字):

    2016 年 8 月 3 日星期三 16:56 +0200,Lennart Poettering 写道:

    2016 年 8 月 3 日星期三 14:46,Werner Fink 博士(suse.de 的 werner)写道:

    v228 的问题(我猜这也是来自当前 git 日志的后来的 AFAICS)重复 CPU 热插拔事件(离线/在线)。根本原因是cpuset.cpus 没有被machined 恢复。请注意 libvirt 不能这样做,因为它是不允许这样做的。

    这是内核 cpuset 接口的一个限制,也是我们现在根本不在 systemd 中公开 cpuset 的原因之一。值得庆幸的是,有一个 cpusets 的替代方案,它是通过 systemd 中的 CPUAffinity= 公开的 CPU 亲和性控件,它的作用大致相同,但语义较少。

    我们想直接在 systemd 中支持 cpuset,但只要内核接口像它们一样无聊,我们就不会这样做。例如,当系统经历挂起/恢复周期时,cpuset 当前被完全清除。

    • 3
  3. Rob
    2018-09-07T23:26:12+08:002018-09-07T23:26:12+08:00

    Linux 内核中的 cpuset cgroup 继承语义“坏了”什么?

    “请注意,当前作为单元属性公开的 cgroup 属性的数量是有限的。这将在稍后扩展,因为它们的内核接口已被清理。例如,cpuset 或 freezer 目前根本没有公开,因为内核逻辑。此外,不支持在运行时将单元迁移到不同的切片(即更改运行单元的 Slice= 属性),因为内核当前缺乏原子 cgroup 子树移动。

    那么,对于 cpuset 的内核逻辑的继承语义有什么问题(以及这种问题如何不适用于其他 cgroup 控制器,例如 cpu)?

    上面的粗体引文是有关的。换句话说,使用此处引用的 cgroup v1 cpuset 可能会遇到哪些“陷阱”(陷阱)?

    非常简短的回答:代码不能很好地多进程,不同的进程使用和释放 PID 在他们的孩子的 PID 终止之前将它们返回到池中 - 让上游相信 PID 的孩子是活动的,所以跳过那个 PID,但是那个 PID在终止孩子之前不应该重新发行。简而言之,糟糕的锁。

    服务、范围和切片可以由管理员自由创建,也可以由程序动态创建。这可能会干扰操作系统在启动期间设置的默认切片。

    使用 Cgroups,一个进程及其所有子进程从包含的组中获取资源。

    还有更多……导致冗长的答案……

    许多人表达了他们的担忧:

    1. Jonathan de Boyne Pollard 的“ Linux 控制组不是工作”(2016 年):

      提供“作业”抽象的操作系统内核提供了一种取消/终止整个“作业”的方法。以 Win32 TerminateJobObject()机制为例。

      当 systemd 终止 cgroup 中的所有进程时,它不会发出单个“终止作业”系统调用。没有这样的事情。相反,它位于应用程序模式代码的循环中,反复扫描 cgroup 中的所有进程 ID(通过重新读取一个充满 PID 编号的文件)并向它以前未见过的新进程发送信号。这有几个问题。

      • systemd 可能比在进程组中获取子进程的速度慢,导致终止信号被发送到完全错误的进程:一个恰好在 systemd 读取 cgroup 的进程列表文件和它实际上是在向进程列表发送信号。...

      ...

      • 一个在 cgroup 中以足够快的速度分叉新进程的程序可以让 systemd 长时间旋转,理论上只要合适的“天气”占上风,就可以无限期地旋转,因为在每个循环迭代中都会有一个更多的进程要杀死。请注意,这不一定是叉子炸弹。它只需要 fork 足够多的时间,systemd 每次运行它的循环时都会在 cgroup 中看到至少一个新的进程 ID。

      • systemd 将它已经发出信号的进程的进程 ID 保存在一组中,以了解它不会再次尝试向哪些进程发送信号。ID N 的进程可能会被收割者/父进程发出信号、终止并从进程表中清除;然后 cgroup 中的某些东西会派生一个新进程,该进程再次分配了相同的进程 ID N。systemd 会重新读取 cgroup 的进程 ID 列表,认为它已经向新进程发出信号,根本不发出信号。

       

      这些都是通过真正的“工作”机制来解决的。但是 cgroups 不是这样的。cgroups 旨在作为对传统 Unix 资源限制机制的改进,解决其一些长期存在且众所周知的设计缺陷。它们的设计目的不是等同于 VMS 或Windows NT 作业对象。

      不,冰箱不是答案。systemd 不仅不使用冷冻机,而且 systemd人明确将其描述为“内核逻辑的继承语义被破坏”。您将不得不问他们这是什么意思,但对他们来说,冷冻机也不会神奇地将 cgroups 转变为工作机制。

      此外:更不用说 Docker 和其他人会为自己的目的操纵控制组的冻结状态,并且没有真正的无竞争机制可以在多个所有者之间共享此设置,例如原子读取和更新为了它。
      只要保留其最后修改日期戳,特此授予许可以原始、未修改的形式复制和分发此网页。

      • TerminateJobObject()函数

        Terminates all processes currently associated with the job. If the  
        job is nested, this function terminates all processes currently  
        associated with the job and all of its child jobs in the hierarchy. 
        
      • Windows NT 作业对象

        A job object allows groups of processes to be managed as a unit.  
        Job objects are namable, securable, sharable objects that control  
        attributes of the processes associated with them. Operations  
        performed on a job object affect all processes associated with the  
        job object. Examples include enforcing limits such as working set   
        size and process priority or terminating all processes associated 
        with a job.
        

      乔纳森的解释中提供的答案是:

      systemd 的资源控制概念

      ...

      服务、范围和切片单元直接映射到 cgroup 树中的对象。当这些单元被激活时,它们每个都直接映射到(以一些字符转义为模)到从单元名称构建的 cgroup 路径。例如,切片 foobar-waldo.slice 中的服务 quux.service 在 cgroup foobar.slice/foobar-waldo.slice/quux.service/ 中找到。

      服务、范围和切片可以由管理员自由创建,也可以由程序动态创建。但是,默认情况下,操作系统定义了许多启动系统所必需的内置服务。另外,默认定义了四个切片:首先是根切片-.slice(如上所述),还有system.slice、machine.slice、user.slice。默认情况下,所有系统服务都放在第一个切片中,所有虚拟机和容器放在第二个切片中,用户会话放在第三个切片中。然而,这只是一个默认设置,管理员可以自由定义新切片并为它们分配服务和范围。另请注意,所有登录会话都会自动放置在单独的范围单元中,VM 和容器进程也是如此。最后,所有登录的用户也将获得一个他们自己的隐式切片,其中放置了所有会话范围。

      ...

      如您所见,服务和作用域包含进程并放置在切片中,而切片不包含它们自己的进程。另请注意,特殊的“-.slice”未显示,因为它隐含地标识为整个树的根。

      可以以相同的方式在服务、范围和切片上设置资源限制。...

    按照上面的链接获取完整的解释。

    1. “ Cgroups v2:资源管理第二次做得更糟”(2016 年 10 月 14 日),作者 davmac:

      ...

      您可以创建嵌套层次结构,以便在其他组中存在组,并且嵌套组共享其父组的资源(并且可能会受到进一步限制)。通过将进程的 PID 写入组的控制文件之一,可以将进程移动到组中。因此,一个组可能同时包含进程和子组。

      您可能想要限制的两个明显的资源是内存和 CPU 时间,它们中的每一个都有一个“控制器”,但可能还有其他资源(例如 I/O 带宽),并且一些 Cgroup 控制器并不真正管理资源利用率因此(例如“冷冻机”控制器/子系统)。Cgroups v1 接口允许创建多个带有不同控制器的层次结构(这个值是可疑的,但可能性是存在的)。

      重要的是,进程从其父进程继承其 cgroup 成员资格,并且除非它们具有适当的权限,否则不能将自己移出(或移入)cgroup,这意味着进程无法摆脱通过分叉施加于它的任何限制。将此与使用 setrlimit 进行比较,其中可以使用 RLIMIT_AS(地址空间)限制来限制进程对内存的使用(例如),但是该进程可以分叉并且其子进程可以消耗额外的内存而无需从原始资源中提取过程。另一方面,使用 Cgroups,一个进程及其所有子进程从包含的组中获取资源。

      ...

      cgroup 控制器实现了许多旋钮,这些旋钮永远不会被接受为公共 API,因为它们只是将控制旋钮添加到系统管理伪文件系统中。cgroup 最终得到了没有适当抽象或细化的接口旋钮,并直接揭示了内核内部细节。

      这些旋钮通过定义不明确的委派机制暴露给各个应用程序,有效地滥用 cgroup 作为实现公共 API 的快捷方式,而无需经过必要的审查。

      ...

      cgroup v1 允许线程位于任何 cgroup 中,这产生了一个有趣的问题,即属于父 cgroup 及其子 cgroup 的线程竞争资源。这很讨厌,因为两种不同类型的实体竞争,没有明显的方法来解决它。不同的控制器做了不同的事情。

    2. 另请参阅 cgroup v2 文档:“ v1 的问题和 v2 的基本原理”:

      多个层次结构

      cgroup v1 允许任意数量的层次结构,每个层次结构可以托管任意数量的控制器。虽然这似乎提供了高度的灵活性,但在实践中并没有什么用处。

      例如,由于每个控制器只有一个实例,因此可以在所有层次结构中使用的实用程序类型控制器(例如冷冻机)只能在一个实例中使用。一旦填充了层次结构,控制器就无法移动到另一个层次结构,这一事实加剧了这个问题。另一个问题是绑定到层次结构的所有控制器都被迫具有完全相同的层次结构视图。无法根据特定控制器改变粒度。

      在实践中,这些问题严重限制了哪些控制器可以放在同一层次结构中,并且大多数配置都将每个控制器放在自己的层次结构中。只有密切相关的控制器,例如 cpu 和 cpuacct 控制器,才有意义放在同一个层次结构中。这通常意味着用户空间最终会管理多个相似的层次结构,只要需要进行层次结构管理操作,就在每个层次结构上重复相同的步骤。

      此外,对多层次结构的支持付出了高昂的代价。它极大地复杂了 cgroup 核心实现,但更重要的是,对多个层次结构的支持限制了 cgroup 一般如何使用以及控制器能够做什么。

      可能有多少层次结构没有限制,这意味着线程的 cgroup 成员资格不能用有限长度来描述。密钥可能包含任意数量的条目并且长度不受限制,这使得操作起来非常尴尬,并导致添加仅用于识别成员身份的控制器,这反过来又加剧了最初的层次结构数量激增的问题。

      此外,由于控制器对其他控制器可能处于的层次结构拓扑没有任何期望,因此每个控制器必须假设所有其他控制器都连接到完全正交的层次结构。这使得控制器之间无法进行合作,或者至少非常麻烦。

      在大多数用例中,没有必要将控制器放在彼此完全正交的层次结构上。通常需要的是根据特定控制器具有不同粒度级别的能力。换句话说,从特定控制器查看时,层次结构可能会从叶向根折叠。例如,一个给定的配置可能不关心内存在某个级别之外是如何分配的,但仍想控制 CPU 周期的分配方式。

    请参阅第 3 节的链接了解更多信息。

    1. Lennart Poettering(systemd 开发人员)和 Daniel P. Berrange(Redhat)之间的通信,周三,20.07.16 12:53 从systemd-devel 档案中检索,标题为:“ [systemd-devel] 通过 cpuset 将所有进程限制到 CPU/RAM控制器“:

      16 年 7 月 20 日星期三 12:53,Daniel P. Berrange(redhat.com 上的 berrange)写道:

      对于虚拟化主机,希望将所有主机操作系统进程限制在 CPU/RAM 节点的子集上是很常见的,而其余的则可供 QEMU/KVM 独占使用。从历史上看,人们使用“isolcpus”内核参数来执行此操作,但去年它的语义发生了变化,因此那里列出的任何 CPU 也被调度排除在负载平衡之外,这使得它在一般的非实时用例中非常无用您仍然希望 QEMU 线程在 CPU 之间进行负载平衡。

      所以唯一的选择是使用 cpuset cgroup 控制器来限制进程。AFAIK,systemd 目前没有对 cpuset 控制器的明确支持,所以我正在尝试找出在 systemd 背后实现这一目标的“最佳”方式,同时最大限度地降低未来 systemd 版本破坏事物的风险。

      2016 年 7 月 20 日星期三下午 3:29:30 +0200,Lennart Poettering 回复:

      是的,我们目前不支持此功能,但我们愿意。但问题是它的内核接口就像现在一样非常糟糕,在没有修复之前,我们不太可能在 systemd 中支持它。(据我了解,Tejun 在 cpuset 中的内存与 cpu 之间的关系可能也不会保持原样)。

      下一条消息

      20.07.16 周三 14:49,Daniel P. Berrange(redhat.com 上的 berrange)写道:

      一旦发行版切换,cgroupsv2 可能会破坏很多东西,所以我认为不会在次要更新中完成 - 只有一个主要的新发行版版本,所以并不那么令人担忧。

    我希望这能澄清事情。

    • 1

相关问题

  • journalctl 中的区分级别

  • 将默认编辑器更改为 vim for _ sudo systemctl edit [unit-file] _

  • systemd:如何在服务启动时运行脚本,而不编辑服务定义

  • 使用 systemd 看门狗支持重新启动应用程序

  • 使用键盘快捷键启动/停止 systemd 服务 [关闭]

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    如何将 GPG 私钥和公钥导出到文件

    • 4 个回答
  • Marko Smith

    ssh 无法协商:“找不到匹配的密码”,正在拒绝 cbc

    • 4 个回答
  • Marko Smith

    我们如何运行存储在变量中的命令?

    • 5 个回答
  • Marko Smith

    如何配置 systemd-resolved 和 systemd-networkd 以使用本地 DNS 服务器来解析本地域和远程 DNS 服务器来解析远程域?

    • 3 个回答
  • Marko Smith

    如何卸载内核模块“nvidia-drm”?

    • 13 个回答
  • Marko Smith

    dist-upgrade 后 Kali Linux 中的 apt-get update 错误 [重复]

    • 2 个回答
  • Marko Smith

    如何从 systemctl 服务日志中查看最新的 x 行

    • 5 个回答
  • Marko Smith

    Nano - 跳转到文件末尾

    • 8 个回答
  • Marko Smith

    grub 错误:你需要先加载内核

    • 4 个回答
  • Marko Smith

    如何下载软件包而不是使用 apt-get 命令安装它?

    • 7 个回答
  • Martin Hope
    rocky 如何将 GPG 私钥和公钥导出到文件 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Wong Jia Hau ssh-add 返回:“连接代理时出错:没有这样的文件或目录” 2018-08-24 23:28:13 +0800 CST
  • Martin Hope
    Evan Carroll systemctl 状态显示:“状态:降级” 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim 我们如何运行存储在变量中的命令? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S 为什么 /dev/null 是一个文件?为什么它的功能不作为一个简单的程序来实现? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 如何从 systemctl 服务日志中查看最新的 x 行 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - 跳转到文件末尾 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla 为什么真假这么大? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis 在一个巨大的(70GB)、一行、文本文件中替换字符串 2017-12-30 06:58:33 +0800 CST
  • Martin Hope
    Bagas Sanjaya 为什么 Linux 使用 LF 作为换行符? 2017-12-20 05:48:21 +0800 CST

热门标签

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve