RFC-0238:VMO 大小

RFC-0238:VMO 大小
状态已接受
区域
  • 内核
说明

定义 VMO 的大小和数据流大小。

Gerrit 更改
作者
审核人
提交日期(年-月-日)2024-01-10
审核日期(年-月-日)2024-01-10

摘要

以前,Zircon 会为每个 VMO 关联两个不同的大小:页面级别的 VMO 大小,以及字节级别的 VMO 内容大小。此 RFC 合理化了 Zircon 系统接口中对这两种大小的处理。

设计初衷

Zircon 系统接口的 VMO 相关部分在处理 VMO 大小内容大小方面不一致。这种不一致是这些接口演变方式的产物。在不同时间点,我们尝试以字节粒度和页面粒度表示每个 VMO 的大小,在本 RFC 之前,还尝试以字节粒度和页面粒度同时表示。这种不一致性会使 Zircon 系统接口难以演变,例如难以添加更复杂的分页接口。

利益相关方

哪些人对此 RFC 的接受与否有利益相关?(此部分为可选部分,但建议填写。)

教员

  • hjfreyer@google.com

审核者

  • cpu@google.com
  • csuter@google.com
  • jamesr@google.com
  • rashaeqbal@google.com

咨询了

  • adanis@google.com
  • dworsham@google.com
  • sagebarreda@google.com

社交

此 RFC 中的提案是本地存储空间团队、虚拟内存管理器团队和架构团队之间进行的一系列讨论的成果。在考虑了各种方法后,该团队决定对 Zircon 系统接口提出一些相当温和的更改,如下所述。

要求

  • 进行这些更改后,系统必须继续正常运行。例如,文件必须具有字节粒度长度,因为大量代码都依赖于文件长度为字节粒度。
  • 该设计必须能够高效地在内核和硬件中实现。例如,内存映射必须采用页面粒度,因为硬件支持这种粒度。
  • 设计应尽量减少对任何公共接口(无论是内核还是 SDK 库)的更改。例如,FDIO 有一个公共接口,用于将 VMO 转换为文件描述符,而不会保留任何其他上下文信息,这些信息应予以保留。不过,如果迁移客户端可行,则可以对公共接口进行细微更改。
  • 设计应尽可能简单。虚拟内存管理器已经是系统中一个重要的复杂性来源。每当我们向系统的这一部分添加更多复杂性时,都需要谨慎行事。

本文档中的关键字“必须”“不得”“必需”“会”“不会”“应”“不应”“建议”“可以”和“可选”将按 IETF RFC 2119 中的描述进行解释。

设计

从概念上讲,VMO 是一组稀疏的内存页面。VMO 在整个系统中用于多种用途,包括存储和共享以字节为粒度的存在的数据。VMO 有多种类型,具体取决于 VMO 中存储的内存类型。例如,VMO 可能包含由分页器或物理内存支持的虚拟内存,后者与硬件资源之间存在更具体的关系。

VMO 具有大小,表示可在 VMO 中存储的内存量上限。此大小始终是页面大小的偶数倍,因为 VMO 以页面精细程度存储数据。客户端可以使用 zx_vmo_get_size 函数了解 VMO 的大小。如果 VMO 是使用 ZX_VMO_RESIZABLE 选项创建的,则客户端可以使用 zx_vmo_set_size 函数更改 VMO 的大小,前提是它们具有具有 ZX_RIGHT_RESIZE 的句柄。绝大多数 VMO 操作都与此大小交互。

VMO 还有一个数据流大小,表示 VMO 中存储的数据流(如果有)的字节数。在创建 VMO 时,系统会初始化数据流大小,通常基于用于创建 VMO 的函数的参数,并且不一定是页面大小的偶数倍数。数据流大小绝不会大于 VMO 大小,因为存储在 VMO 中的数据流不能大于 VMO 中可存储的内存量上限。客户端可以使用 zx_vmo_get_steam_size 函数了解 VMO 的串流大小。如果 VMO 适合存储数据流,客户端可以使用 zx_vmo_set_stream_size 函数更改 VMO 的数据流大小,前提是它们具有具有 ZX_RIGHT_WRITE 权限的句柄。zx_vmo_set_size 函数不会修改数据流大小,除非强制执行数据流大小永远不大于 VMO 本身大小的不变性。通常,客户端会使用与 VMO 关联的流对象上的 zx_stream_* 函数与流大小进行交互。与同一 VMO 关联的所有数据流对象之间会共享数据流大小。

