| 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 - 时钟作为从参考时间到合成时间的转换
在现有设计中,时钟维护人员使用 zx_clock_update_args_v1_t.rate_adjust 提供此线段的梯度,并使用
zx_clock_update_args_v1_t.value 提供线段起点的 y 坐标。内核在处理系统调用时,会将线段起点的 x
坐标设置为单调时间。
此线段起点的定义(由不同实体设置两个坐标)是时钟错误的根本原因,如图 2 所示。
图 2 - 计算和处理之间的延迟会导致时钟错误
我们建议更改 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 选项。
为了保持命名一致性,我们将 value 字段在
zx_clock_update_args_v1_t 中重命名为 synthetic_value 在 zx_clock_update_args_v2_t
中,并将 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_VALID 和
ZX_CLOCK_UPDATE_OPTION_SYNTHETIC_VALUE_VALID。
行为变更
为了便于阅读本部分和下一部分,我们将
ZX_CLOCK_UPDATE_OPTION_X_VALID简称为X_VALID。
当 REFERENCE_VALUE_VALID、SYNTHETIC_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)相交,梯度由 zx_clock_update_args_v2_t.rate_adjust 确定,如图 3 所示。
图 3 - 更新时钟的值和速率
当 REFERENCE_VALUE_VALID 和 SYNTHETIC_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_VALID 和 RATE_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_VALID 和 SYNTHETIC_VALUE_VALID
均未设置时,zx_clock_update 会返回 ZX_ERR_INVALID_ARGS 错误。
当 REFERENCE_VALUE_VALID 未设置时,zx_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 错误:
SYNTHETIC_VALUE_VALID已设置,但REFERENCE_VALUE_VALID未设置;RATE_ADJUST_VALID已设置,且REFERENCE_VALUE_VALID已设置。
请注意,这意味着无法在同一 zx_clock_update 调用中同时更改 ZX_CLOCK_OPT_MONOTONIC 时钟的速率和偏移量。
实现
此更改将分四个阶段实现:
- 定义
zx_clock_update_args_v2_t、ZX_CLOCK_UPDATE_OPTION_REFERENCE_VALUE_VALID和ZX_CLOCK_UPDATE_OPTION_BOTH_VALUES_VALID; - 更新
clock.cc和关联的单元测试以接受zx_clock_update_args_v2_t; - 更新特定于语言的封装容器(例如 Rust 的
fuchsia-zircon)以使用zx_clock_update_args_v2_t并公开新功能。 - 更新 Timekeeper 以在设置 UTC 时钟时提供参考值,并减少允许的测试错误窗口。
性能
此更改对性能的影响很小,因为 zx_clock_update 系统调用使用不多,并且更改幅度不大。
zx_clock_update_args 将增加 8 个字节,并且内核在处理 zx_clock_update 时将执行一些额外的整数算术指令,如果 ZX_CLOCK_UPDATE_OPTION_REFERENCE_VALUE_VALID 已设置。
安全注意事项
此更改不会改变时钟维护人员和时钟用户之间的关系,因此不会影响安全性。
隐私注意事项
此更改不会改变时钟维护人员和时钟用户之间的关系,因此不会影响隐私。
测试
将扩展
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 - 基于合成偏移量的替代解决方案
这样做的效果是,通过较小的 API 更改完全指定新行。但是,对于时钟维护人员来说,这种方式不太直观,并且需要更复杂的计算才能正确使用。在 Fuchsia 的整个生命周期中,我们认为这种替代方案会产生更多 bug。
在先技术和参考文档
kernel_objects/clock 概述了用户空间时钟的运行情况。
UTC 同步算法 总结了当前的 UTC 同步设计。
“Zircon Syscalls Struct Evolution”是 Google 于 2019 年 5 月发布的一份内部文档,讨论了系统调用结构体的演变,可能对有权访问的读者有所帮助。