Writing an OS Kernel from Scratch | Clocksource Framework — How Linux Picks the Best Clock Source Among TSC, HPET, and LAPIC
Have you ever encountered this strange phenomenon: On a multi-core machine, calling clock_gettime(CLOCK_MONOTONIC) twice in succession actually returns time that went backwards? Or in a virtual machine, the system time suddenly “jumps” and then slowly corrects itself?
Behind this, the Linux clock subsystem is at work. Linux doesn’t just use one kind of clock hardware; it abstracts a layer called the Clocksource framework, allowing different hardware on different platforms to provide services upward through a unified interface. Today, we’ll dissect this framework — how it works, how priorities are chosen, and how your programs benefit.
What Is Clocksource: Why Do We Need It
There are at least four kinds of clock hardware on the x86 platform:
| Hardware | Precision | Features | Drawbacks |
|---|---|---|---|
| TSC (Time Stamp Counter) | Nanosecond | CPU built-in, zero overhead, constant rate | Frequency changes (throttling), may be inaccurate in VMs |
| HPET (High Precision Event Timer) | ~100ns | Independent chip, physically stable | Access latency, difficult cross-core synchronization |
| LAPIC Timer | ~100ns | One per CPU, local interrupt | Depends on APIC clock, imprecise across cores |
| ACPI PM Timer | ~100ns | Physical time, not affected by power | Fixed frequency (3.579545MHz), average precision |
The question is: The user process only calls a single clock_gettime() — which clock should the kernel use?
The answer is: Don’t let the user decide; let the kernel choose for you. The Clocksource framework does exactly this — abstracting the underlying hardware into a unified “counter,” where the upper layers don’t ask “which hardware” but only “how many nanoseconds have passed.”
Architecture: The clocksource Structure
Linux uses a structure to describe each kind of clock hardware:
// include/linux/clocksource.h
struct clocksource {
u64 (*read)(struct clocksource *cs); // Read current counter value
u64 mask; // Counter bit width (e.g., ~0ULL means 64-bit)
u32 mult; // Multiplier for counter value → nanoseconds
u32 shift; // Right shift amount (mult/shift together determine precision)
u32 min_corr_ns; // Minimum correction amount (for slewing)
const char *name; // Clock source name, e.g., "tsc", "hpet"
struct list_head list; // Linked list node (all registered clocksources linked together)
enum clocksource_ids id; // CLOCK_SOURCE_{TSC,HPET,LAPIC,ACPI}
u32 rating; // Priority score (higher is better)
int (*enable)(struct clocksource *cs); // Optional: callback on enable
void (*disable)(struct clocksource *cs); // Optional: callback on disable
};Core logic: Each read calls read() to get a raw counter value (cycles), multiplies by mult, shifts right by shift bits, and obtains a nanosecond value. This process is entirely done by the kernel; the upper-layer code doesn’t know whether you’re using TSC or HPET.
The rating field is the core of the whole framework — each clocksource scores itself when registering, and the one with the highest score is selected.
Scoring Mechanism: Why TSC Always Wins
Scoring rules (arch/x86/kernel/tsc.c):
//... [truncated]
Comments