RFC-0204:VMO 参考子项

RFC-0204:VMO 参考子项
状态已接受
领域
  • 内核
说明

用于参考文件跟踪的 VMO 子类型。

问题
  • 81316
Gerrit 更改
  • 751426
作者
审核人
提交日期(年-月-日)2023-01-04
审核日期(年-月-日)2023-01-03

总结

本文档的目标是引入一种新的 VMO 子类型,用于明确跟踪对 VMO 的引用。

设计初衷

使用 VMO 表示内存中文件的文件系统需要跟踪何时可以安全地对这些 VMO 进行垃圾回收。这可能包括以下活动:将修改后的 VMO 内容刷新到磁盘、将 VMO 与 userpager 服务分离、拆除 VMO 本身,从而释放其页面,以及释放文件系统可能用来跟踪打开文件的任何内部元数据。

文件系统可以通过多种方式与客户端共享文件 VMO。它们可以向 VMO 提供重复的句柄,客户端可以使用该句柄直接对 VMO 中的页面执行读写操作。客户端还可以使用具有 zx_vmar_map() 的 VMO 句柄创建虚拟机映射,删除 VMO 句柄,然后通过虚拟机映射中的地址继续访问 VMO 页面。文件系统还可以使用带有 zx_stream_create() 的 VMO 来创建流,并将流句柄发送给客户端。需要一种统一的引用计数机制来跟踪所有这些不同类型的引用。

Blobfs 是为 Fuchsia 提供可执行文件的不可变文件系统,它尝试通过创建文件 VMO 的写入时复制 (ZX_VMO_CHILD_SNAPSHOT_AT_LEAST_ON_WRITE) 克隆来解决此引用计数问题。文件 VMO 的唯一句柄由 blobfs 保存,用于在客户端每次请求文件时创建 CoW 克隆。它是 blobfs 分发的克隆的句柄,而不是原始 VMO。然后,引用计数会映射到跟踪未完成的克隆的数量,并且 blobfs 可以通过等待 ZX_VMO_ZERO_CHILDREN 信号在原始 VMO 上变为活动状态,在克隆计数降为零时进行垃圾回收。这也适用于虚拟机映射,因为即使 VMO 句柄被丢弃,映射也会在内部使 VMO 保持活跃状态。Blobfs 今天不分发直播。

这种使用 CoW 克隆的引用计数策略适用于不可变文件系统,但在应用于 fxfsminfs 等可变文件系统时会中断。在可变文件系统中,对客户端对文件的引用所做的任何更改都会反映在原始文件中。换句话说,在 CoW 克隆中执行的任何写入操作都应返回到父级 VMO。这违反了 CoW 语义,因为在 CoW 克隆中写入页面时,内核会在克隆中创建父级页面的副本;克隆的更改永远不会在父级中显示。快照克隆 (ZX_VMO_CHILD_SNAPSHOT) 也存在一个类似问题。子级中的更新绝不会在父级中显示,反之亦然。

这留下了 ZX_VMO_CHILD_SLICE VMO 子级类型。从表面上看,切片可能就是我们所需的,因为它们提供了一个指向父页面的直接窗口。但是,切片存在以下限制:无法针对可调整大小的 VMO 创建它们,并且无法自行调整大小。可变文件系统希望允许客户端调整其文件 VMO 的大小。因此,目前可用的所有三种 VMO 子级类型都不符合使用 VMO 子级的引用计数方案。

利益相关方

教员

  • cpu@google.com

审核者

  • adanis@google.com、godtamit@google.com

咨询人员

  • brettw@google.com、cdrllrd@google.com、csuter@google.com、travisg@google.com

社交

该设计与 Zircon 团队进行了社交,并在内核演变工作组中进行了讨论。

设计

