RFC-0016:启动页面大小

RFC-0016:启动时间页面大小
状态已接受
领域
  • 内核
说明

将 PAGE_SIZE 常量替换为 vdsocall。

Gerrit 更改
  • 457076
作者
审核人
提交日期(年-月-日)2020-12-02
审核日期(年-月-日)2021-01-12

总结

如果需要获得最佳性能,基于 Fuchsia 的系统应能够利用较大的页面大小。为了实现这一可行的页面大小,页面大小必须是运行时间而不是编译时常量。此常量应由内核在启动期间以某种方式确定,然后通过 VDSO 为用户提供运行时查询。

设计初衷

为了实现最佳性能,系统应能够根据静态已知的信息或启动时查询的信息选择页面大小。此外,静态决策应随着需求或要求的变化而更改,最好以不破坏 ABI 的方式进行更改。

不同的页面大小对性能的影响也不同。较大的页面可以增加有效 TLB 覆盖率,并按比例提高以页面精细度运行的任何算法或操作(例如页面分配、故障和扫描)的性能,从而减少 CPU 开销。如果页面较大可以降低页表的内存利用率,则它们也会因发生过度分配而浪费内存,因此较小的页面可以提供更理想的内存用量。

这种性能与内存用量的权衡可能因硬件和系统工作负载而异。通过在启动时通知用户页面大小,可以在内核编译时静态更改页面大小,或在内核启动时动态更改页面大小,而不会破坏与用户级组件的二进制文件兼容性。

设计

一种方法是向 VDSO 添加一个额外的常量,同时通过 VDSO 调用 (zx_system_get_page_size) 来检索该常量。然后,只要使用现有编译时常量,便可以改用 VDSO 调用,直到可以移除编译时常量为止。

还应为每个平台声明页面大小的下限和上限。这是为了让用户知道要链接的最大页面大小,从而确保其组件的可移植性。

实现

整个实现过程分为三个阶段。尽管使用的是 C/C++ 名称,但需要在 Fuchsia 支持的所有语言之间使用等效名称。

  1. 添加 zx_system_get_page_size VDSO 调用和关联的 VDSO 常量以及 PAGE_MIN_SIZEPAGE_MAX_SIZE 定义。
  2. 迁移使用 PAGE_SIZE(或等效语言)以使用 VDSO 调用
  3. 一旦不使用,请移除 PAGE_SIZE(或等效语言)定义。

第一和第三阶段无关紧要,它们是较小的单个 CL。

迁移阶段应该比较简单,但应尽可能多地按组件划分 CL。

虽然并非严格意义上属于此 RFC,但要让指定产品实际更改页面大小,还需要执行以下操作:

  1. 对较大页面的低级别内核实现支持。
  2. BlobFS 等用户组件需要经过修改才能支持非 4KiB 页面。
  3. 需要增加 ELF 部分的对齐度,以便页面不需要重叠的安全权限。

性能

虽然这会将编译时间常量迁移到运行时查询,但它不会对性能产生任何可衡量的影响,因为已知页面大小计算不会出现在任何热路径上。不过,在执行向 VDSO 调用的迁移时,应记录任何不在初始化或测试代码中的使用情况,并评估受影响组件的性能。

安全注意事项

隐私注意事项

测试

现有的测试应该足以捕获在迁移过程中可能发生的任何愚蠢的错误。迁移组件中的任何代码时,应检查测试的代码覆盖率。

文档

zx_system_get_page_size VDSO 通话需要记录。文档中应指出

  • 这是所有分配中的最小页面大小和基本单位。
  • vdsocall 永远不会失败。
  • 页面大小保证为 2 的幂。
  • 页面大小在读取后是一个常量,可由用户缓存。

另外,关于 VMO 以及其他与内存相关的系统调用和对象的现有文档已经很抽象,并且始终指“系统页面大小”。

平台文档应记录最小和最大页面大小,并反映 PAGE_MIN_SIZEPAGE_MAX_SIZE 常量。这些值是

  • ARM aarch64:最小 4KiB,最大 64KiB。
  • x86-64:最小 4KiB,最大 2MiB。

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

系统页面大小在很大程度上与用户正确执行 VMO 操作或实现与其他 Fuchsia 服务的协议相关。因此,非紫红色原生代码何时需要知道或依赖于页面大小,这一点并不明确,但如果发生这种情况,可能需要修改源代码才能进行移植。

从编译时常量执行迁移虽然在概念上并不复杂,但会导致重要的代码流失,并且有充足的机会在过程中引入 bug。

不过,移除对编译时常量的引用并不意味着代码实际上能够容忍不同的页面大小。算法让算法能够对当前的 4KiB 页面大小进行假设,或者直接定义自己的页面大小常量。如果编译时常量发生更改,这些问题也会产生问题,因此应被视为不相关的 bug。

主要替代方案是继续使用编译时常量,但针对给定产品修复该常量,或针对给定产品修复该常量的某些组合。针对给定产品的修复可能适用于一些受严格控制的产品,但不适用于长期运行的产品,这些产品希望在较长的时间范围内跨不同的硬件迭代实现二进制文件兼容性。要求使用不同的页面大小构建多个版本的二进制文件可以实现所需的灵活性,但会占用大量开发者的时间和存储空间。一般来说,使用编译时常量有许多缺点,唯一可以察觉的优点是可以避免一次迁移。

页面大小可能确实可变,并且可能会随时间而变化,或者因组件而异,而不是启动时间常量。虽然这提供了极大的灵活性,但考虑到可以在组件之间任意共享具有与页面大小相关联的语义的对象(例如 VMO),但试图使用不同的页面大小会给用户带来不合理的负担,不仅要查询页面大小,还要避免页面大小发生变化的竞态条件。当页面大小与系统页面大小不同时,对特定子系统有益,应开发一些单独的机制来明确选择启用 VMO,或以其他方式优化页面大小。

对于应用来说,知道页面大小(以位为单位)对于执行移位算术也很有用。为此,可以添加 zx_system_get_page_shift 以及 zx_system_get_page_size,或同时添加它们来代替 zx_system_get_page_size。鉴于使用偏移是一种微优化,因此可能只有在应用缓存 vdsocall 的结果时才有益。有鉴于此,用户就相当于将页面大小转换为偏移并缓存。因此,作为 vdsocall 提供两个变体并没有任何实际好处。

早期技术和参考资料

Unix 衍生体通过 sysconf(_SC_PAGE_SIZE) 报告页面大小。

PAGE_SIZE 编译时常量以常量的形式在内核代码中提供,并由某些发行版作为 <sys/user.h> 的一部分提供,但它并非标准常量,也不可移植。

Windows 通过 GetSystemInfo() 系统调用报告页面大小。

MacOS 通过 sysctl() 调用或 vm_page_size 变量报告页面大小。