RFC-0238:VMO 大小

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

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

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

总结

以前,Zircon 将两种不同的大小与每个 VMO 相关联:VMO 的大小(按页面粒度)和 VMO 的内容大小(按字节粒度)。此 RFC 合理说明了在 Zircon 系统界面中处理这两种大小的合理理由。

设计初衷

Zircon 系统接口的 VMO 相关部分在对 VMO 大小内容大小的处理方面不一致。这种不一致性是这些接口演变的产物。在不同时间点,我们曾尝试以字节粒度、页面粒度以及在此 RFC 之前,同时以字节和页面粒度表示每个 VMO 的大小。这种不一致性导致难以改进 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 的大小。如果使用 ZX_VMO_RESIZABLE 选项创建 VMO,则客户端可以使用 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 的大小初始化为 size,向上取整到最接近的页面边界。VMO 的数据流大小初始化为没有四舍五入的“size”

此 RFC 为 zx_vmo_createzx_pager_create_vmo 引入了 ZX_VMO_UNBOUNDED 选项,用于创建大型 VMO。如果客户端提供此选项,则 VMO 的大小会初始化为可能的最大值,而不是根据 size 参数进行初始化。如果 VMO 的大小没有用处,例如,由分页器支持的文件系统使用的 VMO 以及支持 C 运行时堆的 VMO,ZX_VMO_UNBOUNDED 选项就会很有用。因此,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 的大小初始化为 size,向上取整到最近的页面边界。VMO 的数据流大小初始化为没有四舍五入的“size”

如果客户端提供 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 的大小初始化为 size,向上取整到最近的页面边界。VMO 的数据流大小初始化为没有四舍五入的“size”

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

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

zx_vmo_create_child

此操作特定于模式:

  • ZX_VMO_CHILD_SNAPSHOT - 如果“大小”不是页面大小的倍数,则此操作会返回 ZX_VMO_CHILD_SNAPSHOT之前,此操作支持非对齐大小值,但这种行为可能存在危险,因为子 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 参数,并将 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 核心测试,以验证对系统调用语义的所有更改。

文档

在实施新语义时,系统调用文档会进行更新。

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

在项目过程中,我们尝试了各种替代方案,但都没有效果。理想情况下,MMO 具有围绕字节粒度或页面粒度大小构建的一致的思维模型。不过,字节粒度也有很强的用例,因为行业已将 8 位字节作为数据的大小粒度进行标准化,并且在页面粒度处理内存方面存在严格的硬性限制。因此,这两种极端都不是可行的设计点。

在实践中,此 RFC 的主要替代方案是不执行任何操作,保留 VMO 的当前双大小模型。不过,我们认为此 RFC 中的细微更改是对当前模型的改进。我们考虑过对模型进行较大的更改,但在每种情况下,我们都发现了用例或设计限制,导致设计回退到这种更温和的方法。

早期技术和参考资料

这方面有大量的先验技术。主要相关的先验技术是文件与其他常用操作系统中的内存映射交互的方式。在我们研究的每种情况下,文件都具有字节粒度,而内存映射具有页面粒度。我们特别研究了文件最后一页的内存映射(例如,超出文件内容末尾)的内存映射是如何运作的。ZX_VM_FAULT_BEYOND_STREAM_SIZE 标志旨在让我们复制在 Windows、macOS 和 Linux 中观察到的语义。但是,我们并未选择这些语义作为默认语义,因为 zx_vmar_map 默认不允许故障。我们预计大多数用于构造内存映射的跨平台代码(例如,mmap)将提供此标志,以跨操作系统对齐语义。