ZX_VMO_CHILD_REFERENCE 是一种新的 VMO 子级,用于跟踪对 VMO 的未完成引用。这样做的目的是,每当文件系统需要为客户端的文件 VMO 的新引用时,都会创建一个引用子项并将其视为原始 VMO。将根据引用子级(而不是原始 VMO)创建数据流。同样,系统会为引用子级创建虚拟机映射。这样一来,内核便可以使用现有的 VMO 子项跟踪机制来跟踪对 VMO 的引用数。文件系统可以使用 ZX_VMO_ZERO_CHILDREN 信号来确定何时不再有任何引用,可以安全销毁 VMO。

请注意,重复使用 ZX_VMO_ZERO_CHILDREN 信号意味着,只要存在任何类型的子项(而不仅仅是引用子项),文件系统便无法拆解原始 VMO。然而,这没有问题,并允许文件系统根据其要求混合不同的子类型。例如,对于需要仅限客户端写入的数据段,blobfs 可以继续使用 CoW 克隆;对于文本段,请使用引用子项(通过 ZX_VMO_CHILD_NO_WRITE 创建只读)。它仍然可以将 ZX_VMO_ZERO_CHILDREN 信号用于统一的引用计数方案。

引用子级创建

zx_vmo_create_child() 系统调用将支持新的选项标记 ZX_VMO_CHILD_REFERENCE。由于从概念上来讲,这是对父级 VMO 的引用,因此 offsetlength 参数没有意义,并且两者都需要设置为 0。子引用将始终指整个父 VMO。另一种方法是让调用方将 offset 指定为 0,并将 length 指定为 VMO 的当前大小,但这会给调用方带来过重的负担,使调用方始终了解父 VMO 的大小,而这可能需要调用 zx_vmo_get_size()。如果文件系统客户端不断更改 VMO 大小(例如,通过附加到文件),文件系统可能需要进行多次 zx_vmo_get_size() 调用,直到看到稳定大小为止,这非常麻烦。

控制 ZX_VMO_ZERO_CHILDREN 信号的相同规则将作为现有子类型应用于引用子级。创建参考子项将会停用该信号(如果该信号之前为活跃状态),销毁最后一个子项会激活该信号。

尺寸可调整性

仅当父级 VMO 是使用 ZX_VMO_RESIZABLE 创建的时,才允许使用 ZX_VMO_CHILD_RESIZABLE。您可以为可调整大小的 VMO 创建不可调整大小的引用或可调整大小的引用。但是,无法为不可调整大小的 VMO 创建可调整大小的引用;可以将参考子项视为对父 VMO 的引用;如果父 VMO 不可调整大小,则没有办法调整引用子项的大小。

引用将继承父级的 ZX_VMO_RESIZABLE 选项。引用句柄本身的大小可调整性将由句柄上的新 ZX_RIGHT_RESIZE 决定,该引用仅在使用 ZX_VMO_CHILD_RESIZABLE 创建引用时才添加。不可调整大小的引用仍然可以由父项调整大小;这里的不可调整大小只是限制通过该引用直接调整大小的能力。将 ZX_RIGHT_RESIZEZX_VMO_RESIZABLE 选项标志隔开即可捕获此区别。

如果引入新的 ZX_RIGHT_RESIZE,则需要将其集成到所有现有的 VMO API(如果适用)。为此,您可以将该权限添加到 VMO 句柄的默认权限集(如果使用 ZX_VMO_RESIZABLE 标志创建)或 ZX_VMO_CHILD_RESIZABLE(如果是 VMO 子级)。

为引用可调整大小的虚拟机 O 创建的所有虚拟机映射都需要 ZX_VM_ALLOW_FAULTS 标志,无论引用句柄本身是否具有 ZX_RIGHT_RESIZE

与 VMO 切片的区别

参考子项提供的很多功能都与 ZX_VMO_CHILD_SLICE 重叠。但有几个关键的区别:

  • Slice 可以为 VMO 中的子范围提供一个窗口。引用始终跨越整个 VMO。
  • 无法为可调整大小的 VMO 创建切片,切片本身也无法调整大小。您可以为可调整大小的 VMO 创建引用,也可以自行调整引用(如果父级可调整大小)。

