RFC-0236:VMO 快照修改后的克隆 | |
---|---|
状态 | 已接受 |
区域 |
|
说明 | 目标是引入一种新的 VMO 子类型,以便能够截取快照,捕获由页面浏览器支持的 VMO 中的任何修改过的页面。 |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2023-07-20 |
审核日期(年-月-日) | 2023-12-12 |
摘要
目标是引入一种新的 VMO 子类型,以便对由页面浏览器支持的 VMO 的子项中的任何修改过的页面进行快照。
设计初衷
目前,内核支持两种类型的 VMO 克隆(也称为子 VMO):在真实快照中,克隆操作完成后,两个 VMO 都不会看到对方的更改;在至少在写入时快照的克隆中,子 VMO 可以在操作完成后看到父 VMO 上的更改,但子 VMO 上已写入的页面除外。
您可以对 VMO 克隆本身重复执行克隆操作,从而创建子 VMO 的层次结构。为了简化实现,Zircon 内核不允许混合层次结构;您可以创建由真实快照组成的层次结构,也可以创建由写入时快照 VMOs 组成的层次结构。
随着 Starnix 的发明,Fuchsia 必须高效支持 fork()。分叉需要将父进程的整个地址空间克隆到子进程中,其中包括匿名内存,但也包括由分页器支持的数据和代码 VMO。
出现问题的原因在于,内核仅支持匿名内存的真实快照,而对于分页器支持的 VMO,它仅支持写入时快照克隆。因此,无法通过克隆来满足 fork() 协定:因此,Starnix 被迫实现了提前复制和/或其他 CPU 和内存密集型权宜解决方法。
利益相关方
哪些人对此 RFC 的接受与否有利益相关?(此部分为可选部分,但建议填写。)
csuter@google.com、jamesr@google.com
教员:
davemoore@google.com
Reviewers:
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
使用的现有克隆类型升级语义。
此标志不适用于克隆具有固定区域、slice 或从 zx_vmo_create_physical()
或 zx_vmo_create_contiguous()
派生的 VMO。
病例数量
在有分页器支持的 VMO 上进行快照修改
创建基于分页器的 VMO 的单个 SNAPSHOT_MODIFIED
克隆将表现为创建了单个 SNAPSHOT_AT_LEAST_ON_WRITE
克隆。执行克隆操作时,新的 VMO 将与父级完全相同。
在对克隆执行另一个 SNAPSHOT_MODIFIED
之前,克隆仍可修改。克隆中的所有未修改页面都至少具有写时复制语义。
在至少写入时修改或快照修改后修改
的 VMO
由于 SNAPSHOT_MODIFIED
和 SNAPSHOT_AT_LEAST_ON_WRITE
在有分页器支持的 VMO 上具有相同的行为,因此在有分页器支持的 VMO 的克隆上调用 SNAPSHOT_MODIFIED
的两种情况都会产生相同的语义。不再由分页器支持的任何页面都将具有快照,并且由分页器支持的页面至少具有写时复制语义。
快照修改(快照后)
在这种情况下,语义将升级为快照,类似于“至少在写入时快照”。
不支持的情况
以下情况目前不受支持,如果尝试克隆 SNAPSHOT_MODIFIED
,系统会返回 ZX_ERR_NOT_SUPPORTED
。
至少在写入时进行快照的链的快照修改端
Snapshot-modified 可能会扩展为在至少在写入时快照链的末尾使用。不过,这可能会产生混淆的结果,因为克隆的 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 锁,但如果这会导致性能下降,只需对克隆选择代码进行重构即可轻松避免这种情况。
安全注意事项
由于快照修改版是使用现有的 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,该 VmCowPages 是目标 VMO 及其新快照的共同祖先。由于没有任何指向隐藏 VmCowPage 的项可以修改其页面,因此它是不可变的。此隐藏的 VmCowPages 会保留目标 VMO 中的页面,子项中的修改具有写时复制语义。因此,若要搜索此层次结构中的网页,需要沿着树向上遍历,直到到达隐藏的根目录。
很难将现有的快照数据结构用于分页器,因为根 VMO 始终会隐藏,原始 VMO 会成为左侧子项。如果分页器仍指向原始 VMO(现在具有隐藏的父级),则必须将分页器操作向上传播到隐藏的根,以便在其子项的请求下为其提供页面。由于页面会添加到未运行的节点,因此会导致不一致。
最简单的解决方案是创建一个混合层次结构,其中根目录是可见的,并且有一个隐藏的子目录,该子目录充当快照树的隐藏根目录。
向用户描述所提供的语义有多种方式。RFC 中的说明概述了与分页器相关的提供行为,但另一种构建方式是仅修改和克隆页面。例如:
此标志将创建一个子项,用于保留父级中所有已修改页面的快照。如果根 vmo 在快照发生后写入未修改的页面,则经过快照修改的子 vmo 将会看到这些更改。这与原始快照语义不同,后者会像创建了提前副本一样。”
此说明是正确的,但需要进一步说明此行为需要分页器这一注意事项,因为该标志在用于匿名 VMO 时会升级为快照语义。
SNAPSHOT_MODIFIED 能否替代 SNAPSHOT_AT_LEAST_ON_WRITE?
逐步淘汰“至少在写入时快照”并将其替换为“快照修改”并非易事,因为这两种克隆类型的语义不同,这可能会导致意外行为。虽然这两种克隆类型都提供“至少写时复制”语义,但“快照修改”可以在同一 VMO 中混合使用快照页和至少写时复制页。此外,此更改还需要进行性能测试。当 VMO 由分页器支持时,至少在写入时创建快照会为每个创建的克隆分配更少的内存,因为系统不会创建任何隐藏的公共祖先。因此,迁移所有“至少在写入时创建快照”的用法可能会在某些用例中导致性能下降。
不过,不妨考虑替换 SNAPSHOT_AT_LEAST_ON_WRITE
,因为这会简化 zx_vmo_create_child()
的 API,因为大多数 fdio 辅助程序都承诺提供与 SNAPSHOT_MODIFIED
兼容的语义。