RFC-0238:VMO 大小 | |
---|---|
状态 | 已接受 |
领域 |
|
说明 | 定义 VMO 的大小和数据流大小。 |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 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_create
和 zx_pager_create_vmo
引入了 ZX_VMO_UNBOUNDED 选项,用于创建大型 VMO。如果客户端提供此选项,则 VMO 的大小将初始化为可能的最大值,而不是根据 size 参数进行初始化。ZX_VMO_UNBOUNDED 选项对于 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 选项可用于实现mmap
与其他类似 POSIX 的操作系统(例如Linux 和 OpenBSD)。在这些操作系统中,如果访问最后一页之外的内存访问与内存映射文件的内容重叠,则会产生故障。
脏范围
未来针对 zx_pager_query_dirty_ranges
的 RFC 可能会添加一个选项来支持查询 VMO 的数据流中的脏范围,而不是当前默认查询整个 VMO 中的脏范围。
ZX_PROP_VMO_CONTENT_SIZE
此 RFC 废弃了 ZX_PROP_VMO_CONTENT_SIZE。客户端应改用 zx_vmo_get_stream_size
和 zx_vmo_set_stream_size
。在可预见的未来,Zircon 将保留对 ZX_PROP_VMO_CONTENT_SIZE 的旧版兼容性。不过,除了 ZX_RIGHT_GET_PROPERTY 和 ZX_RIGHT_SET_PROPERTY 之外,获取和设置 ZX_PROP_VMO_CONTENT_SIZE 将需要分别与 zx_vmo_get_stream_size
和 zx_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_UNBOUNDED 和 ZX_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_contiguous
或 vmo_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_UNBOUNDED 和 ZX_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 参数,并将 size 中的较小者和之前的流大小设为零到 VMO 末尾。这与之前设置内容大小的行为不同,在该行为中,只有在新的 size 到 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_read
和 zx_vmo_write
。作为合理的客户端,可能会假设这些操作会与数据流交互,因为它们会读取和写入数据。不过,我们已选择让这些操作镜像通过映射内存上的 load 和 store 操作可用的语义,这意味着它们会与 VMO 的面向页面的大小进行交互。
这种情况受到了过度限制,特别是考虑到使用大量现有软件的系统设计迭代时,这种特性会依赖于路径。鉴于设计限制,我们已尽最大努力寻找最符合工效学的解决方案。
向后兼容性
此变更会带来一些向后兼容性风险。例如,zx_vmo_create_child
、zx_vmo_create_contiguous
和 zx_vmo_create_physical
现在需要页面对齐的尺寸,设置 ZX_PROP_VMO_CONTENT_SIZE 需要额外的右侧,这两项操作都会破坏一些现有代码。我们相信我们可以迁移该代码以遵循这些新的约束条件,但如果我们未能执行这些迁移,我们可能需要放宽这些约束条件。
此 RFC 中的其他更改不太可能导致向后兼容性问题。在大多数情况下,现有的语义会保留,只是以不同的术语进行说明。
安全注意事项
此 RFC 中的主要安全风险是数据可能存在于 VMO 内的数据流之外的 VMO 中。这种行为可能会让许多开发者感到意外,而意外情况一直是安全风险。但是,以典型方式使用这些操作会导致数据始终为零,并且这种风险存在于其他广泛使用的操作系统中。
隐私注意事项
从理论上讲,如果数据意外泄露到 VMO 内的数据流之外,则数据可能会带来隐私问题。但在正常操作中,VMO 数据流以外的数据将为零。
测试
我们将添加适当的 Zircon 核心测试,以验证对系统调用语义的所有更改。
文档
在实现新语义时,系统调用文档也会随之更新。
缺点、替代方案和问题
在整个项目过程中,我们尝试了各种替代方案,但都没有效果良好。理想情况下,VMO 具有围绕字节粒度或页面粒度大小构建的一致的思维模型。不过,字节粒度还存在很强的用例,因为业界已将 8 位字节作为数据的大小粒度进行标准化,并且在页面粒度处理内存方面也存在基于硬性强的严格限制。因此,这两种极端都不是可行的设计点。
在实践中,此 RFC 的主要替代方案是不执行任何操作,保留 VMO 的当前双大小模型。但是,我们认为此 RFC 中的小幅更改是对当前模型的改进。我们考虑过对模型进行较大的更改,但在每个情况下,我们都发现了导致设计推回这种更为适中的方法的用例或设计限制。
现有艺术和参考资料
这一领域拥有大量的既往技术。主要相关的先验技术是文件与其他常用操作系统中的内存映射交互的方式。在我们研究的每种情况下,文件都具有字节粒度,而内存映射具有页面粒度。我们特别研究了文件最后一页的内存映射(例如,延伸到文件内容末尾以外)的内存映射是如何运作的。ZX_VM_FAULT_BEYOND_STREAM_SIZE 标志旨在让我们复制在 Windows、macOS 和 Linux 中观察到的语义。但是,我们未选择这些语义作为默认语义,因为 zx_vmar_map
默认不允许故障。我们希望大多数构建内存映射的跨平台代码(例如mmap
)将提供此标志,以跨操作系统对齐语义。