RFC-0077:提升了 zx_clock_update

RFC-0077:zx_clock_update 准确性改进
状态已接受
区域
  • 内核
说明

对 zx_clock_update 的更改,允许时钟维护者选择提供更多信息并实现更高的准确性。

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

摘要

zx_clock_update 系统调用用于设置内核时钟对象中的时间,但当前的 API 设计限制了可实现的精度。此 RFC 描述了对 zx_clock_update 的更改,这些更改使时钟维护人员可以选择提供更多信息并实现更高的准确性。

设计初衷

内核时钟对象使用 zx_clock_update 系统调用进行更新。时钟维护人员可以通过设置 ZX_CLOCK_UPDATE_OPTION_VALUE_VALID 并将新时间放置在 zx_clock_update_args_v1_t.value 中,请求时钟发生阶跃变化。

此 API 设计易于使用,但在设置新的时钟值时会限制可实现的准确性:用户空间进程可能会在计算 zx_clock_update_args_v1_t.value 和调用 zx_clock_update 之间被抢占,这两个事件之间每延迟 1 毫秒都会导致时钟出现 1 毫秒的误差。此错误一直困扰着 UTC 集成,并导致许多测试出现抖动,尤其是在模拟器上,该错误可能会达到数百毫秒。请注意,zx_clock_update 还支持在不设置新值的情况下更改时钟速率,并且这些仅限速率的更新不太容易出错。

此 RFC 为 zx_clock_update 定义了一个新选项,可为 UTC 和其他未来的内核时钟用户提供更精确的时钟。

设计

概览

时钟是时钟单调参考时间轴的一维仿射转换,用于定义如何将“参考”时间(即设备的单调时钟)转换为时钟输出的“合成”时间。当时钟维护者请求更改时钟时,他们实际上是在此转换中提供一条新的线段,如图 1 所示。

图 1 - 时钟作为从参考时间到合成时间的转换
此图以一条包含多个线段的线表示 x 轴上的参考时间与 y 轴上的合成时间之间的关系,其中一些线段相邻,另一些不相交。

在现有设计中,时钟维护器使用 zx_clock_update_args_v1_t.rate_adjust 提供此线段的梯度,并使用 zx_clock_update_args_v1_t.value 提供线段起点的 y 坐标。内核将段开头的 x 坐标设置为处理系统调用时的单调时间。

这种由不同实体设置两个坐标的细分开始定义是时钟误差的根本原因,如图 2 所示。

图 2 - 计算和处理之间的延迟会导致时钟错误
此图显示了 x 轴上的参考时间和 y 轴上的合成时间之间的预期相关性和实际相关性,两者之间的差异是时钟误差。

我们建议更改 zx_clock_update 实参,以便时钟维护者完全指定其预期的时间线,同时让内核确定应用更改的参考时间,即此时间线上的起始位置。

API 更改

我们引入了一个新的 zx_clock_update_args_v2_t 结构,其中包含 zx_clock_update_args_v1_t 中的所有字段以及一个额外的 reference_value 字段。我们还添加了一个新的 ZX_CLOCK_UPDATE_OPTION_REFERENCE_VALUE_VALID 选项。

为保持命名一致性,我们将 zx_clock_update_args_v1_t 中的 value 字段重命名为 zx_clock_update_args_v2_t 中的 synthetic_value,并将 ZX_CLOCK_UPDATE_OPTION_VALUE_VALID 重命名为 ZX_CLOCK_UPDATE_OPTION_SYNTHETIC_VALUE_VALID

为方便起见,我们还定义了一个 ZX_CLOCK_UPDATE_OPTION_BOTH_VALUES_VALID,可用于同时设置 ZX_CLOCK_UPDATE_OPTION_REFERENCE_VALUE_VALIDZX_CLOCK_UPDATE_OPTION_SYNTHETIC_VALUE_VALID

行为变更

为了便于阅读本部分和下一部分,我们将 ZX_CLOCK_UPDATE_OPTION_X_VALID 简称为 X_VALID

REFERENCE_VALUE_VALIDSYNTHETIC_VALUE_VALIDRATE_ADJUST_VALID 都设置好后,zx_clock_update 会在当前参考时间开始一条新的线段,该线段与一个点(reference=zx_clock_update_args_v2_t.reference_valuesynthetic=zx_clock_update_args_v2_t.synthetic_value)相交,梯度由 zx_clock_update_args_v2_t.rate_adjust 确定,如图 3 所示。

图 3 - 同时更新时钟的值和速率
此图展示了如何通过指定参考时间、合成时间和梯度来更新时钟。

如果设置了 REFERENCE_VALUE_VALIDSYNTHETIC_VALUE_VALID,但未设置 RATE_ADJUST_VALID,则 zx_clock_update 会在当前参考时间开始一条新的线段,该线段与点 (reference=zx_clock_update_args_v2_t.reference_value, synthetic=zx_clock_update_args_v2_t.synthetic_value) 相交,梯度由之前的时钟速率决定,如图 4 所示。

图 4 - 仅更新时钟值
此图说明了如何通过指定参考时间和合成时间来更新时钟。

当设置了 REFERENCE_VALUE_VALIDRATE_ADJUST_VALID 但未设置 SYNTHETIC_VALUE_VALID 时,zx_clock_update 会在当前参考时间开始一条新的线段,与前一条线段上的一个点相交,该点位于 reference=zx_clock_update_args_v2_t.reference_value 处,梯度由 zx_clock_update_args_v2_t.rate_adjust 确定,如图 5 所示。