ZX_VMO_UNBOUNDED

当客户端使用 zx_vmo_create 创建 VMO 时,size 参数用于初始化 VMO 的大小和数据流大小。VMO 的大小会初始化为向上舍入到最接近的页边界的大小。VMO 的流大小会初始化为 size,而不会进行任何舍入。

此 RFC 为 zx_vmo_createzx_pager_create_vmo 引入了 ZX_VMO_UNBOUNDED 选项,用于创建大小较大的 VMO。如果客户端提供此选项,则 VMO 的大小会初始化为可能的最大值,而不是根据 size 参数进行初始化。ZX_VMO_UNBOUNDED 选项适用于 VMO 大小没有实用用途的情况,例如由分页器支持的文件系统使用的 VMO 以及为 C 运行时堆提供支持的 VMO。因此,ZX_VMO_UNBOUNDED 不能与 ZX_VMO_RESIZABLE 选项搭配使用。

ZX_VM_FAULT_BEYOND_STREAM_SIZE

此 RFC 为 zx_vmar_map 引入了 ZX_VM_FAULT_BEYOND_STREAM_SIZE 选项。如果客户端提供此选项,则对 VMO 中包含数据流的最后一页以外的映射的内存访问将会出错。映射仍然可用于读取和写入数据流之外的内存,但只能读取和写入到下一个页面边界,因为内存映射存在于页面粒度。当具有此类映射的 VMO 调整大小时,Zircon 会移除数据流之外页面的页表条目,以便日后对这些页面的访问会产生故障。

ZX_VM_FAULT_BEYOND_STREAM_SIZE 选项对于实现与其他类 POSIX 操作系统(例如 mmapLinux 和 OpenBSD)。在这些操作系统中,超出最后一个页面且与内存映射文件内容重叠的内存访问会产生故障。

脏范围

未来的 zx_pager_query_dirty_ranges RFC 可能会添加一个选项,以支持查询 VMO 数据流中的脏范围,而不是当前默认的查询整个 VMO 中的脏范围。

ZX_PROP_VMO_CONTENT_SIZE

