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

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

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

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

问题陈述

单调时钟暂停和启动时间轴 RFC 中指出,Fuchsia 必须提供一个启动时间轴,用于报告自启动以来经过的时间量,包括在挂起到空闲状态下花费的任何时间。该 RFC 提到需要进行内核更改,但未明确指定具体更改内容。

摘要

此 RFC 介绍了在 Zircon 内核中支持启动时间轴所需的系统调用和 API 变更。

利益相关方

Facilitator: jamesr@google.com

审核者

  • 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_tzx_duration_t 的大多数现有用法将迁移为使用 zx_duration_mono_t。我们会根据具体情况评估和迁移 zx_ticks_t 的使用情况。

现有别名不会被移除,并且会在需要模糊类型时使用(例如,zx_clock_read 仍会返回 zx_time_t)。内核团队的目标是在长期内移除这些模糊类型,但具体如何实现不在本文档的讨论范围内,留待未来的 RFC。

用户空间时间类型

虽然上一部分中介绍的用于表示时间的新类型别名将提高 Zircon API 中代码的清晰度和可读性,但它们无法提供编译器强制执行的类型安全性。这种类型安全将由 Zircon 的 Rust 绑定C++ 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 系统调用创建的。系统调用已接受 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_object_get_info 并指定 ZX_INFO_TIMER 主题,以获取有关计时器句柄的信息。生成的 zx_info_timer_t 结构体将被修改,以包含创建计时器时使用的 clock_id

通常,这需要进行一些结构体演变,但在这种情况下,结构体已经有 4 字节的填充,这足以容纳我们的 clock_id,后者存储为 4 字节的 zx_clock_t

更新 Zircon 结构中的时间戳

Zircon 结构中有多个时间戳会报告单调递增时间,但一旦启动时间可用,就应切换为使用启动时间。

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

zx_log_record_t 时间戳

zx_log_record_t 是 Zircon 公开的结构体,用于描述 debuglog 中消息的结构。此结构包含一个 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);

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

安全注意事项

我们预计此提案不会带来任何安全方面的影响。如需详细了解原因,请参阅单调时钟暂停和启动时间轴 RFC 的安全部分。

隐私注意事项

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

测试

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

文档

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

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

此外,我们还应更新 fuchsia.dev 上的时钟文档,以突出显示 zx_time_t 不受特定时间线的限制。

在先技术和参考资料