RFC-0084:向 zx_info_task_runtime_t 添加更多指标

RFC-0084:向 zx_info_task_runtime_t 添加更多指标
状态已接受
领域
  • 内核
说明

向 zx_info_task_runtime_t 添加了更多指标,用于调试媒体性能问题。

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2021-03-09
审核日期(年-月-日)2021-04-06

摘要

ZX_INFO_TASK_RUNTIME 主题提供了一种方法来检索 任务在 CPU 上运行或排队等待运行的时间。为了诊断 我们提议增加额外的精细运行时 特别是等待页面错误发生所花费的时间 等待内核互斥所用的时间。

设计初衷

实时任务有截止日期。有时,由于要完成的任务而错过截止日期 在内核中停留的时间超出预期。要调试这些情况, 有助于了解任务被阻止的原因。例如,如果某个任务 而等待页面错误需要 11 毫秒,我们可以得出结论, 由于页面错误太慢,因此错过了截止日期。

具体而言,我们希望改进由以下媒体子系统产生的诊断结果, audio_core。在 audio_core 中,每个 Mixer 任务都必须在 10 毫秒内完成。如果 任务用时超过 10 毫秒,用户会听到杂音。最近,我们 因页面错误缓慢而错过截止日期(其中 audio_core 可执行文件 需要重新分页)和内核堆互斥量上的争用。 这些问题无法通过快照进行诊断,而是必须 跟踪是一个单调乏味的过程,有时会因 在本地重现问题(因为 bug 可能只有在特定情形下才会触发) 应用)。这些是高优先级问题, 对媒体效果造成严重负面影响。

我们的目标是从内核中导出足够的诊断信息, 可让您通过快照诊断这些问题,而无需深入了解 执行跟踪记录本文档建议将新统计信息添加到 zx_info_task_runtime_t,以便更完整地回答以下问题:“为什么 “任务无法运行?”。

Zircon 线程截止时间的背景

Zircon 截止时间配置文件包含三个组成部分:期限、容量和截止时间。 为线程分配截止期限配置文件后,Zircon 可保证 period 时,线程会被分配至最高 capacity CPU 在 deadline 内 每个时间段的开始时间。举例说明:

  • 周期 = 10 毫秒
  • 容量 = 2 毫秒
  • 时限 = 5 毫秒

在接下来的 5 毫秒内,为线程分配 2 毫秒的 CPU。还有 有以下两种可能错过截止日期:

  1. 任务安排得很晚。例如,如果下一时间段的开始时间是 T,但任务在 T+4 毫秒之后才被调度, 任务的截止时间(该任务不得晚于 T+3 毫秒)。内核可以检测到这种错过的截止时间,但实际上 除非调度程序有错误或订阅过多,否则绝不会发生这种情况。

  2. 完成任务耗时超过 2 毫秒。内核无法知道 是因为它不了解任务边界。例如,如果 任务安排在 T+1 毫秒,总运行 1 毫秒,然后阻塞 9 毫秒, 内核无法得知任务是否错过了截止时间(因为 或者任务要求 2 毫秒,但只需要 1 毫秒即可 完成(然后睡眠 9 毫秒以等待下一个周期)。

我们的目标是帮助诊断第二种错过截止日期。目前,如果 任务运行时间过长,可以查询 ZX_INFO_TASK_RUNTIME 以了解 在用户空间中的 CPU 上运行所花费的时间。如果该 cpu_time 大于 预期运行时,任务就会知道它的运行时间过长。不过, 如果 cpu_time 只占总任务时间的一小部分,则该任务 它的大部分时间都花在内核中,很可能处于阻塞状态,无法运行。目标 是为了帮助您了解这些时间花在哪些方面。

设计空间

我们的目标是回答“为什么此任务无法运行?”这一问题。我们必须 做一些决定:

  1. 我们的回答应有多完整?具体而言,我们是否应 任务被阻止的原因,还是仅仅是几个看上去很重要的原因?

  2. 我们应该以怎样的详细程度回答这个问题?例如,我们应该 仅报告用户一级易于理解的简单事件,例如 还是应该添加级别较低的事件 是否特定于 Zircon 的当前实现?

  3. 如果我们报告 N 条统计信息,是否应要求这 N 条统计信息 还是应该允许它们以任意方式重叠?

最简单、最直接的方法是,枚举我们关心的一些事件 并生成这些事件的统计信息鉴于媒体子系统 页面故障和内核锁争用方面的问题, “页面错误所花时间”的统计信息以及“在内核上阻塞花费的时间” 。

可能还存在这两个无法捕获到的其他问题 统计信息。因此,完整的解决方案具有吸引力。一种想法是 线程进入内核的所有方式这包括 N 硬件 中断(计时器和设备中断)和 K 软件中断(系统中断) 调用和故障)。然后,我们会生成 N+K 的统计信息, 中断。计时器在线程进入内核时启动,在线程进入内核时停止。 将控制权返还给用户空间线程。不过,用户级别 计算“在系统调用 X 中花费的时间”,让内核计算该信息 是多余的