支持 VMO 类型

可以为使用 zx_vmo_create()zx_pager_create_vmo() 创建的所有 VMO 及其后代创建引用子项。这不包括使用 zx_vmo_create_contiguous()zx_vmo_create_physical() 创建的 VMO,但不完全排除这些用例。连续 VMO 和物理 VMO 都不可调整大小,因此用户将能够针对整个 VMO 创建切片,以获得等效的行为。

与 VMO 操作交互

针对引用的所有 VMO 操作只会转发到父级。例如:

  • VMO 读写操作将像直接在父级 VMO 上执行。
  • 提交和取消提交也会转发到父级。
  • 根据子引用创建虚拟机映射时,映射将映射父项中的相应页面。

创建引用的子项

能否创建引用的子项将由与在父 VMO 上创建子项相同的一组规则控制。例如,如果父 VMO 是由分页器支持的 VMO,因此不支持 ZX_VMO_CHILD_SNAPSHOT,则其引用子 VMO 也不支持 ZX_VMO_CHILD_SNAPSHOT

根据某个引用创建子项时,子项的 parent_koid 将指向引用,即使从概念上来说该子项可以被视为原始 VMO 的子项也是如此。其目的是准确表示后代 VMO 的创建链;此行为与嵌套 Slice 中子项创建的现有行为保持一致。

网页归因

由于引用中的所有页面都引用了父级 VMO 中的页面,因此正如 committed_bytes (zx_object_get_info()) 所报告的那样,系统将继续对所有页面进行父级归因。引用不直接包含任何页面,并且页面归因计数始终为零。这与 VMO 网页归因的现有模型一致,其中每个网页仅归因于一个 VMO。

调整引用大小

调整引用的大小将会调整父级 VMO 的大小。同样,父 VMO 的大小调整将通过引用反映。引用也将遵循父 VMO 的内容大小,并且能够像 VMO 大小那样访问和操纵它。

内核变更

这些是 VMO 的内核实现将需要进行的广泛更改。

  • 子引用将指向与其父项相同的页面容器 (VmCowPages)。这会对转发到父项的引用产生预期效果,因为它们将针对同一内部对象执行。
  • 引用将在父级的子级列表中跟踪,从而允许在父级使用 ZX_VMO_ZERO_CHILDREN 信号。
  • 父级 VMO 还会维护一个包含所有参考子级的新列表。对于某些操作(例如,将更新传播到虚拟机映射),此操作在页面容器之外进行跟踪。
  • 引用还将与父级共享相同的 ContentSizeManager,以便正确更新内容大小,用于针对针对 VMO 创建的流上的读取和写入操作。
  • 引用不会使父级 VMO 保持活跃状态。一旦父级消失后,引用就可以继续访问共享页面容器,由于引用仍指向该容器,该容器将会保持活跃状态。引用子项将在祖父项(如果有)中重新归置,与现有子类型一样。父级的引用列表将移动到其中一个引用,以便虚拟机映射更新继续正常运行。所有这一切都可确保用户观察到的行为与现有 Slice 中的现有行为一致。

实现

您可以在几个小型 CL 中实现引用,这些 CL 添加了对 VMO 内核内部结构中的新子级类型的支持,然后通过 zx_vmo_create_child() 系统调用公开新的类型标志。

在创建可调整大小的 VMO 时,新的 ZX_RIGHT_RESIZE 将添加到一组默认权限中。因此,在 zx_vmo_set_size() 中强制执行权限不会破坏可调整大小的 VMO 的现有用户。我们可以从同时请求 ZX_RIGHT_WRITEZX_RIGHT_RESIZE 很简单。然后,可以执行审核以查找移除 ZX_RIGHT_WRITE 以防止大小调整的现有情况,并在这些情况下移除 ZX_RIGHT_RESIZE 或对 ZX_RIGHT_WRITE 进行补充。将来,zx_vmo_set_size() 只能检查 ZX_RIGHT_RESIZE

