RFC-0260:内核启动时间支持

RFC-0260:内核启动时间支持
状态已接受
区域
  • 内核
说明

枚举了支持启动时间轴所需的 Zircon API 更改。

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2024-06-14
审核日期(年-月-日)2024-09-25

问题陈述

单调时钟暂停和启动时间轴 RFC 指出,Fuchsia 必须提供一个启动时间轴,用于报告从启动到当前所经过的时间,包括在暂停到空闲状态所花费的任何时间。该 RFC 提到需要进行内核更改,但并未明确说明具体更改内容。

摘要

本文档介绍了在 Zircon 内核中支持启动时间轴所需的系统调用和 API 更改。

利益相关方

主持人:jamesr@google.com

Reviewers:

  • adamperry@google.com
  • andresoportus@google.com
  • cja@google.com
  • costan@google.com
  • fmil@google.com
  • gkalsi@google.com
  • harshanv@google.com
  • johngro@google.com
  • maniscalco@google.com
  • mcgrathr@google.com
  • miguelfrde@google.com
  • surajmalhotra@google.com
  • tgales@google.com
  • tmandry@google.com

社交

此 RFC 以一组文档的形式进行共享,每份文档都讨论了设计的不同部分。这些文档已发送给内核团队和组织中的其他人员,并由他们进行迭代。

要求

内核必须支持:

  1. 读取启动时间轴的当前值。
  2. 使用启动时间轴作为参考时间轴创建自定义时钟。
  3. 在启动时间轴上创建计时器,但可以唤醒系统的计时器会明确留待未来的 RFC。
  4. 在启动时间轴中报告日志消息和崩溃日志的时间戳。

请注意,我们不打算在启动时间轴上支持与时间相关的其他系统调用(例如对象或端口等待),因为我们尚未找到促使我们这样做的用例。如果出现用例,我们会在未来的 RFC 中重新考虑这一决定。

设计

在 Zircon API 中引入新的时区别名

Zircon 有几种表示今天时间的类型:

  1. zx_time_t:表示时间点(以纳秒为单位)。通常,此时间点是从单调时间轴中抽样的,但并不总是如此;例如,zx_clock_read 也会返回 zx_time_t,但它不在单调时间轴上。
  2. zx_duration_t:表示两个时间点之间的时长或距离。此量与时间轴没有具体关联,但始终以纳秒为单位。
  3. zx_ticks_t:表示平台专用计数器中的时间点或时长。

这些都是 int64_t 的别名,这意味着编译器不会对任何这些实体进行类型检查。它们的存在只是为了提高代码的可读性。如果将这些类型用于启动时间轴,则会大大降低与时间戳相关的代码的可读性,因为开发者将无法轻松区分不同时间轴上的时间戳。

因此,我们将引入采用以下结构的新类型别名:

zx_<kind>_<timeline>_[units_]t

其中:

  • kindinstantduration
  • timelinemonoboot
  • unitsticks 或省略,在这种情况下,单位应理解为纳秒。绝大多数代码使用纳秒时间,因此省略此部分可让常见用例更易于输入。

大多数现有的 zx_time_t 用法将迁移为使用 zx_instant_mono_t,大多数现有的 zx_duration_t 用法将迁移为使用 zx_duration_mono_t。我们会根据具体情况评估和迁移 zx_ticks_t 的用法。

现有别名不会被移除,并会在需要模糊类型时使用(例如,zx_clock_read 仍会返回 zx_time_t)。内核团队的目标是长期移除这些模糊类型,但实现方式的确切性质超出了本文档的范围,将留待未来的 RFC 中进行讨论。

时间的用户空间类型

虽然上一部分中介绍的时间的新类型别名会提高 Zircon API 中的代码清晰度和可读性,但它们不提供由编译器强制执行的类型安全性。这种类型安全将由 Rust 绑定到 ZirconC++ libzx 库中的用户空间系统调用封装容器库提供,这两个库都需要使用此 RFC 中引入和修改的系统调用进行扩展。用户空间代码应尽可能使用这些库。

适用于强类型的 API 需要针对每种语言进行精心设计,以便在实践中符合惯例且易于使用。我们不会急于完成此工作,并且在完成 C API 中上一部分详述的类型迁移之前,无需为每种语言完成此工作。

读取当前启动时间

我们将引入两个系统调用,以允许用户模式读取启动时间轴的当前值:

zx_instant_boot_t zx_clock_get_boot(void);
zx_instant_boot_ticks_t zx_ticks_get_boot(void);

第一个将以纳秒为单位报告时间,第二个将以平台滴答为单位报告时间。这会在单调时间轴上镜像相应的系统调用。

请注意,单调时间轴和启动时间轴的计数器与秒数的比率相同,这意味着 zx_ticks_per_second 的结果可用于将 zx_ticks_getzx_ticks_get_boot 的结果转换为秒。

