RFC-0236:VMO 快照修改克隆

RFC-0236:VMO 快照修改克隆
状态已接受
领域
  • 内核
说明

目标是引入新的 VMO 子类型,该类型允许截取快照,以捕获由分页器支持的 VMO 中任何经过修改的页面。

问题
  • 123742
Gerrit 更改
  • 883471
作者
审核人
提交日期(年-月-日)2023-07-20
审核日期(年-月-日)2023-12-12

总结

目标是引入新的 VMO 子级类型,允许为由分页器支持的 VMO 的子级中任何已修改的页面截取快照。

设计初衷

目前的内核支持两种类型的 VMO 克隆(也称为子 VMO):真实快照(其中两个 VMO 在克隆操作完成后都看不到彼此的更改)和“至少写入时快照”克隆(操作完成后,子项可以看到父 VMO 上的更改,但子项上已写入的页面除外)。

克隆操作可对 VMO 克隆本身重复,从而创建子 VMO 的层次结构。为便于实现,Zircon 内核不允许混合层次结构;您可以创建真实快照的层次结构,也可以创建写时快照 VMO 的层次结构。

随着 Starnix 的发明,Fuchsia 必须高效地支持 fork()。Fork 需要将父进程的整个地址空间克隆到子进程中,其中包括匿名内存、由分页器支持的数据和代码 VMO。

之所以出现问题,是因为内核仅支持匿名内存的真实快照,而对于由分页器支持的 VMO,它仅支持写入时快照克隆。因此,克隆无法满足 fork() 协定:因此 Starnix 被迫实现即时副本和/或其他需要占用大量 CPU 和内存的解决方法。

利益相关方

谁与此 RFC 被接受是否相关?(本部分为可选内容,但建议阅读。)

csuter@google.com、jamesr@google.com

教员

davemoore@google.com

审核者

rashaeqbal@google.com、jamesr@google.com

咨询人员

列出应审核 RFC,但无需审批的人员。

csuter@google.com、adanis@google.com、cpu@google.com、mvanotti@google.com、lindkvist@google.com

社交

我们还将设计文档与 Zircon 团队和一些 Starnix 成员进行了社交,并与利益相关方分享了一项正在进行的 CL 以进行基准化分析。

要求

  • 为克隆同时具有由分页器支持的 VMO 和匿名 VMO 的地址空间提供高效支持。
  • 对于父 VMO 或子 VMO,在以下两种情况下,在进程无法访问的 VMO(最后一个句柄已关闭)后,应该不会浪费内存:
vmo = create_vmo();
   loop {
      child = create_snapshot_modified(vmo)
      child.write(...)
      vmo = child  // old vmo is dropped
   }
vmo = create_vmo();
   loop {
      vmo.write(...)
      child = create_snapshot_modified(vmo)
      // child is dropped
   }

设计

ZX_VMO_CHILD_SNAPSHOT_MODIFIED 是一种新型 VMO 克隆,允许创建通过快照修改的子类型。

此标志会创建一个子项,用于保留由分页器支持的 VMO 的子项修改过的任何页面的快照。在语义上,它就像是在没有分页器支持的父级的任何页面上执行即时复制。父页面中由分页器支持的页面的行为至少具有写入时复制语义。这与原始快照语义不同,后者的行为就像在 VMO 的所有页面上创建即时副本一样。

首次在由分页器支持的 VMO 上使用时,语义的行为方式就像使用了 SNAPSHOT_AT_LEAST_ON_WRITE 一样。创建的克隆的句柄最初与父级完全相同,但可以对克隆进行修改,从而导致克隆。

针对任何匿名 VMO 使用时,SNAPSHOT_MODIFIED 克隆将升级以具有快照语义,类似于 SNAPSHOT_AT_LEAST_ON_WRITE 使用的现有克隆类型升级语义。

此标志不适用于克隆具有来自 zx_vmo_create_physical()zx_vmo_create_contiguous() 的固定区域、切片或 VMO 的 VMO。

病例数量

在由页面调度程序支持的 VMO 上修改快照

为由分页器支持的 VMO 创建单个 SNAPSHOT_MODIFIED 克隆的行为就像是创建了单个 SNAPSHOT_AT_LEAST_ON_WRITE 克隆一样。在执行克隆时,新的 VMO 将与父级完全相同。

在对克隆执行另一个 SNAPSHOT_MODIFIED 之前,仍可以修改克隆。克隆中任何未经修改的页面都将至少具有写入时复制语义。

在 at-least-on-write 或 Snapshot-modify 之后修改了快照

由寻呼机支持的 VMO

由于 SNAPSHOT_MODIFIEDSNAPSHOT_AT_LEAST_ON_WRITE 在由分页器支持的 VMO 上的行为相同,因此在由分页器支持的克隆体上调用 SNAPSHOT_MODIFIED 这两种情况将产生相同的语义。任何不再由分页器支持的页面都将具有快照,而由分页器支持的页面至少具有写入时复制语义。

在截取快照之后修改了快照

在这种情况下,语义将升级为快照,类似于 Snapshot-at-least-on-write。

不支持的情况

目前不支持以下情况;如果尝试 SNAPSHOT_MODIFIED 克隆,将返回 ZX_ERR_NOT_SUPPORTED

快照修改的至少写入时快照链的结束

快照修改后,可能会扩展为在至少写入快照链的末尾使用。但是,这可能会造成混淆结果,因为克隆 VMO 的未分支页面可以在单向 VMO 链中看到修改,最接近的相对项就是被读取的页面。这会与原始 promise 指出可为任何已修改页面创建快照不一致。