性能

对引用执行的操作就像直接在父级 VMO 上执行操作一样。通过在内核中共享同一内部页面容器,可以实现此目的。因此,使用引用而不是原始 VMO 应该不会对大多数操作产生任何可观察到的性能影响。系统将写入虚拟机 Microbenchmark 来验证这一点。

安全注意事项

可对参考子项执行的所有操作都可以在父 VMO 本身上执行,该句柄是创建引用所必需的。引用只是用来替换原始 VMO 的某些用法,因此可以在没有引用的情况下执行这些操作。

测试

将为参考子项添加内核单元测试和核心测试。

文档

zx_vmo_create_child() 系统调用文档将更新为 ZX_VMO_CHILD_REFERENCE。其他 VMO 系统调用的文档将需要进行更新,以包含 ZX_RIGHT_RESIZE(如适用)。

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

统计手柄

另一种方法是引入通用的句柄计数方案,该方案会在对象仅有一个句柄时激活对象的信号。但是,VMO 句柄并不能准确表示未完成的引用数量。您可以通过虚拟机映射和流保存对 VMO 的引用,而无需保留用于创建它们的 VMO 句柄。我们还需要考虑虚拟机映射、数据流以及未来可能会存储对 VMO 的引用的任何新对象。此过程很快就会变得复杂,并且无法扩缩。此外,句柄计数在本质上是随机的,不建议将其用于调试之外的目的。

参考子方法可以更好地捕获文件系统与其客户端之间的关系,这些关系实际上是分层的。文件系统对 VMO 的引用可被视为主要引用,它传递给客户端的所有其他引用均为次要引用。父子关系可以很好地体现这一点。另一方面,句柄是对称的;在这种情况下,我们不仅需要关心只保留一个引用,还希望其余一个引用是文件系统存储的引用。

参考令牌

我们可以创建一个新的引用令牌对象,除了 VMO 或数据流之外,文件系统还可以分发该对象。这可以是内核提供的新对象,也可以是文件系统实现的对象。但是,这需要更改文件系统 API Surface,以支持传递新的引用令牌,但这可能不可行。参考子项可与 VMO 互换使用,从而在保留现有文件系统 API 的同时更轻松地支持它们。在 VMO 之外使用外部引用计数这一概念也很容易出错,因为有可能会意外丢弃令牌。

调整 Slice 的大小

引用子级与 Slice 的唯一主要区别在于能够调整大小。因此,另一种方法是在切片的上下文中定义调整大小的语义,而不是创建新的 VMO 子类型。但是,Slice 可以跨越父级中的子范围,这使得调整大小的推理变得更困难。

例如,假设某个切片的大小被调整为大于其创建大小。如果切片现在能够发现之前对其不可见的父页面,那就令人惊讶了。另一方面,切片并不直接拥有任何页面,因此如果在扩展范围内针对零个页面创建分支,就会很尴尬。如果将父视图调大到较大的尺寸,也会出现类似问题。由于激励性用例需要调整尺寸在父项和子项之间传播,因此还需要调整任何 Slice 的大小,这一点同样可能令人惊讶。

向右调整大小

目前,VMO 的大小调整功能由 ZX_RIGHT_WRITE 控制。如果拥有单独的 ZX_RIGHT_RESIZE,未来将会有更强的执行机会。例如,它可以允许用户创建可写的不可调整大小的 VMO 句柄,以便与客户端共享。目前,无法创建此类 VMO 句柄;可写的 VMO 句柄也可以调整大小(如果 VMO 创建为可调整大小)。

早期技术和参考资料

zx_vmo_create_child()

zx_vmar_map()

zx_stream_create()