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
,其作用为永久停用所有回收。Room 未来会支持更多级别,以便在最大限度地缩短延迟时间和防止 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
父级。由于回收由计数器控制,因此当计数器转换为 0 或从 0 转换为 0 时,计数器正在或不为零,因此会发生传播。
这种引用计数传播策略可确保配置文件更改尽可能高效,并且与仅跟踪布尔值相比,几乎没有开销。
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
在将配置文件应用于线程时,系统可以解释 memory_priority
字段,并将优先级应用于其根 VMAR,而无需将配置文件应用于 VMAR。
虽然这会简化 ProfileProvider
协议以及系统调用接口,但会导致用户和内核的选项在未来无法提高效率。一个高效的组件可以组织将关键延迟敏感数据置于其地址空间的一个子区域,将非关键数据置于另一个区域,然后只需将一个配置文件应用于关键区域即可。如此一来,非关键数据仍然需要回收,以便提升内存用量。
单优先级字段
可以重复使用相同的线程优先级 priority
字段,而不是引入额外的 memory_priority
字段。从理论上说,这样可以使配置结构更简单,但现在,如果需要不同的内存和调度器优先级,则需要创建不同的配置文件对象。
扩展 ALWAYS_NEED 提示
有一个现有 API 可通过对 VMO 或 VMAR 使用 ALWAYS_NEED
和 DONT_NEED
提示来控制回收。目前,这些 API 仅对由分页器支持的 VMO 赋予意义,但可以扩展为对匿名 VMO 有意义。
仅仅扩展语义以涵盖匿名 VMO 会产生一些问题:
ALWAYS_NEED
并不保证不会回收,因为它只是一个提示。- 由于系统仍会跟踪年龄,因此网页可能仍需要访问故障。
- 不会停用页面表回收。
- 仅应用于现有映射。
上述每一项限制都可以通过使用类似于主方案中所述的内部实现来解决,但 API 本身有两个基本问题。
通过将配置文件应用于所有 VMAR(或仅应用于根 VMAR),主方案可以清楚地知道何时重新启用了回收。使用提示无法移除 ALWAYS_NEED
,因为 DONT_NEED
比撤消 ALWAYS_NEED
更强大。
将提示 API 用作提示(而不是 promise)的一个动机是,由于缺少访问权限控制,它可用于任何 VMAR 或 VMO。作业政策或类似机制可用于控制提示的作用,但也需要设计这种机制。