| RFC-0077: zx_clock_update accuracy improvement | |
|---|---|
| Status | Accepted |
| Areas |
|
| Description | Changes to zx_clock_update that let clock maintainers opt in to supplying more information and achieving higher accuracy. |
| Issues | |
| Gerrit change | |
| Authors | |
| Reviewers | |
| Date submitted (year-month-day) | 2021-03-04 |
| Date reviewed (year-month-day) | 2021-03-18 |
Summary
The zx_clock_update syscall is
used to set the time in a kernel clock object but the current API design limits
the accuracy that may be achieved. This RFC describes changes to
zx_clock_update that let clock maintainers opt in to supplying more
information and achieving higher accuracy.
Motivation
Kernel clock objects are updated using the zx_clock_update syscall. Clock
maintainers may request a step change in a clock by setting
ZX_CLOCK_UPDATE_OPTION_VALUE_VALID and placing the new time in
zx_clock_update_args_v1_t.value.
This API design is simple to use but limits the accuracy that may be achieved
when setting a new clock value: a userspace process may be preempted between
calculating zx_clock_update_args_v1_t.value and calling zx_clock_update and
every 1ms delay between these two events will lead to a 1ms error in the clock.
This error has been problematic for UTC integration and has led to many test
flakes, particularly on emulators where the error can be hundreds of
milliseconds. Note that zx_clock_update also supports changing a clock's rate
without setting a new value and these rate-only updates are much less
susceptible to error.
This RFC defines a new option for zx_clock_update, enabling a more accurate
clock for UTC and for other future users of kernel clocks.
Design
Overview
A clock is a one dimensional affine transformation of the clock monotonic reference timeline, defining how the "reference" time (i.e. the device's monotonic clock) should be translated to the "synthetic" time output by the clock. When a clock maintainer requests a change to the clock they are effectively supplying a new line segment in this transform as illustrated in Figure 1.
Figure 1 - A clock as a transform from reference to synthetic time
In the existing design a clock maintainer supplies the gradient of this line
segment using zx_clock_update_args_v1_t.rate_adjust and the y-coordinate at
the start of the segment using zx_clock_update_args_v1_t.value. The kernel
sets the x-coordinate at the start of the segment to the monotonic time when it
handles the syscall.
This definition of the segment start, with two coordinates set by different entities, is the root cause of the clock error as illustrated in Figure 2.
Figure 2 - Delays between calculation and handling lead to clock error
We propose a change to the zx_clock_update arguments that lets
a clock maintainer fully specify its intended line, while the kernel determines
the reference time at which the change is applied, i.e. the start position on
this line.
API Changes
We introduce a new zx_clock_update_args_v2_t struct containing all the fields
currently in
zx_clock_update_args_v1_t
plus an additional reference_value field. We also add a new
ZX_CLOCK_UPDATE_OPTION_REFERENCE_VALUE_VALID option.
For naming consistency we rename the value field in
zx_clock_update_args_v1_t to synthetic_value in zx_clock_update_args_v2_t
and rename ZX_CLOCK_UPDATE_OPTION_VALUE_VALID to
ZX_CLOCK_UPDATE_OPTION_SYNTHETIC_VALUE_VALID.
For convenience we also define a
ZX_CLOCK_UPDATE_OPTION_BOTH_VALUES_VALID that may be used to set both
ZX_CLOCK_UPDATE_OPTION_REFERENCE_VALUE_VALID and
ZX_CLOCK_UPDATE_OPTION_SYNTHETIC_VALUE_VALID.
Behavior Changes
For readability throughout this section and the next, we abbreviate
ZX_CLOCK_UPDATE_OPTION_X_VALID to simply X_VALID.
When REFERENCE_VALUE_VALID, SYNTHETIC_VALUE_VALID, and RATE_ADJUST_VALID
are all set, zx_clock_update starts a new line segment at the current
reference time, intersecting a point (
reference=zx_clock_update_args_v2_t.reference_value,
synthetic=zx_clock_update_args_v2_t.synthetic_value) with a gradient
determined by zx_clock_update_args_v2_t.rate_adjust as illustrated in
Figure 3.
Figure 3 - Updating both the value and rate of a clock
When REFERENCE_VALUE_VALID and SYNTHETIC_VALUE_VALID are set but
RATE_ADJUST_VALID is not set, zx_clock_update starts a new line segment at
the current reference time, intersecting a point
(reference=zx_clock_update_args_v2_t.reference_value,
synthetic=zx_clock_update_args_v2_t.synthetic_value) with a gradient
determined by the prior clock rate as illustrated in Figure 4.
Figure 4 - Updating only the clock value
When REFERENCE_VALUE_VALID and RATE_ADJUST_VALID are set but
SYNTHETIC_VALUE_VALID is not set, zx_clock_update starts a new line
segment at the current reference time, intersecting a point on the prior line
segment at reference=zx_clock_update_args_v2_t.reference_value with a gradient
determined by zx_clock_update_args_v2_t.rate_adjust as illustrated in
Figure 5.
Figure 5 - Updating only the clock rate
When REFERENCE_VALUE_VALID is set but neither RATE_ADJUST_VALID nor
SYNTHETIC_VALUE_VALID are set, zx_clock_update returns an
ZX_ERR_INVALID_ARGS error.
When REFERENCE_VALUE_VALID is not set, zx_clock_update follows its existing
behavior, starting a new line segment at the current reference time, changing
the synthetic time to zx_clock_update_args_v2_t.synthetic_value if
SYNTHETIC_VALUE_VALID is set and changing the gradient based on
zx_clock_update_args_v2_t.rate_adjust if RATE_ADJUST_VALID is set.
Continuous and Monotonic Clocks
A clock may have the ZX_CLOCK_OPT_MONOTONIC and/or ZX_CLOCK_OPT_CONTINUOUS
options set at creation, limiting the updates that can be applied.
A ZX_CLOCK_OPT_CONTINUOUS clock will not accept any step changes in time. From
Figures 4, 5, and 6 it can be seen that REFERENCE_VALUE_VALID always
introduces a discontinuity in the synthetic value and therefore setting
REFERENCE_VALUE_VALID for a ZX_CLOCK_OPT_CONTINUOUS clock will always lead
to a ZX_ERR_INVALID_ARGS error.
A clock with ZX_CLOCK_OPT_MONOTONIC but not ZX_CLOCK_OPT_CONTINUOUS will
accept step changes in time if and only if they cause an increase in the
synthetic value. Some clock updates could lead to a race condition where the
request might either be accepted or rejected depending on when it reaches the
kernel. For example, before this RFC a request to set a monotonic clock 5ms in
the future would be rejected if the calling process was preempted for more than
5ms between calculating zx_clock_update_args and calling zx_clock_update.
This non-determinism is undesirable and therefore the following
zx_clock_update requests will fail with an ZX_ERR_INVALID_ARGS error when
made on a ZX_CLOCK_OPT_MONOTONIC clock:
SYNTHETIC_VALUE_VALIDis set butREFERENCE_VALUE_VALIDis not set;RATE_ADJUST_VALIDis set andREFERENCE_VALUE_VALIDis set.
Note this means it is not possible to change both the rate and the offset of a
ZX_CLOCK_OPT_MONOTONIC clock in the same zx_clock_update call.
Implementation
This change will be implemented in four stages:
- Define
zx_clock_update_args_v2_t,ZX_CLOCK_UPDATE_OPTION_REFERENCE_VALUE_VALID, andZX_CLOCK_UPDATE_OPTION_BOTH_VALUES_VALID; - Update
clock.ccand the associated unit tests to acceptzx_clock_update_args_v2_t; - Update language-specific wrappers such as Rust's
fuchsia-zirconto usezx_clock_update_args_v2_tand expose the new functionality. - Update Timekeeper to supply a reference value when setting the UTC clock and reduce the allowable test error windows.
Performance
This change has minimal impact on performance since the zx_clock_update
syscall is not heavily used and the changes are modest.
zx_clock_update_args will increase by 8 bytes and the kernel will perform a
few more integer arithmetic instructions when handling zx_clock_update if
ZX_CLOCK_UPDATE_OPTION_REFERENCE_VALUE_VALID is set.
Security considerations
This change does not alter the relationship between clock maintainers and clock users and therefore does not impact security.
Privacy considerations
This change does not alter the relationship between clock maintainers and clock users and therefore does not impact privacy.
Testing
Unit tests in
kernel-clocks.cc
will be expanded to cover this new behavior. Timekeeper will be updated to set
UTC using an explicit monotonic time so the existing UTC unit and integration
tests will provide additional test coverage.
Documentation
The zx_clock_update reference documentation
will be updated to describe this new behavior.
Drawbacks, alternatives, and unknowns
A simpler alternative would be to retain the existing
zx_clock_update_args_v1_t struct but introduce an additional
ZX_CLOCK_UPDATE_OPTION_ZERO_VALUE_VALID option that changes its
interpretation. When ZX_CLOCK_UPDATE_OPTION_ZERO_VALUE_VALID was set, the
kernel would interpret zx_clock_update_args_v1_t.value as the synthetic time
corresponding to monotonic=0 and ensure that the new line segment passes through
a point (monotonic=0, synthetic=zx_clock_update_args_v1_t.value) as
illustrated in Figure 6.
Figure 6 - Alternative solution based on synthetic offset
This has the effect of fully specifying the new line with a smaller change in the API. However, it is less intuitive for clock maintainers and requires more complex calculations to use correctly. Over the life of Fuchsia we believe this alternative would create more bugs.
Prior art and references
kernel_objects/clock provides an overview of the operation of userspace clocks.
UTC synchronization algorithms summarizes the current UTC synchronization design.
"Zircon Syscalls Struct Evolution" is a Google-internal document from May 2019 that discussed the evolution of syscall structs and may be helpful to readers with access.