另一种方法是生成统计信息, 内核模式”以及“在 X 上阻塞的时间”,其中 X 是 基元,如“内核锁定”或“channel”[频道]。这种做法存在导致膨胀的风险 随着内核基元集随时间变化而发生变化。

回过头来,我们真正想要的是从实际使用的设备获取跟踪记录。 理想情况下,我们可以连续将轨迹记录到环形缓冲区中,并上传该轨迹文件 缓冲区 TRACE_ALERT

设计

虽然需要一个全面的解决方案,但可能需要很长时间 设计和构建。我们迫切需要诊断性能下降问题 大自然。因此,我建议我们添加两个有针对性的指标, 眼前的问题:等待页面错误所花费的时间, 等待内核锁定所用的时间

对于上述设计空间问题:

  1. 我们不会力求完整性。

  2. 粒度是任意的(我们会记录我们认为所需的任何值)

  3. 统计信息可能会以任意方式重叠

内核变更

// This struct contains a partial breakdown of time spent by this task since
// creation. The breakdown is not complete and individual fields may overlap:
// there is no expectation that these fields should sum to an equivalent
// "wall time".
typedef struct zx_info_task_runtime {
  // Existing fields
  zx_duration_t cpu_time;
  zx_duration_t queue_time;

  // New fields below here

  // The total amount of time this task and its children spent handling page faults.
  zx_duration_t page_fault_time;

  // The total amount of time this task and its children spent waiting on contended
  // kernel locks.
  zx_duration_t lock_contention_time;

} zx_info_task_runtime_t;

这两个字段都将按线程计算,然后跨进程求和 和作业。cpu_timequeue_time请注意, 媒体子系统不需要按进程和作业汇总, 为了与 zx_info_task_runtime_t

存在多种类型的页面错误和多种内核锁定。 page_fault_time 表示处理各种网页所花费的总时间 错误。通过涵盖所有页面错误,我们无需说明 页面故障,这可能很难,因为内核可能会添加或 随着实现随时间的推移而发生变化,并消除某些类型的页面错误 因为其支持新架构

lock_contention_time 涵盖所有争用锁。不过,“竞争”一词 故意未指定规格,因此内核可以改进其实现 以平衡衡量争用的成本和 报告竞争时间。有关其他讨论,请参阅“实现”(见下文)。

用户空间如何诊断错过截止日期

鉴于这些新字段,用户空间可以使用如下代码来诊断 错过的截止日期:

for (;;) {
  zx_object_get_info(current_thread, ZX_TASK_RUNTIME_INFO, &start_info, ...)
  deadline_task()
  if (current_time() > deadline) {
    zx_object_get_info(current_thread, ZX_TASK_RUNTIME_INFO, &end_info, ...)
    // ...
    // report stats from (end_info - start_info)
    // ...
  }
}