创建自定义表盘

用户模式时钟是使用 zx_clock_create 系统调用创建的。syscall 已经接受 options 参数,这意味着我们无需更改函数签名。

而是会引入一个名为 ZX_CLOCK_OPT_BOOT 的新选项标志,用于指示新创建的时钟使用启动时间轴作为其参考时间轴。

时钟(单调和启动)的时间轴属性将是不可变的,这意味着时钟在创建后无法切换时间轴。

重命名 zx_clock_details 中的字段

zx_clock_details_v1_t 结构体包含两个字段,用于存储从单调时间轴到时钟的合成时间轴的转换:

typedef struct zx_clock_details_v1 {
  // other fields
  zx_clock_transformation_t ticks_to_synthetic;
  zx_clock_transformation_t mono_to_synthetic;
  // other fields
} zx_clock_details_v1_t;

这些字段将被重命名,以突出显示它们现在将存储从参考时间轴到时钟的合成时间轴的转换:

typedef struct zx_clock_details_v1 {
  // other fields
  zx_clock_transformation_t reference_ticks_to_synthetic;
  zx_clock_transformation_t reference_to_synthetic;
  // other fields
} zx_clock_details_v1_t;

快速扫描代码库后发现,使用这些字段的用户非常少,而且这些用户都位于 fuchsia.git 之外。因此,原地重命名应该相对简单。

创建启动计时器

用户模式计时器是使用 zx_timer_create 系统调用创建的。此系统调用非常方便地接受 clock_id 参数,该参数决定了计时器应在哪个时间轴上运行。

因此,我们将引入一个名为 ZX_CLOCK_BOOT 的新 clock_id 值,用于指示新创建的计时器在启动时间轴上运行。

与时钟类似,计时器的时间轴属性将是不可变的,这意味着计时器一经创建便无法切换时间轴。

请注意,zx_timer_set 将被重复使用来设置启动计时器。由于此系统调用接受单调期限和启动期限作为输入,因此其参数类型将保持为 zx_time_t

clock_id 添加到 zx_info_timer_t

用户可以使用 ZX_INFO_TIMER 主题调用 zx_object_get_info,以获取有关计时器句柄的信息。生成的 zx_info_timer_t 结构体将被修改为包含用于创建计时器的 clock_id

通常,这需要对结构体进行一些演变,但在本例中,结构体已经有 4 字节的填充,这足以容纳存储为 4 字节 zx_clock_tclock_id

更新 Zircon 结构中的时间戳

Zircon 结构中有一些时间戳会报告今天的单调时间,但应在启动时间可用时改用启动时间。

请注意,继续报告单调时间的时间戳在功能上不会发生变化,但其类型将更新为 zx_instant_mono_t,如上文中的类型别名部分所述。

zx_log_record_t 时间戳

zx_log_record_t 是 Zircon 公开的结构体,用于描述调试日志中消息的结构。此结构体包含一个 timestamp 字段,该字段是 zx_time_t,也是 ZX_CLOCK_MONOTONIC 上的一点。此值将更新为 zx_instant_boot_t,因此将是 ZX_CLOCK_BOOT 上的一点。

崩溃日志运行时间戳

crashlog 目前会报告使用单调时间的 uptime 字段。由于单调时间不包括暂停时间,因此应将其修改为使用启动时间,并将其类型更新为 zx_instant_boot_t

中断 API 变更

中断系统调用

Zircon 中断 API 包含两个使用时间戳的系统调用:zx_interrupt_waitzx_interrupt_trigger。目前,这些系统调用使用单调时间轴。不过,由于中断可以在挂起到空闲期间触发,因此默认时间戳行为将更改为使用启动时间轴。这有助于确保中断时间戳是唯一的,即使在挂起-恢复周期中也是如此,但唯一性仍然受时钟分辨率的限制。使用单调时钟会导致在挂起到空闲期间触发的所有中断具有相同的时间戳,即系统挂起的时间。

为了与可能依赖于单调时间戳的现有驱动程序(例如显示屏驱动程序)保持兼容性,系统会向 zx_interrupt_create 系统调用添加一个名为 ZX_INTERRUPT_TIMESTAMP_MONO 的新标志。设置此标志后,关联的中断对象将继续使用 zx_interrupt_waitzx_interrupt_trigger 和中断端口数据包(下文将对此进行讨论)的单调时间戳。

新的 zx_object_get_info 主题

