RFC-0236:VMO 快照修改克隆

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

目标是引入一种新的 VMO 子类型,以便能够截取快照,捕获分页器支持的 VMO 中的任何已修改页面。

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

摘要

目标是引入一种新的 VMO 子类型,该类型允许对分页器支持的 VMO 的子项中的任何已修改页面进行快照。

设计初衷

现在的内核支持两种类型的 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 上修改了快照

创建单个 SNAPSHOT_MODIFIED 内存分页支持的 VMO 克隆的行为与创建单个 SNAPSHOT_AT_LEAST_ON_WRITE 克隆的行为相同。在执行克隆时,新的 VMO 将与父级完全相同。

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

快照修改后(至少写入一次)或快照修改后

的内存分页支持 VMO

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

快照修改时间晚于快照

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

不支持的情况

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

快照-至少在写入时修改的快照链的末尾

快照修改后可能会扩展为在“至少在写入时截取快照”链的末尾使用。不过,这可能会导致令人困惑的结果,因为来自克隆 VMO 的未分叉页面可以查看整个单向 VMO 链中的修改,而读取的是最近的相对页面。这与原始承诺(即可以为任何修改过的网页创建快照)相矛盾。

对“至少写入一次”快照链的中间部分进行了快照修改

快照修改后的 VMO 永远不能用于具有子级的 VMO(即位于“至少在写入时进行快照”链的中间),因为这可能会导致层次结构不一致。

术语

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

实现

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

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

性能

在大多数情况下,新添加的代码仅在创建新的经过快照修改的克隆时调用,因此现有代码的性能预计不会发生变化。唯一的例外是,在创建 SNAPSHOT_AT_LEAST_ON_WRITE 子级时,简单的方法会额外获取 VMO 锁,但如果这会造成性能损失,则可以轻松重构克隆选择代码来避免这种情况。

安全注意事项

Snapshot-modified 不太可能引入任何漏洞,因为它使用现有的 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 创建的任何具有非 slice 子级或不是由分页支持的 VMO 的子级的 VMO,也不支持此标志。

缺点、替代方案和未知因素

使用现有的 Zircon VMO 原语为分页器支持的 VMO 创建快照语义并非易事。用户分页器通过处理对单个 VMO 的页面请求来运行,目前其子级形成一个具有写入时复制语义的单向链。

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

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

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

您可以通过多种方式向用户描述所提供的语义。RFC 中的说明概述了与分页器相关的提供行为,但另一种表达方式是仅针对网页的修改和克隆。示例如下:

“此标志将创建一个子级,该子级会保留父级中任何已修改页面的快照。如果根 VMO 在发生快照后写入未修改的页面,则快照修改的子 VMO 将看到这些更改。这与原始快照语义不同,后者表现得好像创建了急切副本。”

此说明是正确的,但需要进一步说明此行为的注意事项,即需要分页器,因为当在匿名 VMO 上使用该标志时,该标志将升级为快照语义。

SNAPSHOT_MODIFIED 是否可以取代 SNAPSHOT_AT_LEAST_ON_WRITE?

逐步淘汰 snapshot-at-least-on-write 并将其替换为 snapshot-modified 并非易事,因为这两种克隆类型的语义不同,可能会导致意外行为。虽然这两种克隆类型都提供“至少写入时复制”语义,但 snapshot-modified 可以在同一 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()