此 RFC 废弃了 ZX_PROP_VMO_CONTENT_SIZE。客户端应改用 zx_vmo_get_stream_sizezx_vmo_set_stream_size。在可预见的未来,Zircon 将保留对 ZX_PROP_VMO_CONTENT_SIZE 的旧版兼容性。不过,除了 ZX_RIGHT_GET_PROPERTYZX_RIGHT_SET_PROPERTY 之外,获取和设置 ZX_PROP_VMO_CONTENT_SIZE 还需要分别具有与 zx_vmo_get_stream_sizezx_vmo_set_stream_size 相同的权限(即 ZX_RIGHT_WRITE(用于设置 ZX_PROP_VMO_CONTENT_SIZE)。

实现

本部分介绍了每个 Zircon 系统调用的详细变更。

zx_pager_create_vmo

目前,VMO 的大小会初始化为向最近页面边界向上舍入的大小。VMO 的流大小会初始化为大小,而不会进行任何舍入。

如果客户端提供 ZX_VMO_UNBOUNDED 选项,此操作会创建一个大小初始化为可能的最大值的 VMO。

如果客户端同时提供 ZX_VMO_UNBOUNDEDZX_VMO_RESIZABLE 选项,此操作会返回 ZX_ERR_INVALID_ARGS

zx_pager_query_vmo_stats

无任何更改。不过,如果 VMO 包含数据流以外的修改,此操作可能会返回意料之外的结果。在未来的 RFC 中,我们预计会向 zx_pager_query_dirty_ranges 添加一个选项,以将查询限制为数据流。

zx_stream_create

如果 vmo 参数引用的是使用 zx_vmo_create_contiguousvmo_create_physical 创建的 VMO 对象,此操作会返回 ZX_ERR_WRONG_TYPE

zx_stream_writev

如果此操作尝试写入数据流末尾之外的位置,此操作将增加数据流的大小,这与 zx_vmo_set_stream_size 更改数据流大小的方式类似。如果无法增加数据流(例如,因为新的数据流大小会超过 VMO 的大小),则调整大小操作会失败。zx_stream_writev 的文档中明确定义了如何处理这种情况,但大致而言,如果 writev 操作能够将任何数据写入 VMO,则会成功执行部分写入,否则会传播错误。

这些语义与此操作的先前语义不同。在本 RFC 之前,如果所需的数据流大小超出了 VMO 的大小zx_stream_writev 会尝试更改 VMO 的大小。在本 RFC 发布后,VMO 的大小绝不会因流操作而更改。

zx_vmo_create

目前,VMO 的大小会初始化为向最近页面边界向上舍入的大小。VMO 的流大小会初始化为大小,而不会进行任何舍入。

如果客户端提供 ZX_VMO_UNBOUNDED 选项,此操作会创建一个大小初始化为可能的最大值的 VMO。

如果客户端同时提供 ZX_VMO_UNBOUNDEDZX_VMO_RESIZABLE 选项,此操作会返回 ZX_ERR_INVALID_ARGS

zx_vmo_create_child

对此操作所做的更改因模式而异:

  • ZX_VMO_CHILD_SNAPSHOT - 如果 size 不是页面大小的倍数,此操作会返回 ZX_ERR_INVALID_ARGS。以前,此操作支持非对齐大小值,但此行为可能很危险,因为子 VMO 实际上可以访问父 VMO 中的整数页数。

    子 VMO 的大小和数据流大小均初始化为 size 参数。子级的串流大小可以独立于父级更改。

  • ZX_VMO_CHILD_SNAPSHOT_AT_LEAST_ON_WRITE - 与 ZX_VMO_CHILD_SNAPSHOT 的行为类似。

  • ZX_VMO_CHILD_SLICE - 与 ZX_VMO_CHILD_SNAPSHOT 的行为类似。

  • ZX_VMO_CHILD_REFERENCE - 子项的数据流大小会初始化为父项的数据流大小

zx_vmo_create_contiguous

如果 size 不是页面大小的倍数,此操作会返回 ZX_ERR_INVALID_ARGS

VMO 的大小会初始化为 size

VMO 的流大小会初始化为零,无法修改。

vmo_create_physical

如果 size 不是页面大小的倍数,此操作会返回 ZX_ERR_INVALID_ARGS

VMO 的大小会初始化为 size

VMO 的流大小会初始化为零,无法修改。

zx_vmo_get_size

无任何更改。

zx_vmo_set_size

如果 size 参数不是页面大小的倍数,此操作会返回 ZX_ERR_INVALID_ARGS

将 VMO 的大小覆盖到 size 参数,并将此点之后的所有页面设为零(即舍弃)。如果数据流大小大于新 VMO 大小,则截断数据流大小以匹配新 VMO 大小。

此行为不同于此操作之前的行为,后者可用于增加 VMO 的内容大小。如需将串流大小增加到当前大小(如果是 VMO),请先使用 zx_vmo_set_size 增加 VMO 的大小,然后使用 zx_vmo_set_stream_size 增加串流大小。

zx_vmo_get_stream_size

返回 VMO 的数据流大小

zx_vmo_set_stream_size

如果 size 参数大于 VMO 的大小,此操作会返回 ZX_ERR_OUT_OF_RANGE

将 VMO 的流大小替换为 size 参数,并从 size 和上一个流大小中的较小者到 VMO 的末尾将值设为零。这与之前设置内容大小的行为不同,之前的行为是,只会从新大小到 VMO 的末尾进行清零。这意味着,当增加数据流大小时,新显示的范围会设为零。

此行为变更可确保新显示的内容始终为零,即使通过其他方式(例如直接内存映射)修改了超出流大小的 VMO 也是如此。

如果此操作缩减了数据流,则系统会取消映射不再与使用 ZX_VM_FAULT_BEYOND_STREAM_SIZE 选项映射的数据流重叠的任何页面。

无论是增大还是缩小,此操作都可能会导致写时复制和用户分页器支持的 VMO 提交与数据流重叠的最后一个页面,以便在该页面中存储零。

句柄必须具有 ZX_RIGHT_WRITE

zx_vmo_op_range

无任何更改。

zx_vmo_read

无任何更改。

此操作会与 VMO 的大小(而非数据流大小)进行交互,这意味着此操作可以读取数据流以外的数据。

zx_vmo_write

无任何更改。

此操作会与 VMO 的大小(而非数据流大小)进行交互,这意味着此操作可以写入数据流之外的数据。

zx_vmar_map

如果客户端提供 ZX_VM_FAULT_BEYOND_STREAM_SIZE 选项,则对 VMO 中包含数据流的最后一页以外的映射的内存访问将会出错。此选项需要 ZX_VM_ALLOW_FAULTS 选项。

性能

此 RFC 对性能应该没有影响。主要变更与 Zircon 报告 VMO 元数据的方式有关。我们选择了每项操作的语义,以避免实现过程中出现性能问题。

工效学设计

VMO 的概念模型很复杂,因为某些操作会将 VMO 视为内存页集合,而其他操作会与 VMO 中存储的数据流进行交互。面向页面的操作在页面级别运行,而面向流的操作在字节级别运行。之所以出现这种复杂性,是因为商用 CPU 中的虚拟内存硬件是围绕页面设计的,而客户端存储在内存中的数据是围绕字节设计的。

本 RFC 尝试通过更明确地区分基于页面的操作和基于流的操作来消除这些概念的歧义。例如,zx_vmo_set_size 是基于页面的操作,因此需要页面对齐的大小,而 zx_vmo_set_stream_size 是基于流的操作,因此支持字节对齐的大小。

有些情况比较细微,例如 zx_vmo_readzx_vmo_write。合理的客户端可能会假定这些操作会与数据流交互,因为它们会读取和写入数据。不过,我们选择让这些操作反映通过对映射内存执行 loadstore 操作可获得的语义,这意味着它们会与 VMO 的页面级大小进行交互。

这种情况存在过多约束条件,尤其是考虑到迭代包含大量现有软件的系统设计的路径依赖性。在设计约束条件下,我们已尽力找到最符合人体工学的解决方案。

向后兼容性

这项更改会带来一些向后兼容性风险。例如,zx_vmo_create_childzx_vmo_create_contiguouszx_vmo_create_physical 现在需要页面对齐的大小,并且设置 ZX_PROP_VMO_CONTENT_SIZE 需要额外的权限,这两项都会破坏一些现有代码。我们认为可以迁移该代码以遵守这些新限制,但如果我们无法执行这些迁移,则可能需要放宽这些限制。

此 RFC 中的其他更改不太可能导致向后兼容性问题。在大多数情况下,现有的语义会保留,只是用不同的术语进行说明。

安全注意事项

此 RFC 中的主要安全风险在于,除了 VMO 中的数据流之外,数据还可能存在于 VMO 中。许多开发者可能会对此行为感到意外,而意外总是会带来安全风险。不过,这些操作的典型用法会导致数据始终为零,而其他广泛使用的操作系统也存在此风险。

隐私注意事项

从理论上讲,如果 VMO 中除数据流之外存储的数据意外泄露,就可能会造成隐私问题。不过,在正常操作期间,超出 VMO 数据流的数据将为零。

测试

我们将添加适当的 Zircon 核心测试,以验证对系统调用语义的所有更改。

文档

随着这些更改的实现,系统会更新系统调用文档,以反映新的语义。

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

在项目实施过程中,我们尝试了各种替代方案,但都没有取得理想的效果。理想情况下,VMOS 应围绕字节级或页面级大小构建一致的心理模型。不过,字节粒度也有非常实用的用例,因为业界已将八位字节作为数据的大小粒度进行了标准化,并且在页面粒度处理内存时存在强大的硬件限制。因此,这两种极端情况都不是可行的设计方案。

在实践中,此 RFC 的主要替代方案是无需执行任何操作,并保留当前的双尺寸 VMO 模型。不过,我们认为此 RFC 中进行的细微更改是对当前模型的改进。我们曾考虑对模型进行更大的更改,但在每种情况下,我们都发现了一些用例或设计约束条件,这些约束条件促使我们将设计回归到这种更为温和的方法。

在先技术和参考文档

此领域存在大量的先前技术。主要相关的现有技术是文件在其他流行操作系统中与内存映射互动的方式。在我们研究的所有情况下,文件都是以字节为粒度,而内存映射则以页为粒度。我们特别研究了文件最后一页的内存映射(例如,超出文件内容末尾)的运作方式。ZX_VM_FAULT_BEYOND_STREAM_SIZE 标志旨在让我们重现我们在 Windows、macOS 和 Linux 中观察到的语义。不过,我们并未选择将这些语义作为默认值,因为 zx_vmar_map 默认不允许故障。我们预计,构建内存映射的大多数跨平台代码(例如mmap)将提供此标志,以使各操作系统的语义保持一致。