| RFC-0204:VMO 参考子级 | |
|---|---|
| 状态 | 已接受 |
| 区域 |
|
| 说明 | 用于参考跟踪的 VMO 子类型。 |
| 问题 | |
| Gerrit 更改 | |
| 作者 | |
| 审核人 | |
| 提交日期(年-月-日) | 2023-01-04 |
| 审核日期(年-月-日) | 2023-01-03 |
摘要
本文档旨在介绍一种新的 VMO 子类型,用于明确跟踪对 VMO 的引用。
设计初衷
使用 VMO 表示内存中文件的文件系统需要跟踪何时可以安全地对这些 VMO 进行垃圾回收。这可能包括将修改后的 VMO 内容刷新到磁盘、从 userpager 服务分离 VMO、拆除 VMO 本身(从而释放其页面)以及释放文件系统可能用于跟踪打开文件的任何内部元数据等活动。
文件系统可以通过多种方式与客户端共享文件 VMO。它们可以向 VMO 售出重复句柄,客户端可以使用这些句柄直接从 VMO 中的页面读取数据和向 VMO 中的页面写入数据。客户端还可以使用 VMO 句柄通过 zx_vmar_map() 创建虚拟机映射,舍弃 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 上变为活跃状态来执行垃圾回收。这同样适用于 VM 映射,因为即使 VMO 句柄被舍弃,VMO 也会因映射而在内部保持活动状态。Blobfs 今天没有发放直播。
这种使用 CoW 克隆的引用计数策略对于不可变的文件系统来说效果很好,但如果应用于可变的文件系统(如 fxfs 或 minfs),则会失效。在可变文件系统中,对客户端的文件引用所做的任何更改都应反映回原始文件中。换句话说,在 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 的引用,因此 offset 和 length 参数没有意义,并且都需要设置为 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_VMO_RESIZABLE 选项标志分开可体现这种区别。
引入新的 ZX_RIGHT_RESIZE 需要将其集成到所有现有的 VMO API 中(如果适用)。如果 VMO 句柄是使用 ZX_VMO_RESIZABLE 标志创建的,则可以通过将其添加到 VMO 句柄的默认权限集中来实现此目的;如果是 VMO 子级,则可以通过将其添加到 ZX_VMO_CHILD_RESIZABLE 中来实现此目的。
为可调整大小的 VMO 的引用创建的所有虚拟机映射都需要 ZX_VM_ALLOW_FAULTS 标志,无论引用句柄本身是否具有 ZX_RIGHT_RESIZE。
与 VMO slice 的区别
参考子项提供的许多功能与 ZX_VMO_CHILD_SLICE 重叠。主要有两个区别:
- 切片可以提供 VMO 中子范围的窗口。引用始终涵盖整个 VMO。
- 无法为可调整大小的 VMO 创建切片,切片本身也无法调整大小。可以为可调整大小的 VMO 创建引用,并且引用本身也可以调整大小(如果父级可调整大小)。
支持的 VMO 类型
可以为使用 zx_vmo_create() 或 zx_pager_create_vmo() 创建的所有 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。
当针对引用创建子项时,即使从概念上讲,子项可以被视为原始 VMO 的子项,但子项的 parent_koid 仍会指向引用。目的是准确表示后代 VMO 的创建链;此行为与嵌套切片情况下子级创建的现有行为一致。
网页归因
由于引用中的所有网页都指向父 VMO 中的网页,因此父级将继续被归因于所有网页,如 committed_bytes (zx_object_get_info()) 所报告的那样。引用不直接包含任何网页,并且网页归因计数始终为零。这与现有的 VMO 网页归因模型一致,在该模型中,每个网页都归因于一个且仅一个 VMO。
调整参考的大小
调整参考的大小会调整父 VMO 的大小。同样,父 VMO 的大小调整将通过引用反映出来。引用也将遵循父 VMO 的内容大小,并且能够以类似于 VMO 大小的方式访问和操纵它。
内核变更
以下是 VMO 的内核实现中需要进行的大致更改。
- 引用子项将指向与其父项相同的页面容器 (
VmCowPages)。这样一来,对引用的操作就会转发到父级,因为它们都将针对同一内部对象执行,从而实现所需的效果。 - 系统会在父级的子级列表中跟踪引用,从而允许在父级上使用
ZX_VMO_ZERO_CHILDREN信号。 - 父 VMO 还会维护一个包含所有引用子项的新列表。某些操作(例如将更新传播到在页面容器外部跟踪的虚拟机映射)需要此权限。
- 引用还将与父级共享相同的
ContentSizeManager,以便正确更新内容大小,该大小用于对根据 VMO 创建的流进行读取和写入。 - 引用不会使父 VMO 保持活跃状态。父级消失后,引用仍可继续访问共享页面容器,因为引用仍指向该容器,所以该容器会一直处于有效状态。引用子级将重新归入祖父级(如果有),这与现有子级类型的情况相同。父级的引用列表将移至其中一个引用,以便虚拟机映射更新继续正常运行。这一切可确保用户观察到的行为与当前切片中的现有行为保持一致。
实现
可以在几个小型 CL 中实现引用,这些 CL 在 VMO 内核内部添加了对新子类型的支持,然后通过 zx_vmo_create_child() 系统调用公开了新的类型标志。
新的 ZX_RIGHT_RESIZE 将在创建时添加到可调整大小的 VMO 的默认权限集中。因此,在 zx_vmo_set_size() 中强制执行该权利不应影响可调整大小的 VMO 的现有用户。我们可以先从简单的开始,要求同时提供 ZX_RIGHT_WRITE 和 ZX_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 句柄并不能准确反映未完成引用的数量。可以通过 VM 映射和流来保留对 VMO 的引用,而无需保留用于创建这些 VMO 的 VMO 句柄。我们还需要考虑虚拟机映射、视频流以及未来可能包含对 VMO 的引用的任何新对象。这种方法很快就会变得复杂,而且无法扩缩。此外,句柄计数本身就存在竞态条件,不建议将其用于调试以外的目的。
参考子方法可以更好地捕获文件系统及其客户端之间的关系,而这种关系实际上是分层式的。文件系统对 VMO 的引用可以视为主要引用,而它向客户端分发的所有其他引用都是次要引用。父子关系很好地体现了这一点。另一方面,句柄是对称的;在这种情况下,我们不仅关心是否只剩下一个引用,还希望剩下的那个引用是文件系统持有的引用。
参考令牌
我们可以铸造一个新的引用令牌对象,文件系统除了 VMO 或流之外,还可以分发该对象。这可以是内核提供的新对象,也可以是文件系统实现的对象。不过,这需要更改文件系统 API Surface 以支持传递新的引用令牌,这可能不可行。引用子对象可以与 VMO 互换使用,这使得支持它们变得更加容易,同时保留了现有的文件系统 API。在 VMO 之外具有外部引用计数概念也容易出错,因为可能会意外丢弃令牌。
调整 Slice 的大小
引用子项与切片的主要区别在于是否能够调整大小。因此,一种替代方案是在切片的上下文中定义调整大小的语义,而不是创建新的 VMO 子类型。不过,切片可以跨越父范围中的子范围,这使得调整大小的推理变得更加困难。
例如,假设某个 slice 的大小调整为大于其创建时的大小。如果切片现在能发现之前看不到的父网页,那才令人惊讶。另一方面,切片不直接拥有任何页面,因此如果它在扩展范围内派生零个页面,就会显得很奇怪。如果父级调整为更大的尺寸,也会出现类似的问题。由于激励性用例要求在父级和子级之间传播调整大小,因此这也需要调整任何切片的大小,这同样可能会令人意外。
向右调整大小
目前,VMO 的可调整尺寸性由 ZX_RIGHT_WRITE 控制。拥有单独的 ZX_RIGHT_RESIZE 可在未来提供更严格的违规处置机会。例如,它可以允许用户创建可写入的不可调整大小的 VMO 句柄,以与客户端共享。目前无法创建此类 VMO 句柄;可写入的 VMO 句柄也是可调整大小的(如果 VMO 是可调整大小的)。