RFC-0209:内存优先级配置文件

RFC-0209:内存优先级配置文件
状态已接受
区域
  • Kernel
说明

使用配置文件标记具有优先级的 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 设计

为了向用户提供回收控制,配置文件对象将通过以下两种方式进行扩展:

  1. 将为 zx_profile_create 添加一个额外的标志 ZX_PROFILE_INFO_FLAG_MEMORY_PRIORITY,以及一个 memory_priority 字段。
  2. 允许通过 zx_object_set_profile 将配置文件应用于 VMAR。

最初,memory_priority 字段仅支持两个值:ZX_PRIORITY_DEFAULTZX_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_infoZX_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 服务。在这里,ProfileProviderSetProfileByRole 方法将放宽为接受任意句柄,而不仅仅是线程。

内核设计

本部分介绍了如何更改内核中的对象以适应配置文件中的信息。目标是确保系统中任何可能需要知道内存优先级才能做出决策的部分都能高效访问该优先级。在可能的情况下,此设计倾向于在配置文件应用时执行工作,前提是配置文件应用与其他操作相比不频繁。

缩减为布尔值

回收中涉及的内核对象包括:

  • VmAspace - 控制页表映射,页表回收通过此处进行。
  • VmAddressRegion - 目前不参与回收,但所有页表映射的创建都通过此对象进行。
  • VmObject - 任何驱逐或未来的页面回收策略都通过 VmObject 进行。

除了配置文件应用到的对象 VmAddressRegion 之外,每个对象都可以查询其任何子范围,以了解该范围中存在的 VMAR 以及应用的内存优先级。

通过采用最高的应用优先级,可以解决将具有不同优先级的多个 VMAR 应用于 VMO 区域的问题。

为了避免在所有 VMAR 中重复执行长时间运行的搜索,对象需要一种高效的方式来了解是否有任何内存优先级适用于它们。为了便于跟踪,最初提出的实现会将任何优先级范围升级到完整对象。也就是说,如果配置文件引用了 VmObject 的任何部分,则整个对象将被视为具有该配置文件。

这两个实现简化使得高效的内存配置文件查询只需要通过对象链接传播布尔值的并集。

传播

此回收停用布尔值的传播基于边缘转换和计数。

当 VMAR 设置了配置文件时,需要考虑以下三种结果:

  1. 此 VMAR 的回收保持不变。
  2. 回收从停用转换为启用。
  3. 回收从启用转换为停用。

在第一种情况下,不会发生直接传播,但仍必须遍历所有子区域并将配置文件应用于它们。之所以需要进行这种无条件遍历,是因为子区域已应用了需要替换的不同配置文件。

在任何一种转换中,两个潜在的引用对象 VmAspaceVmObject 都需要更新。

VmAspaceVmObject 对象将具有引用它们的对象的计数器,而不是单个布尔值,这些对象已停用回收。因此,确定是否为这些对象停用回收操作只需将计数与零进行比较。

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_HIGHmemory_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_NEEDDONT_NEED 提示来控制回收。目前,这些提示仅对分页器支持的 VMO 有意义,但可以扩展为对匿名 VMO 有意义。

仅扩展语义以涵盖匿名 VMO 会留下一些空白:

  • ALWAYS_NEED 并不能保证不会回收,因为它只是一个提示。
  • 页面可能仍然需要访问错误,因为使用时长仍在跟踪。
  • 不会停用页表回收。
  • 只能应用于现有映射。

可以使用与主要提案中所述类似的内部实现来解决这些限制中的每一个,但 API 本身存在两个基本问题。

主要提案提供了一种明确的方法来了解何时通过将配置文件应用于所有 VMAR(或仅根 VMAR)重新启用了回收。使用提示时,无法移除 ALWAYS_NEED,因为 DONT_NEED 比撤消 ALWAYS_NEED 更强大。

提示 API 成为提示而不是承诺的一个原因是,它缺乏访问权限控制,可以在任何 VMAR 或 VMO 上使用。作业政策或类似政策可用于控制提示的作用,但还需要设计此类机制。