RFC-0209:内存优先级配置文件 | |
---|---|
状态 | 已接受 |
区域 |
|
说明 | 使用配置文件为 VMAR 标记优先级。 |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2022-08-16 |
审核日期(年-月-日) | 2023-02-15 |
摘要
使用配置文件对象指示内核允许将配置文件应用于 VMAR,以最大限度地减少可能增加内存访问延迟时间的页面故障和其他操作。
设计初衷
Zircon 是一个过度提交系统,采用不同的回收系统来尝试支持这些应用。这些回收方法包括驱逐、页表回收、零页扫描等,可能会与截止日期任务相冲突,因为它们可能会给内存访问增加不可接受的延迟时间。未来可能采用的回收方法(例如页面压缩)也会存在问题。
音频就是一个反复出现的例子,其中内核的任何回收操作(导致音频线程中出现后续页面故障)都可能会导致音频线程错过截止期限。
到目前为止,解决方案是在内核中使用特殊情况权宜解决方法,仅针对音频相关组件停用所有类型的回收。不过,这种方法非常脆弱,无法扩展到具有类似要求的其他用户。
此 RFC 旨在提供一个适用于音频的通用机制,以替代现有的临时权宜解决方法,以及任何其他组件。
利益相关方
教员:
jamesr@google.com
审核者:
eieio@google.com、rasheqbal@google.com、tomberga@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
,其中 high 会永久停用所有声明。我们会在未来支持更多级别,以便在尽可能缩短延迟时间和防止 OOM 之间进行权衡。
将配置文件应用于 VMAR 时,假设该配置文件具有有效的 memory_priority
,则该配置文件会立即应用于该 VMAR 及其所有子区域,并覆盖之前应用的所有配置文件。
内核必须遵循 ZX_PRIORITY_HIGH
设置,并且必须停用标记的 VMAR 中的任何动态回收。
ZX_PRIORITY_DEFAULT
设置没有特殊含义,也不必遵循。具体而言,为了便于实现,内核允许将 ZX_PRIORITY_HIGH
请求扩展到标记为 ZX_PRIORITY_DEFAULT
的更大范围。
虽然始终允许过度应用,但如果某个地址空间从具有一些 ZX_PRIORITY_HIGH
VMAR 变为没有任何 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 和内存用量)方面应该完全没有影响。
安全注意事项
配置文件的使用(以及设置内存优先级的能力)需要 root 作业句柄。因此,与拒绝服务攻击相关的任何安全注意事项都等同于 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 上使用。作业政策或类似机制可用于控制提示的操作,但也需要设计此类机制。