| RFC-0209:内存优先级配置文件 | |
|---|---|
| 状态 | 已接受 |
| 区域 |
|
| 说明 | 使用配置文件标记具有优先级的 VMAR。 |
| 问题 | |
| Gerrit 更改 | |
| 作者 | |
| 审核人 | |
| 提交日期(年-月-日) | 2022-08-16 |
| 审核日期(年-月-日) | 2023-02-15 |
摘要
使用配置文件对象指示内核通过允许将配置文件应用于 VMAR 来最大限度地减少缺页中断和其他可能会增加内存访问延迟时间的操作。
设计初衷
Zircon 是一个过度提交系统,它采用不同的回收系统来尝试支持这些应用。这些回收方法包括逐出、页表回收、零页扫描等,可能会与截止时间任务相冲突,因为它们可能会给内存访问增加不可接受的延迟时间。未来的潜在回收方法(例如页面压缩)也会存在问题。
音频是一个反复出现的示例,其中内核的任何回收(导致音频线程中出现后续页面错误)都可能导致音频线程错过其截止时间。
到目前为止,解决方案一直是内核中的特殊情况解决方法,即仅针对音频相关组件停用所有类型的回收。不过,这种方法非常脆弱,无法扩展到具有类似要求的其他用户。
此 RFC 旨在提供一种通用机制,既适用于音频,以取代现有的临时解决方法,也适用于任何其他组件。
利益相关方
辅导员:
jamesr@google.com
审核者:
eieio@google.com、rashaeqbal@google.com、tombergan@google.com
已咨询:
andresoportus@google.com
共同化:
Zircon 团队和内核演进工作组 (KEWG) 已在文档中讨论了此问题和提案。
API 设计
为了向用户提供回收控制权,我们将通过以下两种方式扩展个人资料对象:
- 将为
zx_profile_create添加一个额外的标志ZX_PROFILE_INFO_FLAG_MEMORY_PRIORITY,以及一个memory_priority字段。 - 通过
zx_object_set_profile将配置文件应用于 VMAR 将是允许的。
最初,memory_priority 字段仅支持两个值:ZX_PRIORITY_DEFAULT 和 ZX_PRIORITY_HIGH,其中高值表示永久停用所有回收。预留空间以支持未来的其他级别,从而在最大限度减少延迟和防止 OOM 之间实现权衡。
当配置文件应用于 VMAR 时,假设该配置文件具有有效的 memory_priority,则该配置文件会立即应用于相应 VMAR 及其所有子区域,并覆盖之前应用的所有配置文件。
内核必须遵守 ZX_PRIORITY_HIGH 设置,并且必须在标记的 VMAR 中停用任何动态回收。
ZX_PRIORITY_DEFAULT 设置没有特殊含义,也不必遵守。特别是,为了便于实现,内核可以扩展 ZX_PRIORITY_HIGH 请求,使其覆盖标记为 ZX_PRIORITY_DEFAULT 的更广范围。
虽然始终允许过度应用,但如果地址空间从具有某些 ZX_PRIORITY_HIGH VMAR 变为没有,则内核应返回到与从未应用过任何配置相当的状态。
信息查询
zx_object_get_info 的 ZX_INFO_KMEM_STATS_EXTENDED 主题将扩展为报告一个额外的字段:
// The amount of memory in VMOs that would otherwise be tracked for
// reclamation, but has had reclamation disabled.
uint64_t vmo_reclaim_disabled_bytes;
组件使用情况
用户空间组件通常不直接使用 zx_profile_create 等,而是调用 ProfileProvider 服务。这里,ProfileProvider 的 SetProfileByRole 方法将放宽限制,以接受任意句柄,而不仅仅是线程。
内核设计
本部分介绍了如何更改内核中的对象,以适应配置文件中的信息。这样做的目的是确保系统中可能需要了解内存优先级才能做出决策的任何部分都能高效地访问该优先级。在可能的情况下,此设计倾向于在应用配置文件的时点执行工作,前提是与其它操作相比,配置文件应用将不频繁。
缩减为布尔值
回收涉及的内核对象包括:
VmAspace- 控制页面表映射和页面表回收通过此处。VmAddressRegion- 目前不参与回收,但所有页表映射的创建都通过此对象进行。VmObject- 任何逐出或未来的页面回收策略都通过VmObject进行。
除了 VmAddressRegion(配置文件应用到的对象)之外,每个对象都可以查询其任何子范围中存在的 VMAR 以及应用的内存优先级。
如果多个具有不同优先级的 VMAR 应用于 VMO 区域,则可以通过采用最高的应用优先级来解决此问题。
为了避免在所有 VMAR 中重复进行长时间运行的搜索,对象需要一种高效的方式来了解是否有任何内存优先级适用于它们。为简化跟踪,最初提出的实现方案会将任何优先级范围升级为完整对象。也就是说,如果某个配置引用了 VmObject 的任何部分,则整个对象都将被视为具有该配置。
这两种实现简化使得高效的内存配置文件查询只需通过对象链接传播布尔值的并集。
传播
此回收停用布尔值的传播基于边缘转换和计数。
当 VMAR 设置了个人资料时,需要考虑以下三种结果:
- 此 VMAR 的回收保持不变。
- 回收从停用到启用。
- 回收从启用状态过渡到停用状态。
在第一种情况下,不会发生直接传播,但仍必须遍历所有子区域并将配置文件应用于这些子区域。之所以需要进行这种无条件遍历,是因为子区域已应用需要覆盖的不同配置文件。
在任一转换中,两个潜在的参考对象(VmAspace 和 VmObject)都需要更新。
VmAspace 和 VmObject 对象将具有一个计数器,用于统计有多少引用它们的对象已停用回收功能,而不是使用单个布尔值。因此,确定是否为这些对象停用了回收功能,只需将该数量与零进行比较即可。
VmObject 可能有其他 VmObject 父级,需要将标志传播到这些父级。由于回收由计数器是否为零控制,因此当计数器从零过渡到非零或从非零过渡到零时,就会发生传播。
这种引用计数传播策略可确保尽可能高效地进行配置文件更改,与仅跟踪布尔值相比,几乎没有开销。
VmObject 转换
除了传播回收标志之外,VmObject 还需要执行操作,作为更新其网页的过渡的一部分。
如果停用回收,所有原本可回收的页面都会移至单独的页面队列。进入此队列既可以防止回收,又可以提供一种统计网页数量的方法。
同样,当启用回收时,需要将页面移回其默认队列,以便它们再次成为回收候选对象。当这些网页放回其默认队列时,其年龄被视为多少是实现细节。在没有硬件访问标志的平台上,无法获取任何年龄信息,因此必须虚构一个年龄。
实现
该实现将通过一系列步骤从内核实现到连续的 API 层。
内核实现
对内核对象的拟议设计更改可以完全实现,以添加对设置内存优先级的支持,而不会更改任何行为。这将通过多个 CL 完成,并使用内核中的单元测试进行测试。
内核 API 变更
ZX_INFO_KMEM_STATS_EXTENDED 查询需要相当特许的系统资源,并且只有少数用法(全部在树中)。因此,可以在单个 CL 中修改此查询,而无需进行多阶段结构演变。
更新了配置文件 API 和文档,并将配置文件系统调用与之前实现的内核支持相关联。zx_profile_create 系统调用及其关联的配置结构体 zx_profile_info_t 都是特权系统调用,因此同样可以在单个 CL 中进行修改。
ProfileProvider 更改
扩展 ProfileProvider 的实现,以支持在 .profiles 中指定内存优先级。
更改了 ProfileProvider FIDL API,使其可以接受任意句柄,而不仅仅是线程。由于这是对 API 的放宽,因此不会破坏任何向后兼容性。
媒体迁移
媒体相关组件的相关配置文件将更改为包含 ZX_PRIORITY_HIGH 的 memory_priority,然后任何媒体组件都将以与应用于其线程完全相同的方式将这些配置文件应用于其根 VMAR。
确认配置文件正常运行后,即可移除现有的硬编码内核变通方案。
性能
所提出的内核设计与内核中使用的临时解决方法非常相似,只是当前临时方法无法取消应用,并且对所有相关 VMAR 和 VMO 都是永久性的。因此,从这种方法切换到配置文件在功能和内核行为(包括 CPU 和内存使用情况)方面应该是完全的无操作。
安全注意事项
使用配置文件(以及设置内存优先级的能力)需要根作业句柄。因此,与拒绝服务攻击相关的任何安全注意事项都等同于 ProfileProvider 现有的拒绝服务可能性。
测试
大部分测试可以集中在相应内核和配置文件提供程序实现的单元测试中,同时进行一些集成测试来验证完整的使用路径。
文档
将更新有关配置文件对象、相关系统调用和 FIDL 协议的文档。
替代方案
VMAR 上的属性或等效项
可以直接在 VMAR 对象上设置属性(或等效属性)来指示其优先级,而不是使用配置文件对象。这样可以避免扩展调度程序对象,简化用户空间使用,并且不需要 ProfileProvider 的参与。
采用这种方法时,没有固有的方式来限制设置优先级的能力。虽然任何组件都可以分配任意内存并执行拒绝服务,但这不一定理想,并且禁用回收可能会很诱人,因为这是一种提升性能的方式,但可能会导致公地悲剧。
可以设计某种形式的自定义访问控制来解决此问题,但现在利用配置文件的优势已不复存在。
通过污点进行推理
用户空间直接标记的替代方案是假设任何截止期限线程都需要截止期限内存访问,并将它访问的任何内存标记为高优先级。这不需要对用户空间进行任何更改,但需要进行过度标记,即每个具有至少一个截止时间线程的地址空间的所有映射都已标记,或者映射/VMO 在被截止时间线程使用/触及后即被标记。
过度标记是不好的,因为并非所有截止时间线程都具有相同的内存延迟要求,并且并非所有地址空间都具有相同的内存延迟要求。延迟污染意味着无法预先缺页中断,因此在最坏的情况下,截止时间线程在缺页中断项时可能会始终错过一次截止时间。
采用延迟标记方案时,也不清楚如何取消标记商品。
将 memory_priority 应用于线程
无需将配置文件应用于 VMAR,而是可以在将配置文件应用于线程时解读 memory_priority 字段,并将优先级应用于其根 VMAR。
虽然这简化了 ProfileProvider 协议和系统调用接口,但会阻止用户和内核在未来提高效率。一个高效的组件可以组织起来,将对延迟敏感的关键数据放在地址空间的一个子区域中,将非关键数据放在另一个区域中,然后仅将配置文件应用于关键区域。这样一来,非关键数据仍可被视为可回收的数据,从而提高内存使用效率。
单个优先级字段
可以重复使用线程优先级的同一 priority 字段,而不是引入额外的 memory_priority 字段。这样一来,理论上可以节省内存,因为配置结构更简单,但如果需要不同的内存和调度器优先级,现在就需要创建不同的配置文件对象。
扩展 ALWAYS_NEED 提示
目前,有一个现有的 API 可用于通过在 VMO 或 VMAR 上使用 ALWAYS_NEED 和 DONT_NEED 提示来控制回收。目前,这些提示仅对由分页器支持的 VMO 有意义,但可以扩展为对匿名 VMO 有意义。
仅扩展语义以涵盖匿名 VMO 会留下一些空白:
ALWAYS_NEED无法保证不会发生回收,因为它只是一个提示。- 由于系统仍会跟踪页面年龄,因此页面可能仍需要访问错误。
- 不会停用页面表回收。
- 仅适用于现有映射。
这些限制都可以通过使用与主要提案中所述类似的内部实现来解决,但 API 本身存在两个根本性问题。
主要提案提供了一种清晰的方法来了解何时通过将配置文件应用于所有 VMAR(或仅应用于根 VMAR)重新启用了回收。使用提示时,无法移除 ALWAYS_NEED,因为 DONT_NEED 比撤消 ALWAYS_NEED 更强。
提示 API 之所以是提示而不是承诺,一个原因是它缺乏访问权限控制,可在任何 VMAR 或 VMO 上使用。作业政策或类似机制可用于控制提示的作用,但还需要设计此类机制。