由于中断对象在创建时会设置时间轴,因此我们必须为用户空间提供一种在运行时查询给定中断句柄的时间轴的方法。为此,系统会向 zx_object_get_info 添加一个新的 ZX_INFO_INTERRUPT 主题。传入此主题将返回以下结构体:typedef struct zx_info_interrupt { // The options used to create the interrupt object. uint32_t options; } zx_info_interrupt_t; 然后,调用方可以检查 options 字段中是否存在 ZX_INTERRUPT_TIMESTAMP_MONO

libzx 封装容器

libzx 库围绕 zx_interrupt_waitzx_interrupt_trigger 系统调用提供了 C++ 封装容器。这些封装容器需要更新,才能同时使用单调时间戳和启动时间戳。

端口数据包变更

有几个端口数据包包含需要修改的时间戳。

zx_packet_interrupt_t

zx_interrupt_bind 系统调用允许将中断对象绑定到端口。当绑定的中断触发时,系统会在端口上将 ZX_PKT_TYPE_INTERRUPT 数据包加入队列。此数据包包含一个 timestamp 字段,用于指明中断发生的时间。

为了与更新后的中断 API 保持一致,这些数据包中的 timestamp 字段也将默认改用启动时间轴。这样可以确保所有中断时间戳(无论是通过系统调用还是中断数据包获取的)都能提供关于挂起-恢复周期中断时间的一致视图。

不过,对于使用 ZX_INTERRUPT_TIMESTAMP_MONO 标志创建的中断,相应数据包中的 timestamp 字段将继续使用单调时间戳。这可保留与依赖于此行为的驱动程序的兼容性。

zx_packet_signal_t

此数据包会加入到使用 zx_object_wait_async 系统调用绑定到对象的端口的队列中,该系统调用具有以下签名:

zx_status_t zx_object_wait_async(zx_handle_t handle,
                                 zx_handle_t port,
                                 uint64_t key,
                                 zx_signals_t signals,
                                 uint32_t options);

signals 集中的信号在 handle 上断言时,如果调用方在 options 字段中传递了 ZX_WAIT_ASYNC_TIMESTAMP,则生成的数据包会加入到端口队列中:

typedef struct zx_packet_signal {
  // ... other fields ...
  uint64_t timestamp;
  // ... reserved fields ...
} zx_packet_signal_t;

timestamp 字段记录信号的有效化时间。根据我们正在等待的对象类型,时间戳在不同的时间轴上是合理的。例如,我们希望在等待启动计时器时获取启动时间戳。

因此,我们建议您采取以下措施:

  1. zx_object_wait_async 系统调用添加一个名为 ZX_WAIT_ASYNC_BOOT_TIMESTAMP 的新标志,该标志表示 timestamp 字段应使用启动时间轴。
  2. timestamp 的类型切换为多态别名 zx_time_t

我们将审核 ZX_WAIT_ASYNC_TIMESTAMP 标志的所有现有用法,并确保在暂停单调时钟之前,所有需要启动时间戳的调用方都已迁移。

实现

我们将先实现对内核 API 的更改,并将每类更改(读取时间、时钟、计时器等)分隔到不同的 CL 中。

这些更改(应该相对较小)之后,我们将进行一些稍微更大的更改,以便为 Rust 和 C++ 系统调用封装容器库添加启动时间支持。这些变更将分两个阶段进行:

  1. 第 1 阶段将添加对此 RFC 添加或修改的系统调用的支持。 这需要对所用类型进行一些修改,但本阶段将主要关注功能变更。
  2. 第 2 阶段将针对时间类型添加更强大的类型强制执行功能。这将在第 1 阶段之后完成,因为确定每种语言所需的精确类型结构可能需要更长时间。

性能

支持启动时间所需的系统调用修改完全是累加的,因此不应更改系统的任何现有性能特性。

同样,更新 Zircon 中各种结构的时间戳字段以使用启动时间轴应该不会对性能产生影响,因为计算启动时间的开销与计算单调时间的开销一样。

工效学设计

使用时间系统调用和类型的用户可能需要注意,内核现在支持多个时间轴。不过,大多数程序应该只需使用单调时间即可。

向后兼容性

除了对日志记录时间戳时间轴的语义更改外,这些更改在源代码和二进制文件方面都向后兼容。

不过,许多现有代码可能需要从使用单调时间轴改为使用启动时间轴,以确保正确性。如需详细了解我们计划如何处理此问题,请参阅单调时钟暂停和启动时间表 RFC

安全注意事项

我们预计此提案不会产生任何安全影响。如需详细了解原因,请参阅 Monotonic Clock Suspension and the Boot Timeline RFC 的安全部分。

隐私注意事项

此提案不会影响系统隐私,因为向内核添加启动时间支持不会涉及任何类型的数据收集。

测试

核心测试将被修改,以同时测试新的系统调用和对现有系统调用的修改。

文档

上述所有 Zircon API 修改都需要记录在案。 具体而言,这意味着:

  1. 记录所有新的类型别名,并更新现有类型别名的文档。
  2. 记录新添加的系统调用。
  3. 记录添加到现有系统调用的新标志。
  4. 记录更新的各种时间戳的时间表。

此外,我们应更新 fuchsia.dev 上的时钟文档,以突出说明 zx_time_t 如何不受特定时间表的约束。

在先技术和参考文档