图 5 - 仅更新时钟速率
此图展示了如何通过指定参考时间和更新后的速率来更新时钟。

如果设置了 REFERENCE_VALUE_VALID,但未设置 RATE_ADJUST_VALIDSYNTHETIC_VALUE_VALIDzx_clock_update 会返回 ZX_ERR_INVALID_ARGS 错误。

如果未设置 REFERENCE_VALUE_VALIDzx_clock_update 会遵循其现有行为,在当前参考时间开始新的线段,如果设置了 SYNTHETIC_VALUE_VALID,则将合成时间更改为 zx_clock_update_args_v2_t.synthetic_value,如果设置了 RATE_ADJUST_VALID,则根据 zx_clock_update_args_v2_t.rate_adjust 更改梯度。

持续时钟和单调时钟

时钟在创建时可能设置了 ZX_CLOCK_OPT_MONOTONIC 和/或 ZX_CLOCK_OPT_CONTINUOUS 选项,从而限制了可应用的更新。

ZX_CLOCK_OPT_CONTINUOUS时钟不会接受任何时间步进变化。从图 4、5 和 6 可以看出,REFERENCE_VALUE_VALID 始终会在合成值中引入不连续性,因此为 ZX_CLOCK_OPT_CONTINUOUS 时钟设置 REFERENCE_VALUE_VALID 始终会导致 ZX_ERR_INVALID_ARGS 误差。

如果时钟具有 ZX_CLOCK_OPT_MONOTONIC 但不具有 ZX_CLOCK_OPT_CONTINUOUS,则仅当时间步进变化导致合成值增加时,才会接受这些变化。某些时钟更新可能会导致竞态条件,即请求可能会被接受,也可能会被拒绝,具体取决于请求到达内核的时间。例如,在此 RFC 之前,如果调用进程在计算 zx_clock_update_args 和调用 zx_clock_update 之间被抢占的时间超过 5 毫秒,则将单调时钟设置为未来 5 毫秒的请求会被拒绝。

这种不确定性是不理想的,因此,当在 ZX_CLOCK_OPT_MONOTONIC 时钟上发出以下 zx_clock_update 请求时,这些请求将失败并显示 ZX_ERR_INVALID_ARGS 错误:

  1. 设置了 SYNTHETIC_VALUE_VALID,但未设置 REFERENCE_VALUE_VALID
  2. RATE_ADJUST_VALID 已设置,REFERENCE_VALUE_VALID 已设置。

请注意,这意味着无法在同一次 zx_clock_update 调用中同时更改 ZX_CLOCK_OPT_MONOTONIC 时钟的速率和偏移量。

实现

此更改将分四个阶段实施:

  1. 定义 zx_clock_update_args_v2_tZX_CLOCK_UPDATE_OPTION_REFERENCE_VALUE_VALIDZX_CLOCK_UPDATE_OPTION_BOTH_VALUES_VALID
  2. 更新 clock.cc 及相关联的单元测试,以接受 zx_clock_update_args_v2_t
  3. 更新特定于语言的封装容器(例如 Rust 的 fuchsia-zircon)以使用 zx_clock_update_args_v2_t 并公开新功能。
  4. 更新了 Timekeeper,以便在设置 UTC 时钟时提供参考值,并减少允许的测试误差窗口。

性能

由于 zx_clock_update 系统调用使用不多,且更改幅度不大,因此这项更改对性能的影响很小。

如果设置了 ZX_CLOCK_UPDATE_OPTION_REFERENCE_VALUE_VALIDzx_clock_update_args 将增加 8 字节,并且内核在处理 zx_clock_update 时会执行更多整数算术指令。

安全注意事项

此更改不会改变时钟维护者与时钟用户之间的关系,因此不会影响安全性。

隐私注意事项

此变更不会改变时钟维护者与时钟用户之间的关系,因此不会影响隐私权。

测试

我们将扩展 kernel-clocks.cc 中的单元测试,以涵盖此新行为。Timekeeper 将更新为使用显式单调时间设置 UTC,以便现有的 UTC 单元测试和集成测试提供额外的测试覆盖率。

文档

zx_clock_update 参考文档将更新以描述这一新行为。

缺点、替代方案和未知因素

一种更简单的替代方案是保留现有的 zx_clock_update_args_v1_t 结构,但引入一个额外的 ZX_CLOCK_UPDATE_OPTION_ZERO_VALUE_VALID 选项来更改其解释。设置 ZX_CLOCK_UPDATE_OPTION_ZERO_VALUE_VALID 后,内核会将 zx_clock_update_args_v1_t.value 解释为与 monotonic=0 对应的合成时间,并确保新线段通过点 (monotonic=0, synthetic=zx_clock_update_args_v1_t.value),如图 6 所示。

图 6 - 基于合成偏移量的替代解决方案
此图显示了 x 轴上的参考时间和 y 轴上的合成时间,并说明了如何使用 x=0 位置和梯度进行设置。

这样一来,只需对 API 进行较小的更改,即可完全指定新行。不过,对于时钟维护人员来说,这种方法不太直观,需要进行更复杂的计算才能正确使用。我们认为,在 Fuchsia 的整个生命周期内,这种替代方案会产生更多 bug。

在先技术和参考资料

kernel_objects/clock 提供了对用户空间时钟运行情况的概览。

世界协调时间 (UTC) 同步算法总结了当前的世界协调时间 (UTC) 同步设计。

“Zircon Syscalls Struct Evolution”是 Google 内部于 2019 年 5 月发布的一份文档,讨论了 syscall 结构的演变,可能对有权访问的读者有所帮助。