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_size
VDSO 调用和关联的 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
或将其替换为 zx_system_get_page_shift
。鉴于使用 shift 是一种微优化,因此只有在应用缓存了 vdsocall 的结果时,这种优化才可能有益。因此,用户将页面大小转换为偏移量并将其缓存,就等同于执行上述操作。因此,将这两个变体作为 vdsocall 提供没有实际益处。
在先技术和参考文档
Unix 派生版本通过 sysconf(_SC_PAGE_SIZE)
报告页面大小。
PAGE_SIZE
编译时常量在内核代码中作为常量提供,并且某些发行版会在 <sys/user.h>
中提供该常量,但它不是标准的也不是可移植的。
Windows 通过 GetSystemInfo()
系统调用报告页面大小。
MacOS 通过 sysctl()
调用或 vm_page_size
变量报告页面大小。