An ontology of clocks

Daniel Franke dfoxfranke at gmail.com
Fri Feb 19 22:56:04 UTC 2016


This email proposes an ontology for describing what clocks are present
on a system and how they behave. It's based on some thinking that came
out of the "How NTP Works" presentation that I'm working on, but which
I decided is too for afield to make it in. Nonetheless it deserves
writing down somewhere. One place it could be inspirational is in the
design of a portable abstraction layer over the myriad different time
APIs exposed by various operating systems.

Systems have lots of different clocks. If you look at the manpage for
clock_gettime(2) you'll see a bunch of them, and there are actually a
few more that either aren't documented there or are accessed through
other APIs. This ontology provides a language to describe how they all
relate.

At a high level, a clock is characterized by the answers to three questions:

I. What does the clock represent, and how does it represent it?
II. What signal drives it?
III. What knobs are available for adjusting it?
IV. What can cause it to become corrupt?

# I. What does the clock represent, and how?

Every clock is some sort of counter, so this question can be rephrased
by asking:

1. Does the clock have a specified epoch?
2. What does the clock count?
3. What its precision, i.e., the smallest unit of time it can represent?

Clocks with a specified epoch are those which at least presume to
answer "what time is it?", though the precise interpretation of this
depends on the answer question #2. Clocks without a specified epoch
are those only intended to measure durations: the difference between
two timestamps is meaningful, but a single timestamp in isolation is
not.

Based on the answer to question #2, clocks can be organized into four
categories.

A. Atomic clocks, which count SI seconds. Their epoch can be specified
in terms of a TAI timestamp.

B. Universal clocks, which count mean solar seconds. Their epoch can
be specified in terms of a UT1 timestamp. (It is very unlikely that
your system provides a universal clock, but nonetheless the concept is
enlightening).

C. Coordinated clocks, which count calendar days plus SI seconds since
midnight of the current day. Their epoch can be specified in terms of
a UTC datestamp. (To avoid needless complexity, only epoch occurring
at midnight UTC are considered.)

D. Clumsy clocks, which count non-leap seconds. Their epoch can be
specified in terms of a UTC timestamp. These clocks generally want to
be coordinated clocks, but fail because they're incapable of
representing that a leap second is occurring. POSIX time is an example
of a clumsy clock.

I've deliberately left clocks that track local time out of
consideration. Any remotely sane timekeeping implementation tracks
civil time as UTC and then does table-based conversion to local time
as an afterthought, orthogonal to all else. Also out of consideration
are clocks that count time selectively, e.g. CPU time used by a
particular process, as these are uninteresting to NTP implementations.

# II - What drives it?

A typical PC has several oscillators. In addition to the main one,
which sends clock interrupts to the CPU, there's likely to be a
separate one on each NIC (important for PTP), one driving the RTC (aka
BIOS clock), and perhaps an external oscillator providing a PPS
signal.

Clocks are arranged in a multi-rooted hierarchy. At the top of the
hierarchy are those clocks which are directly driven by an oscillator.
Clocks lower in the hierarchy have values that are computed based on
the value of their parent. For example, a coordinated clock could be
derived from an atomic clock using a table of leap seconds.

Operationally, two clocks having the same oscillator means that they
are expected to wander together, and having different oscillators
means that they are expected to wander independently. If desired, this
model could be generalized by relating oscillators to each other
through a covariance matrix.

# III. How can the clock be adjusted:

Operating systems commonly provide several mechanisms for adjusting a clock:

A. Stepping it discontinuously, e.g., clock_settime(), or adjtimex()'s
ADJ_SETOFFSET mode).

B. Slewing it gradually, e.g., adjtime(), or equivalently adjtimex()'s
ADJ_OFFSET mode. Specification of this mechanism can be further
refined by describing the rate at which the residual offset is carried
over: is it exponential or is it linear, and what is its time
constant?

C. Adjusting its frequency, e.g., adjtimex()'s ADJ_FREQUENCY,
OpenBSD's adjfreq(), Windows' SetSystemTimeAdjustment().

D. Scheduling insertion or deletion of leap seconds.

A clock's adjustability can be specified by two bits per mechanism:

1. Can this clock be adjusted (independently of its parent) using the
given mechanism?
2. When this clock's parent is adjusted using the given mechanism, is
this clock affected?

# IV. What corrupts it?

Certain events, such as suspending or rebooting the machine, can cause
a clock to lose its time. Timestamps from after such an event are
incomparable with timestamps from before it.

# Putting it together

Let's see how this all applies to an example Linux system which has
four oscillators: the system oscillator, the RTC oscillator, an
oscillator on the NIC, and a PPS on the serial port.

* CLOCK_MONOTONIC_RAW is an atomic clock with an unspecified epoch and
a precision of one nanosecond. It is driven by the system oscillator.
It supports no adjustments at all. It is corrupted by suspending or
rebooting the machine.

* CLOCK_MONOTONIC is an atomic clock with an unspecified epoch and a
precision of one nanosecond. If kPPS is disabled, then it is driven by
CLOCK_MONOTONIC_RAW, and supports independent slewing and frequency
adjustment using adjtimex(). If kPPS is enabled, then it is driven by
the PPS and supports no adjustments. It is corrupted by suspending or
rebooting the machine.

* CLOCK_BOOTTIME is an atomic clock with an unspecified epoch and a
precision of one nanosecond. It is derived from CLOCK_MONOTONIC, is
affected by all adjustments to it, and supports no adjustments of its
own. However, CLOCK_BOOTTIME is corrupted only by rebooting, not by
suspending.

* CLOCK_REALTIME is a clumsy clock with an epoch of midnight UTC,
January 1, 1970 and a precision of one nanosecond. It is driven by
CLOCK_BOOTTIME and is affected by all adjustments to it; in addition,
it supports independent stepping and scheduling insertion and deletion
of leap seconds. It is corrupted by rebooting.

* CLOCK_TAI (undocumented but present!) is an atomic clock with an
epoch of midnight UTC, January 1, 1970 and a precision of one
nanosecond. It is driven by CLOCK_REALTIME and is affected by
stepping, slewing, and frequency adjustment but not by
insertion/deletion of leap seconds. It can be stepped independently of
CLOCK_REALTIME by using adjtimex() to set the TAI offset. It is
corrupted by rebooting.

* /dev/rtc0 is a clumsy clock with a specified epoch and a precision
of one second. It is driven by the RTC oscillator. It supports
stepping. It is corrupted only by the death of the CMOS battery.

* /dev/ptp0 is an atomic clock with an unspecified epoch and a
precision of one nanosecond. It is driven by the NIC oscillator. It
supports stepping. It is corrupted by suspending and rebooting.


More information about the devel mailing list