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. _______________________________________________ devel mailing list devel@ntpsec.org http://lists.ntpsec.org/mailman/listinfo/devel