RFC-0204:VMO 参考子项

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

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

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)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 子类型。从表面上看,我们似乎需要 Slice,因为它们提供了直接进入父级页面的窗口。不过,slice 存在以下限制:无法针对可调整大小的 VMO 创建,并且无法自行调整大小。可变文件系统希望允许客户端调整其文件 VMO 的大小。因此,目前可用的所有三种 VMO 子类型在使用 VMO 子项的引用计数方案中都存在不足。

利益相关方

教员

  • cpu@google.com

Reviewers:

  • adanis@google.com, godtamit@google.com

咨询了

  • brettw@google.com、cdrllrd@google.com、custer@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_RESIZE。不可调整大小的引用仍可由父级调整大小;此处的不可调整大小仅限制了通过引用直接调整大小的功能。将 ZX_RIGHT_RESIZE 权限与 ZX_VMO_RESIZABLE 选项标志分开可以捕获这一区别。

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

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

与 VMO 切片的区别

引用子项提供的许多功能与 ZX_VMO_CHILD_SLICE 重叠。这两种方法之间存在一些关键区别:

  • Slice 可以提供 VMO 中子范围的视窗。引用始终跨整个 VMO。
  • 无法为可调整大小的 VMO 创建 slice,它们本身也无法调整大小。您可以为可调整大小的 VMO 创建引用,并且这些引用本身也可以调整大小(如果父项可调整大小)。

支持的 VMO 类型

您可以为使用 zx_vmo_create()zx_pager_create_vmo() 创建的所有 VMO 以及此类 VMO 的后代创建引用子项。这不包括使用 zx_vmo_create_contiguous()zx_vmo_create_physical() 创建的 VMO,但并不完全排除这些用例。连续 VMO 和实体 VMO 均不可调整大小,因此用户可以改为在整个 VMO 上创建 slice 以获得等效行为。

与 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() 系统调用公开新类型标志。

新的 ZX_RIGHT_RESIZE 将在创建时添加到可调整大小的 VMO 的默认权限集。因此,强制执行 zx_vmo_set_size() 中的权限不应破坏可调整大小的 VMO 的现有用户。我们可以先从简单的开始,同时要求 ZX_RIGHT_WRITEZX_RIGHT_RESIZE。然后,您可以执行审核,以查找正在移除 ZX_RIGHT_WRITE 以防止调整大小的现有用例,并让这些用例改为移除 ZX_RIGHT_RESIZE(或者在移除 ZX_RIGHT_WRITE 的同时移除 ZX_RIGHT_RESIZE)。将来,zx_vmo_set_size() 只能检查 ZX_RIGHT_RESIZE

性能

对引用执行的操作就像直接对父级 VMO 执行一样。这通过在内核中共享相同的内部页面容器来实现。因此,对于大多数操作,使用引用而非原始 VMO 不应会产生任何明显的性能影响。我们将编写虚拟机微基准测试来验证这一点。

安全注意事项

可对参考子项执行的所有操作都可以对父级 VMO 本身执行,创建引用时需要使用父级 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 之间的唯一主要区别在于是否能够调整大小。因此,另一种方法是在 slice 上下文中定义 resize 的语义,而不是创建新的 VMO 子类型。不过,slice 可以跨父级中的子范围,这使得推理调整大小变得更加困难。

例如,假设将某个 Slice 的大小调整为大于其创建大小的大小。如果该 Slice 现在会显示之前无法看到的父级页面,那就很奇怪了。另一方面,slice 不会直接拥有任何页面,因此如果它在扩展范围内分叉零个页面,就会很不方便。如果将父项调整为更大的尺寸,也会出现类似问题。由于促使我们进行此项研究的用例要求在父级和子级之间传播大小调整,因此这也需要调整任何 Slice,这可能又会令人惊讶。

向右调整大小

目前,VMO 可调整大小功能由 ZX_RIGHT_WRITE 控制。拥有单独的 ZX_RIGHT_RESIZE 有助于日后更严格地执行违规处置措施。例如,它可以允许用户创建可写入且不可调整大小的 VMO 句柄,以便与客户端共享。目前无法创建此类 VMO 句柄;可写 VMO 句柄也支持调整大小(如果 VMO 在创建时支持调整大小)。

在先技术和参考文档

zx_vmo_create_child()

zx_vmar_map()

zx_stream_create()