通过快照修改了至少写入时快照链的中间

无法通过快照修改具有子项的 VMO(即在至少写入快照链的中间),因为它可能会产生不一致的层次结构。

术语

zx_vmo_create_child() 中克隆类型标志的现有命名惯例旨在以描述所提供的语义的方式为标志命名。此标志的当前名称为 SNAPSHOT_MODIFIED,因为它总结了克隆中已修改页面构成快照的行为。类似的选项是 SNAPSHOT_MODIFICATIONS。其他一些需要考虑的标志是 SNAPSHOT_MIXED,它确实描述了语义,但 SNAPSHOT_PAGER_MODIFICATIONS 是不太清晰的另一个考虑因素,但将 VMO 与分页器相结合并不理想。

实现

快照修改会影响 Zircon 中的许多文件,但可以拆分为 CL,这些 CL 在将选项标志添加到系统调用之前,在内核内部组件中添加对新快照类型的支持。

将添加一些内核内测试,以在第一阶段验证新结构的正确行为;引入选项标志后,更复杂的核心测试将包含在内。

性能

在大多数情况下,只有在创建由快照修改的新克隆时才会调用新添加的代码,因此现有代码的性能会发生意外变化。一种例外情况是,在创建 SNAPSHOT_AT_LEAST_ON_WRITE 子项时,简单方法包括额外获取 VMO 锁,但如果这会导致性能损失,则重构克隆选择代码以避免发生这种情况变得轻而易举。

安全注意事项

快照修改不太可能引入任何漏洞,因为它是使用现有 Zircon 基元进行构建,并且不会引入新功能。

测试

内核单元测试和核心测试将包含在相关 CL 中。

文档

我们还将发布一份面向 Zircon 开发者的详细设计文档。其中将简要介绍新的数据结构、支持和不支持的情况、代码更改、挑战和替代方案。

系统将向 zx_vmo_create_child() 中添加一个新标记,其说明如下:

ZX_VMO_CHILD_SNAPSHOT_MODIFIED - 创建一个子级,使其在父级中不由分页器支持的任何页面上执行即时复制,即那些由分页器支持的 VMO 的子级修改过的网页。由分页器支持的页面至少具有写入时复制语义。此标志不可用于使用 zx_vmo_create_physical()、zx_vmo_create_contiguous() 创建的 VMO、包含固定页面的 VMO 或此类 VMO 的后代。使用 SNAPSHOT_AT_LEAST_ON_WRITE 创建且具有非切片子级或不是由分页器支持的 VMO 的子级的任何 VMO 也不支持此标志。

缺点、替代方案和未知情况

使用现有的 Zircon VMO 基元为由分页器提供支持的 VMO 创建快照语义非常重要。用户分页器的运行方式是为单个 VMO 处理页面请求,并且其子项目前形成了一个具有写入时复制语义的单向单向链。

可用于匿名 VMO 的快照标志会创建一个隐藏的 VmCowPages,它是目标 VMO 及其新快照的共同祖先实体。由于没有指向隐藏的 VmCowPages 可以修改其页面的内容,因此它是不可变的。这个隐藏的 VmCowPages 会保留目标 VMO 中的页面,子项中的修改具有写入时复制语义。因此,搜索此层次结构中的页面涉及到遍历树,该树可以结束于隐藏的根。

很难使用现有的快照数据结构供分页器使用,因为根 VMO 会始终处于隐藏状态,而原始 VMO 会成为左子 VMO。如果分页器仍指向原始 VMO(它现在具有隐藏的父级),则必须将分页器操作传播到隐藏的根,以便根据其子级的请求为其提供页面。当页面已添加到未操作的节点时,会导致不一致。

最简单的解决方案是创建一个混合层次结构,其中根是可见的,并且有一个隐藏的子级,该子级充当快照树的隐藏根。

您可以通过多种方式向用户描述所提供的语义。RFC 中的描述概述了所提供的关于分页器的行为,但另一种构建该行为的方法只是修改和克隆网页。示例如下:

"此标志将创建一个子项,用于保留父项中任何已修改页面的快照。如果根 vmo 在快照生成后向未经修改的页面写入数据,则受快照修改的子级将会看到更改。这与原始快照语义不同,后者的行为方式就像创建即时副本一样。”

这种描述正确,但需要进一步说明此行为需要分页器,因为在匿名 VMO 上使用该标记时,该标记将升级到快照语义。

SNAPSHOT_MODIFIED 能否替换 SNAPSHOT_AT_LEAST_ON_WRITE?

逐步停用“至少写入时快照”并将其替换为快照修改后,这两种克隆类型的语义不同,这可能会导致意外行为。虽然这两种克隆类型都提供“至少在写入时复制”语义,但通过快照修改后可以在同一 VMO 中混合使用快照和至少写入时页面。此外,此更改需要进行性能测试。当 VMO 由分页器提供支持时,由于没有创建隐藏的共同祖先实体,因此 Snapshot-at-least-on-write 会为每个创建的克隆分配较少的内存。因此,在某些用例中,迁移所有使用 Snapshot-at-least-on-write 的情况可能会导致性能降低。

不过,可能值得研究一下替换 SNAPSHOT_AT_LEAST_ON_WRITE,因为它可以简化 zx_vmo_create_child() 的 API,因为大多数 fdio 帮助程序都承诺提供与 SNAPSHOT_MODIFIED 兼容的语义。

早期技术和参考资料

zx_vmo_create_child()