RFC-0077:提升了 zx_clock_update

RFC-0077:zx_clock_update 准确性提升
状态已接受
领域
  • 内核
说明

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

问题
  • 65726
Gerrit 更改
  • 495844
作者
审核人
提交日期(年-月-日)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_REFERENCE_VALUE_VALIDZX_CLOCK_UPDATE_OPTION_SYNTHETIC_VALUE_VALIDZX_CLOCK_UPDATE_OPTION_BOTH_VALUES_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_VALIDzx_clock_update 会在当前参考时间处开始一个新线段,使点(reference=zx_clock_update_args_v2_t.reference_valuesynthetic=zx_clock_update_args_v2_t.synthetic_value)与由先前时钟频率确定的渐变相交,如图 4 所示。

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

如果设置了 REFERENCE_VALUE_VALIDRATE_ADJUST_VALID,但未设置 SYNTHETIC_VALUE_VALIDzx_clock_update 会在当前参考时间处开始一个新线段,使用由 zx_clock_update_args_v2_t.rate_adjust 确定的渐变与前一线段上位于 reference=zx_clock_update_args_v2_t.reference_value 的点相交,如图 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_args 会增加 8 个字节,并且如果设置了 ZX_CLOCK_UPDATE_OPTION_REFERENCE_VALUE_VALID,在处理 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=0synthetic=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”是 2019 年 5 月发布的 Google 内部文档,讨论了系统调用结构的演变,可能对拥有访问权限的读者有帮助。