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

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

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

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

问题陈述

单调时钟暂停和启动时间轴 RFC 规定,Fuchsia 必须提供一个启动时间轴,用于报告启动以来经过的时间,包括在暂停到空闲状态期间所花费的任何时间。该 RFC 中提到了需要进行内核更改,但并没有具体说明具体是什么。

摘要

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

利益相关方

主持人:jamesr@google.com

审核者

  • adamperry@google.com
  • andresoportus@google.com
  • cja@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 中上一部分详述的类型迁移之前,无需为每种语言完成此工作。

读取当前启动时间

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

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 系统调用创建的。该系统调用已经接受 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 个字节的填充,这足以容纳 clock_id(存储为 4 字节的 zx_clock_t)。

更新 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 上的一点。

崩溃日志运行时间戳

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

中断 API 变更

中断系统调用

Zircon Interrupt API 包含两个对单调时间戳运行的系统调用:

// Current function signatures.

// Allows users to wait on an interrupt, and returns the timestamp at which the
// interrupt was triggered.
zx_status_t zx_interrupt_wait(zx_handle_t handle,
                              zx_time_t* out_timestamp);

// Triggers a virtual interrupt at the given timestamp.
zx_status_t zx_interrupt_trigger(zx_handle_t handle,
                                 uint32_t options,
                                 zx_time_t timestamp);

这会带来一个问题,因为在挂起期间可能会触发中断,而此时单调时钟会暂停。因此,这些时间戳必须改为使用启动时间轴。具体而言,系统调用签名将更改为:

// Proposed function signatures.

zx_status_t zx_interrupt_wait(zx_handle_t handle,
                              zx_instant_boot_t* out_timestamp);
zx_status_t zx_interrupt_trigger(zx_handle_t handle,
                                 uint32_t options,
                                 zx_instant_boot_t timestamp);

由于 zx_instant_boot_tzx_time_t 都是 int64_t 的别名,因此可以通过简单的搜索和替换来实现此更改。

libzx 封装容器

libzx 库在 zx_interrupt_waitzx_interrupt_trigger 系统调用周围提供了 C++ 封装容器。需要更新这些封装容器才能使用新的启动时间戳。由于这些封装容器针对不同时间轴上的时间戳使用强类型,因此需要迁移所有调用站点以使用启动时间。

端口数据包更改

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

zx_packet_interrupt_t

借助 zx_interrupt_bind 系统调用,用户可以将中断对象绑定或解绑定到端口。触发绑定中断后,系统会在给定端口上将类型为 ZX_PKT_TYPE_INTERRUPT 的数据包加入队列。该数据包的结构如下:

typedef struct zx_packet_interrupt {
  // Timestamp at which the interrupt was triggered.
  zx_time_t timestamp;
  // ..some reserved fields...
} zx_packet_interrupt_t;

timestamp 字段必须改用启动时间(因此变为 zx_instant_boot_t),原因与 zx_interrupt_wait 返回的时间戳必须在启动时间轴上相同。

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);

当在 handle 上断言 signals 集中的某个信号时,如果调用方在 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 如何不受特定时间表的约束。

在先技术和参考文档