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:

C
// 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):

C

//... [truncated]
Last modified: 2024年8月16日

Author

Comments

Write a Reply or Comment

Your email address will not be published.