Operating Systems · Interrupts and Timers
Clock Sources, Clock Events, and the Tick
The two abstractions behind kernel time: hardware that can be read for a timestamp, hardware that can raise an interrupt at a chosen moment, and the periodic tick built from the second.
Two questions about time look similar and are answered by different hardware. “What time is it now?” needs a counter that can be read at any moment and compared against a reference. “Wake me when the clock reaches this value” needs a device that will raise an interrupt at that moment, without anyone polling. A machine that could only answer the first would have to spin; a machine that could only answer the second could not timestamp anything. The kernel therefore keeps two abstractions, and the periodic tick is what happens when the second is programmed to fire on a fixed schedule regardless of what anyone asked for.
The split is older than the names. Early systems had a single programmable interval timer that served both purposes: reading it gave a coarse timestamp, and programming it gave a periodic interrupt. Modern hardware separates them because the requirements conflict. A timestamp source wants a wide, stable, monotonically increasing counter with cheap reads. An event source wants precise, arbitrary, one-shot programming with low jitter. The same oscillator often drives both, but the counters are distinct.
What makes a counter a clock source
A clock source is a structure with a read function, a rating, a mask, and a description of its resolution. The read function returns a cycle count; the kernel multiplies it by a mult/shift pair to convert cycles into nanoseconds without floating point. The pair is computed once at registration so that the conversion is a multiply and a shift rather than a division, because the read path is called from the scheduler, from timestamps, and from the delay loop.
The mask exists because counters wrap. A 32-bit counter at a few tens of megahertz wraps every couple of minutes; the kernel must detect the wrap by comparing successive readings and adding the modulus. A counter whose read function cannot be called safely from any context, or whose value can move backwards, is unusable, and the registration path checks for both.
Selection is by rating. The kernel keeps a list of registered sources and picks the highest-rated one that is currently usable, with a fallback chain behind it. The rating is a number chosen by the driver author to express preference, and the ordering reflects properties that matter for timekeeping: a counter in the CPU itself is cheaper to read than one behind a bus, a counter driven by a stable oscillator drifts less than one derived from an unreliable source, and a counter that stops in a low-power state is worse than one that keeps running.
The watchdog is the part that makes selection dynamic. Each clock source can be watched: the kernel periodically compares it against an independent reference, and if it drifts beyond a threshold or jumps, it is marked unstable and demoted. This is not a theoretical safeguard. A counter whose oscillator is thermally sensitive, or one that is affected by frequency scaling, will produce a timestamp that goes backwards, and a monotonic clock that goes backwards breaks every interval calculation built on it.
From cycles to nanoseconds
The conversion is the part that is easy to get subtly wrong. A naive implementation computes cycles * 1000000000 / frequency, which overflows for any reasonable cycle count. The kernel instead stores a 32-bit multiplier and a 32-bit shift such that ns = (cycles * mult) >> shift, choosing the pair so the error stays below one nanosecond over the counter’s range.
/* The shape of the conversion, using the kernel's 64x32 multiply helper. */
static inline u64 cycles_to_ns(const struct clocksource *cs, u64 cycles)
{
u64 ns;
/* mult is 32 bits, shift is at most 32: the product needs the wide form. */
ns = mul_u64_u32_shr(cycles, cs->mult, cs->shift);
return ns;
}
Two properties of this arithmetic matter downstream. First, the result is monotonic if and only if the counter is monotonic, because the multiply and shift are monotone. Second, the resolution is quantised: two cycle counts that differ by less than 1 << shift cycles can map to the same nanosecond value, so a source with a coarse counter cannot produce fine timestamps no matter how the conversion is arranged.
The same read function feeds sched_clock, which the scheduler uses for accounting and for the timestamps on trace events. That path has a stricter requirement than timekeeping: it must be callable from any context, including the scheduler itself while runqueue locks are held, so a source that needs a lock to read cannot be used for it. The kernel therefore maintains a separate sched_clock source, chosen for safety rather than accuracy, and the two may differ slightly.
Clock events and the programming interface
A clock event device is the other half. It has a set of supported modes, a function to program the next event, and a function to set the periodic interval. The modes describe what the hardware can do: one-shot, periodic, or both; absolute or relative programming; whether it stops in a low-power state.
The distinction between absolute and relative programming is not cosmetic. An absolute device is told “fire when the counter reaches this value”, which is immune to the delay between the decision to program and the write reaching the device. A relative device is told “fire this many cycles from now”, so any latency in the programming path shifts the expiry. Modern local APIC timers on x86 are programmed in the initial-count form, which is relative, and the kernel compensates by reading the current count when it needs an absolute deadline.
The broadcast problem is the awkward case. On x86, the local APIC timer stops when the CPU enters a deep idle state, because the entire clock domain is gated. A machine with such states needs a timer that keeps running, and the usual answer is an HPET or an ACPI power-management timer that is shared across the package. When a CPU in a deep state needs a wakeup, the kernel programs the shared device to fire, and the interrupt is broadcast to the sleeping CPU. The bookkeeping for this is why the clock event layer has an explicit notion of a broadcast device rather than treating every device as private.
The tick
The periodic tick is a clock event programmed to fire at a fixed interval, conventionally expressed in hertz and configured at build time. Each firing increments jiffies, updates the timekeeping state, runs expired timers, and gives the scheduler an opportunity to preempt. jiffies is an unsigned long, so it wraps; the kernel’s comparison macros handle wrap by comparing differences rather than values, and code that writes if (jiffies > deadline) is wrong for exactly that reason.
The tick is the reason jiffies is coarse. A 250 Hz tick gives a resolution of 4 ms, and a 1000 Hz tick gives 1 ms. Neither is adequate for a timer that needs to fire within a few hundred microseconds, which is why the high-resolution timer subsystem exists and why the tick can be disabled entirely on an idle CPU.
The tickless mode is the interesting design. When a CPU has nothing to do, there is no reason to wake it every few milliseconds just to discover that. The kernel therefore programs the next event to the earliest deadline that actually matters, which may be far in the future, and lets the CPU idle. The accounting that the tick provided, such as per-CPU time and load averages, is reconstructed from the timestamps on either side of the idle period.
The cost is complexity in every consumer of jiffies. Code that expects the tick to advance while a CPU is idle will observe jumps, because the value is brought forward in one step when the CPU wakes rather than in one-per-period increments. Whether such code keeps working depends on which tickless mode is in use and how long the CPU stayed idle, which is why the kernel’s guidance is that anything with a deadline should use a timer or a ktime comparison rather than polling jiffies, and why the conversion helpers exist to make that migration mechanical.
The ordering guarantee that timekeeping relies on
Timestamps must not go backwards on a monotonic clock, and this constrains how the timekeeping state is updated. The kernel reads the clock source and a snapshot of the timekeeping parameters together, and it sequences the update so that a reader on another CPU sees either the old pair or the new pair, never a mix. On 64-bit machines a single wide load is enough for some of it; on 32-bit machines the kernel uses a sequence counter and retries, which is why a timestamp read can loop.
The NTP correction is applied on top, as an offset and a frequency adjustment, and it is applied in a way that does not step the monotonic clock. A step is only applied to the real-time clock, and only when the correction exceeds a threshold; the monotonic clock is slewed. This is the reason CLOCK_MONOTONIC is the right choice for measuring intervals and CLOCK_REALTIME is the right choice for timestamping events that must be comparable across machines.
Where the two abstractions diverge
The clock source and the clock event device are usually the same physical timer, but they are rated separately and can be selected separately. A machine may use one device for timestamps and another for events, and the choice can change at runtime as devices are found unstable or as a CPU is hotplugged.
The divergence becomes visible in the failure modes. A clock source that jumps produces timestamps out of order and is demoted by the watchdog. A clock event device that fires late produces timer expiries that are late by the same amount, which shows up as scheduling latency rather than as a clock error. A device that fires early is worse, because a timer that is not yet due will be reprogrammed and the loop repeats, consuming CPU until the deadline actually arrives.
Both abstractions feed the timer subsystem, where the question changes from “what time is it” to “which of these thousands of deadlines is next”. That structure, and the reason a sorted list is the wrong answer, is the subject of Timer Wheels and the Cost of Finding the Next Expiry. The interrupt side of the same device, including what happens when the timer interrupt is delayed by another handler, is bounded by the mechanisms in Top Halves and Bottom Halves.
References
- ClockSources, ClockEvents, sched_clock() and DelayTimers — The Linux kernel documentation
- hrtimers and Beyond: Transforming the Linux Time Subsystems — Thomas Gleixner and Douglas Niehaus, Proceedings of the Linux Symposium 2006
- High Resolution Timers and Dynamic Ticks — The Linux kernel documentation