实现

page_fault_time 将计算所有页面错误处理程序占用的总时间。 在当前的实现中,这包括 vmm_page_fault_handlervmm_accessed_fault_handler

lock_contention_time会计算 Mutex::AcquireContendedMutexBrwLock::Block。此方法已有 访问当前的 Threadcurrent_ticks()。实现 不会涵盖自旋锁。虽然自旋锁可能会争用,但我们会忽略自旋锁 因为测量自旋锁的争用可能让人望而却步, 价格高昂。

为了尽可能减少开销,我们会将这些时长记录为 tick 计数, 在 zx_object_get_info 系统调用期间转换为 zx_duration_t。 该实现的其他详情将遵循 cpu_timequeue_time。原型实现可在以下位置找到: fxrev.dev/469818。

性能

我们将运行 Zircon 互斥基准,以验证没有回归问题。我们将 在原始硬件(x86 和 ARM)上运行这些基准测试。此外,要验证 在虚拟化环境中不会出现回归问题, 在 QEMU(x86 和 ARM)上进行基准测试。

向后兼容性

zx_info_task_runtime_t 结构体将进行版本控制,这与 已针对其他 zx_info_* 结构体完成(有关示例,请参阅 fxrev.dev/406754)。

安全注意事项

ZX_INFO_TASK_RUNTIME 是一种边信道,可能会泄露 检查任务。例如,page_fault_time 可用于衡量任务的 内存访问模式作为此类泄露的缓解措施, ZX_INFO_TASK_RUNTIME主题已要求使用ZX_RIGHT_INSPECT。拥有 则可以假定有权访问任务的私有数据。

ZX_INFO_TASK_RUNTIME 还可能会泄露有关其他任务的间接信息。对于 例如,如果任务知道自己的 page_fault_time,或许能够推断出 其他任务的内存访问模式。同样,如果任务知道 就能够推断出 其他任务使用共享内核资源。将来,我们可能会构建 zx_info_task_runtime_t正在使用低分辨率计时器。这并不一定 可以防止定时攻击,但可能会限制其有效性。

另一项防御措施是限制特殊开发者访问 zx_info_task_runtime_t build。但是,这会极大地限制此功能的实用性: 在开发环境中,我们往往难以重现性能错误。 我们需要一个可在正式版 build 中启用的解决方案。

为了彻底避免出现这种边信道,我们需要使用单独的报告功能 和指标检查等功能。例如,如果我们 连续将轨迹记录到环形缓冲区中,并将该缓冲区上传到 特定通道或端口 TRACE_ALERT、 则不需要提供触发 TRACE_ALERT 的任务 访问跟踪记录,从而消除边信道。如前所述 这样的解决方案需要很长时间来设计和构建,而我们 需要立即解决。

隐私注意事项

无。

文档

需要更新 Zircon 系统调用文档,以纳入新的 zx_info_task_runtime_t 字段。

先验技术和参考资料

在 Linux 中,相关度最高的先前技术是 getrusage,它会报告用户 和系统 CPU 时间和页面故障数、I/O 操作数和上下文切换次数。 Windows 具有 GetThreadTimes,用于报告用户和系统 CPU 时间。五金件 性能计数器(如 x86 上的 RDPMC)可提供类似的信息和 拥有 类似的安全问题

测试

我们将通过 audio_core 记录新的运行时信息来手动测试 (我们已经完成了原型实现:请参阅 fxrev.dev/469819)。

我们将更新 cpu_timequeue_time 的现有测试,以测试旧测试 名为“zx_info_task_runtime_t”的版本 zx_info_task_runtime_v1_t。此外,Zircon 的 abi_type_validator.h 将进行更新,以验证新旧 ABI。这样可以确保 并保持向后兼容性

为此功能添加集成测试并不容易,因为 没有 API 可强制内核遇到锁争用或触发页面错误 (进程终止分段错误除外)。