| RFC-0016:启动时页面大小 | |
|---|---|
| 状态 | 已接受 |
| 区域 |
|
| 说明 | 将 PAGE_SIZE 常量替换为 vdsocall。 |
| Gerrit 更改 | |
| 作者 | |
| 审核人 | |
| 提交日期(年-月-日) | 2020-12-02 |
| 审核日期(年-月-日) | 2021-01-12 |
摘要
基于 Fuchsia 的系统应能够在需要时利用更大的内存页大小来获得最佳性能。为了实现这一点,页面大小需要是运行时常量,而不是编译时常量。此常量应在启动期间由内核以某种方式确定,然后通过 VDSO 提供给用户以供运行时查询。
设计初衷
为了实现最佳性能,系统应能够根据静态已知的信息或在启动时查询的信息来选择页面大小。此外,静态决策应可随着需求或要求的变化而变化,最好是以不破坏 ABI 的方式变化。
不同的内存页大小会带来不同的性能权衡。更大的内存页可以通过增加有效 TLB 覆盖率来减少 CPU 开销,并按比例提高以内存页粒度运行的任何算法或操作的性能,例如内存页分配、故障和扫描。大内存页虽然可以减少内存页表的内存利用率,但也会因过度分配而浪费内存,因此较小的内存页可以提供更优的内存用量。
这种性能与内存使用量之间的权衡取舍可能会因硬件和系统工作负载而异。在启动时告知用户页面大小,以便在内核编译时静态更改页面大小,或在内核启动时动态更改页面大小,而不会破坏与用户级组件的二进制兼容性。
设计
方法是向 VDSO 添加一个额外的常量,同时添加一个用于检索该常量的 VDSO 调用 (zx_system_get_page_size)。然后,可以将对现有编译时常量的所有使用情况迁移为使用 VDSO 调用,直到可以移除编译时常量为止。
还应针对每个平台声明最小和最大网页大小。这是为了让用户知道要链接到的最大页面大小,以便他们确保自己的组件是可移植的。
实现
实现过程分为三个阶段。虽然目前使用的是 C/C++ 名称,但需要在所有 Fuchsia 支持的语言中实现等效功能。
- 添加了
zx_system_get_page_sizeVDSO 调用和关联的 VDSO 常量,以及PAGE_MIN_SIZE和PAGE_MAX_SIZE定义。 - 迁移
PAGE_SIZE(或等效语言)的用法以使用 VDSO 调用 - 移除不再使用的
PAGE_SIZE(或等效语言)定义。
第一阶段和第三阶段非常简单,只需提交少量单个 CL。
迁移阶段应简单明了,但应尽可能多地完成按组件划分范围的 CL。
虽然这并非严格意义上属于此 RFC 的一部分,但要实际更改给定商品的分页大小,还需要执行以下操作:
- 对更大数据页的低级别内核实现支持。
- 用户组件(例如 BlobFS)需要进行修改才能支持非 4KiB 页面。
- 需要提高 ELF 部分的对齐度,以便页面不需要重叠的安全权限。
性能
虽然这会将编译时常量迁移到运行时查询,但预计不会产生任何可衡量的性能影响,因为页面大小计算不在任何热门路径上。不过,在迁移到 VDSO 调用时,应注意所有不在初始化或测试代码中的用法,并评估受影响组件的性能。
安全注意事项
无
隐私注意事项
无
测试
现有测试应足以发现迁移期间可能发生的任何愚蠢错误。迁移组件中的任何代码时,都应检查测试的代码覆盖率。
文档
需要记录 zx_system_get_page_size VDSO 调用。文档应说明
- 这是最小的内存页大小,也是所有分配的基本单位。
- vdsocall 永远不会失败。
- 保证页面大小为 2 的幂。
- 页面大小一经读取,即为常量,可由用户缓存。
有关 VMO 和其他内存相关系统调用和对象的现有文档已经抽象化,并且始终是指“系统页面大小”。
平台文档应记录最小和最大页面尺寸,并反映 PAGE_MIN_SIZE 和 PAGE_MAX_SIZE 常量。这些值是
- ARM aarch64:最小值为 4KiB,最大值为 64KiB。
- x86-64:最小 4KiB,最大 2MiB。
缺点、替代方案和未知因素
系统页面大小主要与用户正确执行 VMO 操作或与其他 Fuchsia 服务实现协议有关。因此,尚不清楚非 Fuchsia 原生代码何时需要了解或依赖于页面大小,但如果出现这种情况,可能需要修改源代码才能进行移植。
虽然从编译时常量执行迁移在概念上并不复杂,但会导致大量代码改动,并且在此过程中有充足的机会引入 bug。
不过,移除对编译时常量的引用并不意味着代码实际上能够容忍不同的内存页大小。算法很有可能已经内置了有关当前 4KiB 页面大小的假设,或者只是定义了自己的页面大小常量。如果编译时常量发生更改,也会出现这些问题,因此应将其视为无关的 bug。
主要替代方案是继续使用编译时常量,但要么针对给定产品修复该常量,要么针对给定产品修复该常量的某些组合。针对特定产品进行修复可能适用于一些受严格控制的产品,但不太适合需要长时间在不同硬件迭代版本之间保持二进制兼容性的长期运行产品。要求构建具有不同内存页大小的二进制文件的多个版本可提供所需的灵活性,但会大大增加开发者的时间和存储空间成本。一般来说,坚持使用编译时常量有很多缺点,唯一可感知到的优点是避免了一次性迁移。
与启动时常量不同,页面大小可能确实是可变的,并且可能会随时间变化,或者对于不同的组件而言有所不同。虽然这提供了极大的灵活性,但鉴于具有与页面大小相关联的语义的对象(例如 VMO)可以在组件之间任意共享,尝试使用不同的页面大小会给用户带来不合理的负担,因为用户既要查询页面大小,又要避免页面大小发生更改时出现竞态条件。如果某个子系统在某些时候需要使用不同于系统页面大小的页面大小,则应开发单独的机制来明确选择启用 VMO,或者以其他方式优化页面大小。
对于需要执行移位运算的应用来说,了解网页的大小(以位为单位)也很有用。为此,可以添加 zx_system_get_page_shift,也可以添加 zx_system_get_page_size。鉴于使用移位是一种微优化,因此只有在应用程序缓存了 vdsocall 的结果时,这种优化才可能带来好处。鉴于此,用户将页面大小转换为偏移量并缓存该偏移量,这两种方式是等效的。因此,以 vdsocall 形式同时提供这两种变体实际上没有任何好处。
在先技术和参考资料
通过 sysconf(_SC_PAGE_SIZE) 报告 Unix 衍生品的页面大小。
PAGE_SIZE 编译时常量作为内核代码中的常量提供,并且某些发行版将其作为 <sys/user.h> 的一部分提供,但它不是标准或可移植的。
通过 GetSystemInfo() 系统调用报告 Windows 页面大小。
macOS 通过 sysctl() 调用或 vm_page_size 变量报告页面大小。