| 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 转换为文件描述符,而无需任何其他上下文信息,应保留该接口。但是,如果迁移客户端是可行的,则允许对公共接口进行小幅更改。
- 该设计应尽可能简单。虚拟内存管理器已经是系统中复杂性的重要来源。每当向系统的这一部分添加更多复杂性时,我们都需要谨慎。
本文档中的关键字“MUST”“MUST NOT”“REQUIRED”“SHALL”“SHALL NOT”“SHOULD” “SHOULD NOT”“RECOMMENDED”“MAY”和“OPTIONAL”需按照 在 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
的大小初始化为 size,并向上舍入到最接近的页面边界。VMO 的流大小初始化为 size,不进行任何舍入。
此 RFC 为 zx_vmo_create 和 zx_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 的其他操作系统(例如 Linux 和
OpenBSD)匹配的 mmap 语义非常有用。在这些操作系统中,对与内存映射文件的内容重叠的最后一页之外的内存访问会生成故障。
脏范围
未来的 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` 的文档精确定义了如何处理这种情况,但大致来说,如果操作能够将任何数据写入 VMO,则 `writev` 操作将成功进行部分写入,否则将传播错误。zx_stream_writevwritev
这些语义与此操作之前的语义不同。
在此 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 - 如果 size 不是页面大小的倍数, 此操作将返回 ZX_ERR_INVALID_ARGS。以前,此操作支持非对齐的大小值,但这种行为可能很危险,因为子 VMO 实际上可以访问父 VMO 中的整数个页面。
子 VMO 的大小和流大小都初始化为 size 参数。子 VMO 的流大小可以独立于父 VMO 的流大小进行更改。
ZX_VMO_CHILD_SNAPSHOT_AT_LEAST_ON_WRITE - 与 ZX_VMO_CHILD_SNAPSHOT类似的行为。
ZX_VMO_CHILD_SLICE - 与 ZX_VMO_CHILD_SNAPSHOT类似的行为。
ZX_VMO_CHILD_REFERENCE - 子 VMO 的 流大小初始化 为父 VMO 的 流大小。
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 将具有一致的心理模型,该模型围绕字节粒度或页面粒度大小构建。但是,字节粒度有很强的用例,因为行业已将八位字节标准化为数据的大小粒度,并且在以页面粒度处理内存方面存在很强的基于硬件的限制。因此,这两种极端情况都不是可行的设计点。
实际上,此 RFC 的主要替代方案是不采取任何措施,保留 VMO 的当前双大小模型。但是,我们认为此 RFC 中的小幅更改是对当前模型的改进。我们考虑对模型进行更大的更改,但在每种情况下,我们都发现了用例或设计限制,这些用例或设计限制将设计推回到了这种更小幅的方法。
在先技术和参考文档
此领域存在大量在先技术。主要相关的在先技术是文件在其他常用操作系统中与内存映射的交互方式。在我们研究的每种情况下,文件都具有字节粒度,而内存映射具有页面粒度。我们特别研究了文件最后一页的内存映射(例如,超出文件内容末尾的内存映射)的运行方式。ZX_VM_FAULT_BEYOND_STREAM_SIZE
标志旨在让我们复制在 Windows、macOS 和 Linux 中观察到的语义。但是,我们没有选择这些语义作为默认语义,因为
zx_vmar_map 默认不允许出现故障。我们预计大多数构建内存映射的跨平台代码(例如 mmap)都会提供此标志,以使语义在不同操作系统之间保持一致。