RFC-0077:zx_clock_update 准确性改进 | |
---|---|
状态 | 已接受 |
区域 |
|
说明 | 对 zx_clock_update 进行了更改,让时钟维护者可以选择提供更多信息并实现更高的精确度。 |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2021-03-04 |
审核日期(年-月-日) | 2021-03-18 |
摘要
zx_clock_update
系统调用用于在内核时钟对象中设置时间,但当前的 API 设计限制了可实现的精确度。本文档介绍了对 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
选项。
为保持命名一致性,我们将 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_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
会在当前参考时间开始新的线段,与由 zx_clock_update_args_v2_t.rate_adjust
确定的梯度相交的点为 (reference=zx_clock_update_args_v2_t.reference_value
, synthetic=zx_clock_update_args_v2_t.synthetic_value
),如图 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
并公开新功能。 - 更新了计时器,以便在设置世界协调时间 (UTC) 时钟时提供参考值,并缩短允许的测试误差时间范围。
性能
由于 zx_clock_update
系统调用用量不高,并且所做更改不大,因此这项更改对性能的影响微乎其微。
如果设置了 ZX_CLOCK_UPDATE_OPTION_REFERENCE_VALUE_VALID
,zx_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 - 基于合成偏移的替代解决方案
这样可以通过更少的 API 更改来完全指定新行。不过,对于时钟维护人员来说,这种方法不太直观,并且需要进行更复杂的计算才能正确使用。我们认为,在 Fuchsia 的生命周期内,这种替代方案会产生更多 bug。
在先技术和参考文档
kernel_objects/clock 简要介绍了用户空间时钟的运作方式。
UTC 同步算法总结了当前的 UTC 同步设计。
“Zircon Syscalls Struct Evolution”(Zircon 系统调用结构体演变)是一份 2019 年 5 月的 Google 内部文档,其中讨论了系统调用结构体的演变,对于有权访问该文档的读者可能很有帮助。