Linux hrtimer 纳秒精度从何而来:从 TSC 到红黑树的完整链路

在前面的 tokio 系列文章中,我们提到了 Linux 内核的 hrtimer 能为 epoll_wait(timeout) 提供纳秒精度的定时中断。这个说法引出了一个追问:内核里的 hrtimer 如何实现纳秒级别的精度?

引言

“纳秒精度”这个表述很容易被误解。说 hrtimer 精度纳秒,到底是什么意思?

一个 nanosleep(&ts) 系统调用,传入的 timespec 确实是以纳秒为单位的。但内核真的能保证在恰好那个纳秒点唤醒你的进程吗?

答案是否定的。但”纳秒精度”也不是一个营销话术——它是内核在数据结构、时钟硬件和中断机制三层协作下,确实能达到的能力。理解这个协作过程,就能同时理解精度的来源和它的边界。

概念先行:精度来自三层协作

hrtimer 的纳秒精度是三层协作的结果:

  1. 数据结构层:hrtimer 的到期时间用 ktime_t(纳秒)表示,用红黑树(Red-Black Tree)按到期时间排序——这是”纳秒”作为数据精度的入口
  2. 时钟源层:CPU 内部的 TSC(Time Stamp Counter)在每个 cycle 递增——现代 CPU 上相邻两次递增的时间间隔在 0.3-0.5ns 之间,这是”纳秒”在硬件上能被读取到的来源
  3. 中断触发层:LAPIC 的 TSC Deadline 模式允许内核写入一个绝对 TSC 值,硬件在该 TSC 值到达时直接触发中断——这是”纳秒”的硬实时出口

把这三层串起来,就是 hrtimer 从用户注册到期时间到实际触发中断的完整链路:

用户态: clock_nanosleep(&ts)  // ts = 100ns
    ↓ 系统调用
内核态: ktime_t expires = timespec_to_ktime(ts)  // 纳秒存为 ktime_t
    ↓ 插入红黑树
       hrtimer 红黑树按 expires 排序
    ↓ 如果是当前最早到期
       hrtimer_reprogram() → tick_program_event(expires)
    ↓ 转到时钟事件设备
       clockevents_program_event(dev, expires, force)
    ↓ TSC Deadline 模式
       lapic_next_deadline():
         tsc = rdtsc()
         native_wrmsrq(MSR_IA32_TSC_DEADLINE, tsc + delta)
    ↓ 硬件层
       LAPIC 监视 TSC 寄存器,到达指定值时触发中断

从这条链路可以看到,”纳秒精度”是数据结构精度(ktime_t)、硬件读时精度(TSC)、和中断编程精度(TSC Deadline)三者叠加的结果。下面逐层展开。

一、数据结构层:hrtimer 的红黑树

1.1 ktime_t:纳秒级的时间表示

hrtimer 的核心数据结构没有使用内核传统的 jiffies(基于定时器中断周期,通常是 1ms/4ms),而是使用 ktime_t——一个专门设计的高精度时间类型1

// include/linux/ktime.h
typedef s64 ktime_t;

ktime_t 就是一个 64 位有符号整数,单位是纳秒。取值范围从 -292 年+292 年。所有 hrtimer 的到期时间、剩余时间、间隔值都以此为单位。

这也直接体现在 hrtimer 源码顶部的宏定义上2

// kernel/time/hrtimer.c: 61-62
// HIGH_RES_NSEC = 1 表示 hrtimer 在高精度模式下分辨率为 1ns
#define HIGH_RES_NSEC		1

对比传统 timer_list(基于 jiffies 的时间轮),hrtimer 的优势在于”到期时间”和”现在时间”的比较不需要经过 jiffies 这个中间层:

方面 传统 timer_list hrtimer
时间单位 jiffies(~4ms @ 250Hz) ktime_t(1ns)
排序结构 多级时间轮(O(1) 但粒度粗) 红黑树(O(log n) 但粒度细)
中断编程 等下一个 tick 精确到硬件允许的任意时刻
精度来源 定时器中断的周期 TSC + 时钟事件设备

1.2 红黑树:按到期时间排序

每个 CPU 有 8 个 hrtimer_clock_base,分别对应不同的时钟源(CLOCK_MONOTONICCLOCK_REALTIMECLOCK_BOOTTIME 等,每个时钟分 hard 和 soft 两种模式)。每个 base 内部用一棵红黑树管理所有活跃的 hrtimer3

// include/linux/hrtimer_defs.h: 28-43
struct hrtimer_clock_base {
    struct hrtimer_cpu_base    *cpu_base;
    const unsigned int         index;
    const clockid_t            clockid;
    seqcount_raw_spinlock_t    seq;
    ktime_t                    expires_next;   // 此 base 最早到期时间
    struct hrtimer             *running;       // 正在执行回调的 timer
    struct timerqueue_linked_head  active;     // 红黑树根节点
    ktime_t                    offset;         // 相对于 MONOTONIC 的偏移
};

每个 CPU 的 8 个 clock base 打包在 hrtimer_cpu_base3

// include/linux/hrtimer_defs.h: 82-108
struct hrtimer_cpu_base {
    raw_spinlock_t              lock;
    unsigned int                cpu;
    unsigned int                active_bases;     // 哪些 base 有活跃 timer
    bool                        hres_active;      // 高精度模式已启用
    // ...
    ktime_t                     expires_next;     // 本 CPU 最早到期时间
    struct hrtimer              *next_timer;      // 最早到期的 timer 指针
    struct hrtimer_clock_base   clock_base[HRTIMER_MAX_CLOCK_BASES];  // 8 个 base
};

当一个 hrtimer 被插入时,它被放入对应 clock base 的红黑树,按 expires(到期时间)排序。插入操作的时间复杂度是 O(log n)4

// kernel/time/hrtimer.c: 640-660
static bool enqueue_hrtimer(struct hrtimer *timer, struct hrtimer_clock_base *base,
                            enum hrtimer_mode mode, bool was_armed)
{
    base->cpu_base->active_bases |= 1 << base->index;
    WRITE_ONCE(timer->is_queued, HRTIMER_STATE_ENQUEUED);

    // 如果新插入的 timer 是当前 base 中最左的(最早到期),返回 true
    if (!timerqueue_linked_add(&base->active, &timer->node))
        return false;

    base->expires_next = hrtimer_get_expires(timer);
    return true;
}

timerqueue_linked_add 返回 true 表示新插入的节点成为了红黑树的最左节点——也就是最早到期的那个。这种情况下,内核需要重新编程时钟事件设备。

1.3 什么时候编程硬件?

从红黑树到硬件只差一步:每插入一个 timer,都检查它是不是当前最早的;如果是,重新编程时钟事件设备5

// kernel/time/hrtimer.c: 732-760
static void hrtimer_reprogram(struct hrtimer *timer, bool reprogram)
{
    struct hrtimer_cpu_base *cpu_base = this_cpu_ptr(&hrtimer_bases);
    ktime_t expires = hrtimer_get_expires(timer);
    expires = ktime_sub(expires, base->offset);
    // ...

    // 如果到期时间在当前最早之后,什么都不做
    if (expires >= cpu_base->expires_next)
        return;

    cpu_base->next_timer = timer;
    __hrtimer_reprogram(cpu_base, timer, expires);
}

__hrtimer_reprogram 最终调用 tick_program_event(expires_next, 1),进入时钟事件设备层6

// kernel/time/hrtimer.c: 700-708
static inline void hrtimer_rearm_event(ktime_t expires_next, bool deferred)
{
    trace_hrtimer_rearm(expires_next, deferred);
    tick_program_event(expires_next, 1);
}

1.4 中断到来后:批量收割

当硬件中断到来时,hrtimer_interrupt() 被调用。它遍历红黑树,收割所有已到期的 timer,执行回调2

// kernel/time/hrtimer.c: 2083-2138
void hrtimer_interrupt(struct clock_event_device *dev)
{
    struct hrtimer_cpu_base *cpu_base = this_cpu_ptr(&hrtimer_bases);

    raw_spin_lock_irqsave(&cpu_base->lock, flags);
    entry_time = now = hrtimer_update_base(cpu_base);

retry:
    // 将最早到期时间设为 KTIME_MAX(阻止远程 CPU 插入新 timer)
    cpu_base->expires_next = KTIME_MAX;

    // 收割所有到期的 hard hrtimer
    __hrtimer_run_queues(cpu_base, now, flags, HRTIMER_ACTIVE_HARD);

    // 重新计算下一个到期时间
    now = hrtimer_update_base(cpu_base);
    expires_next = hrtimer_update_next_event(cpu_base);

    // 如果还有 timer 已到期(更新 now 后发现的),重试
    if (expires_next < now) {
        if (++retries < 3)
            goto retry;
        // 超过 3 次仍到期 → 判定为 hang,设定 max_hang_time
        cpu_base->hang_detected = true;
    }

    // 重新编程硬件到下一个到期时间
    hrtimer_interrupt_rearm(cpu_base, expires_next);
    raw_spin_unlock_irqrestore(&cpu_base->lock, flags);
}

__hrtimer_run_queues 遍历每个有活跃 timer 的 clock base,从红黑树中取出第一个到期时间在 basenow 之前的 timer,执行其回调函数,然后取下下一个,直到遇到还没到期的为止7

// kernel/time/hrtimer.c: 1968-1992
static void __hrtimer_run_queues(struct hrtimer_cpu_base *cpu_base, ktime_t now,
                                 unsigned long flags, unsigned int active_mask)
{
    unsigned int active = cpu_base->active_bases & active_mask;

    for_each_active_base(base, cpu_base, active) {
        ktime_t basenow = ktime_add(now, base->offset);
        struct hrtimer *timer;

        while ((timer = clock_base_next_timer(base))) {
            if (basenow < hrtimer_get_softexpires(timer))
                break;
            __run_hrtimer(cpu_base, base, timer, basenow, flags);
        }
    }
}

遍历从 Red-Black Tree 取最左节点(最早到期),如果到期时间(softexpires)还在 basenow 之后则停止。注意这里用了 softexpires(soft expiration)而不是硬到期时间——这是 hrtimer 的”软到期”特性,允许 timer 在软到期后、硬到期前的任意时刻触发,目的是减少中断次数,将多个接近到期的 timer 合并为一次中断4

二、硬件时钟源层:TSC 如何提供纳秒级读数

数据结构层用 ktime_t(纳秒)做比较和存储。但内核从哪里拿到”当前纳秒时间”?答案是时钟源(clocksource)层。

2.1 clocksource 抽象

内核有一个 clocksource 抽象层,每种计时硬件注册为一个 clocksource 实例,启动时自动选择精度最高的那个8

// include/linux/clocksource.h: 35-135
struct clocksource {
    u64     (*read)(struct clocksource *cs);  // 读取当前计数值
    u64     mask;                              // 计数值的位掩码
    u32     mult;                              // 计数值 → 纳秒 的乘数
    u32     shift;                             // 计数值 → 纳秒 的位移
    u64     max_idle_ns;                       // 最大无中断安全闲置时间
    int     rating;                            // 精度评级(选源依据)
    // ...
};

每种平台的可用时钟源不同,内核按 rating 择优选取:

时钟源 典型分辨率 评级 (rating) 平台
TSC ~0.3ns (按 GHz 频率) 250-300 x86
ARM Generic Timer ~0.5ns 250-300 ARM/ARM64
HPET ~100ns (10MHz) 50-100 x86(备选)
ACPI PM ~280ns (3.58MHz) 50 所有(fallback)
jiffies 4ms (250Hz) 1 最后回退

现代 x86 平台上 TSC 是默认首选。一个 3GHz 的 CPU 上,TSC 每 0.33ns 递增一次。

2.2 TSC 的演进:为什么它可信

早期 TSC 有不稳定的问题(不同步、频率可变)。现代 CPU 上这些问题已经解决:

三、中断触发层:从 TSC 到硬件中断

有了 ktime_t 做数据结构、TSC 做纳秒级读数,最后一步是:”如何在恰好那个纳秒点触发中断?”

3.1 时钟事件设备(clock_event_device)

clocksource(只读,告诉内核”现在几点”)配对的是一套 clock_event_device 层(可写,告诉硬件”什么时候触发中断”)。每个 CPU 有一个本地时钟事件设备,在 x86 上就是 Local APIC Timer9

// arch/x86/kernel/apic/apic.c: 495-509
static struct clock_event_device lapic_clockevent = {
    .name               = "lapic",
    .features           = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
    .shift              = 32,
    .set_state_shutdown = lapic_timer_shutdown,
    .set_state_oneshot  = lapic_timer_set_oneshot,
    .set_next_event     = lapic_next_event,       // 传统模式
    .rating             = 100,
};

3.2 传统模式 vs TSC Deadline 模式

这里有一个关键的区别,决定了 hrtimer 精度的登顶:

传统 LAPIC 模式lapic_next_event9

// arch/x86/kernel/apic/apic.c: 415-419
static int lapic_next_event(unsigned long delta, struct clock_event_device *evt)
{
    apic_write(APIC_TMICT, delta);  // 写入 cycle 数,递减到 0 时中断
    return 0;
}

向 LAPIC 的 TMICT 寄存器写入一个相对值,LAPIC 以总线时钟频率递减,到 0 时触发中断。这里有一个重要限制:写入的值需要换算成 APIC 总线周期,而 APIC 总线频率和 CPU 频率不同,存在换算误差。

TSC Deadline 模式lapic_next_deadline9

// arch/x86/kernel/apic/apic.c: 421-428
static int lapic_next_deadline(unsigned long delta, struct clock_event_device *evt)
{
    u64 tsc = rdtsc();       // 读当前 TSC 值
    native_wrmsrq(MSR_IA32_TSC_DEADLINE, tsc + (((u64) delta) * TSC_DIVISOR));
    return 0;
}

MSR_IA32_TSC_DEADLINE 写入一个绝对 TSC 值。LAPIC 内部监视 TSC 寄存器,当 TSC 到达该值时直接触发 LVTT 定时器中断。

TSC Deadline 模式的优势:

方面 传统模式(TMICT) TSC Deadline 模式
写入值 相对值(递减计数) 绝对值(TSC 比较)
换算 需要从 ns → bus cycle 直接从 TSC 选未来值
精度 受 APIC 总线频率限制 等同于 TSC 精度
累积误差 逐次积累 无累积(每次写绝对值)
需要功能 通用 LAPIC X86_FEATURE_TSC_DEADLINE_TIMER

TSC Deadline 模式在 CPUID 中通过 X86_FEATURE_TSC_DEADLINE_TIMER 标识10

// arch/x86/include/asm/cpufeatures.h: 129
#define X86_FEATURE_TSC_DEADLINE_TIMER  ( 4*32+24) /* "tsc_deadline_timer" */

内核启用它时,把 LAPIC 时钟事件设备的 set_next_event 指针替换为 lapic_next_deadline9

// arch/x86/kernel/apic/apic.c: 581-598
static void setup_APIC_timer(void)
{
    // ...
    if (this_cpu_has(X86_FEATURE_TSC_DEADLINE_TIMER)) {
        levt->name = "lapic-deadline";
        levt->features |= CLOCK_EVT_FEAT_CLOCKSOURCE_COUPLED;
        levt->cs_id = CSID_X86_TSC;
        levt->set_next_event = lapic_next_deadline;  // ← 关键替换
        clockevents_config_and_register(
            levt, tsc_khz * (1000 / TSC_DIVISOR), 0xF, ~0UL);
    } else {
        clockevents_register_device(levt);  // 传统模式
    }
}

CLOCK_EVT_FEAT_CLOCKSOURCE_COUPLED 表示这个时钟事件设备”绑定”到了 TSC 时钟源。clockevents_program_event 识别到这个标志后,走 ktime_t → TSC 的直接转换路径,跳过中间换算11

// kernel/time/clockevents.c: 360-380
int clockevents_program_event(struct clock_event_device *dev, ktime_t expires, bool force)
{
    // ...
    // 如果设备支持 COUPLED 模式,走 ktime → TSC 直接路径
    if (likely(clockevent_set_next_coupled(dev, expires)))
        return 0;

    // 否则:传统路径,ktime → delta ns → 换算 → 写入
    delta = ktime_to_ns(ktime_sub(expires, ktime_get()));
    cycles = ((u64)delta * dev->mult) >> dev->shift;
    dev->set_next_event((unsigned long) cycles, dev);
}

传统路径多了一步从 delta-ns 到设备 tick 的换算(mult/shift),可能引入误差。Coupled 路径直接使用 TSC 值,无换算。

当然,使用 lapic_next_deadline 也有注意事项:由于历史原因,一些 CPU 型号(如部分 Haswell/Broadwell/Skylake X)存在 TSC Deadline 的 bug,需要在特定 microcode 版本后才能启用。内核维护了一张 bug 型号表9

// arch/x86/kernel/apic/apic.c: 514-545
static const struct x86_cpu_id deadline_match[] __initconst = {
    X86_MATCH_VFM_STEPS(INTEL_HASWELL_X,   0x2, 0x2, 0x3a),
    X86_MATCH_VFM_STEPS(INTEL_BROADWELL_D, 0x5, 0x5, 0x0e000003),
    X86_MATCH_VFM_STEPS(INTEL_SKYLAKE_X,   0x5, 0xf, 0),
    // ...
};

如果 microcode 不满足,内核会自动禁用 TSC Deadline 模式,回退到传统 LAPIC 定时器。

3.3 完整链路图

将三层串起来,hrtimer 从注册到触发的完整链路是:

sequenceDiagram
    participant User as 用户程序
    participant HR as hrtimer 框架
    participant RB as 红黑树
    participant CE as 时钟事件设备
    participant LAPIC as LAPIC (TSC Deadline)
    participant TSC as TSC 寄存器

    User->>HR: clock_nanosleep(100ns)
    HR->>HR: ktime_t expires = now + 100
    HR->>RB: 插入红黑树 (O(log n))
    Note over RB: 如果是最早到期
    HR->>CE: hrtimer_reprogram(expires)
    CE->>CE: clockevents_program_event(expires)
    CE->>LAPIC: lapic_next_deadline(delta)
    LAPIC->>TSC: rdtsc() + delta → MSR_IA32_TSC_DEADLINE
    Note over LAPIC,TSC: 硬件监视 TSC<br/>到达目标值时触发中断
    TSC-->>LAPIC: TSC == deadline
    LAPIC-->>CE: LVTT 定时器中断
    CE-->>HR: hrtimer_interrupt()
    HR->>RB: 收割到期 timer
    RB-->>HR: 回调函数
    HR-->>User: 进程被唤醒

四、精度 vs 准确性:一个重要的区分

让很多人困惑的一件事是:hrtimer 的”纳秒精度”和”实际中断是否能准时在纳秒级触发”是两回事。

中断延迟的来源:

理想路径:
  t=0ns:    注册 hrtimer(deadline = t + 100ns)
  t=100ns:  TSC deadline 到达 → LAPIC 拉高 IRQ 线 → CPU 响应 → handler

实际路径:
  t=0ns:    注册 hrtimer(deadline = t + 100ns)
  t=50ns:   CPU 进入 spin_lock_irqsave 临界区(关中断)
  t=100ns:  TSC deadline 到达 → LAPIC 拉高 IRQ 线
            但 CPU 关中断 → IRQ 在 CPU 的 INTR 引脚等待
  t=300ns:  退出临界区 → 开中断 → CPU 从 IDT 读取中断向量
            → 跳转到 hrtimer_interrupt → handler 执行
            晚了 200ns

这个偏差在普通 Linux 内核上通常在 1-10μs 级别,在 PREEMPT_RT(实时内核)上可以压到亚微秒级。

但即便是普通内核,hrtimer 和老的时间轮(timer_list)也有本质区别:

传统 timer_list (jiffies):
  到期间隔: t=0 注册 5ms 的 timer
  下一个 jiffies tick 在 3ms 后
  再下一个 jiffies tick 在 7ms 后
  → 实际触发在 7ms,偏差 ~2ms

hrtimer (TSC Deadline):
  到期间隔: t=0 注册 5ms 的 hrtimer
  → TSC deadline 写入 t + 5ms 对应的 TSC 值
  → 即使关中断导致延迟,但 5ms+300ns 比 7ms 精确得多
  → 且 hrtimer 不会因为 jiffies 的周期而被延迟

五、NOHZ + hrtimer:为什么 tickless 需要 hrtimer

hrtimer 的纳秒精度还有一个关键的应用场景:tickless 模式(NOHZ)

在没有 NOHZ 的传统内核上,即使 CPU 空闲,每个 tick(1ms/4ms)都会触发一次定时器中断,唤醒 CPU 检查是否需要调度。这浪费了功耗。

在 NOHZ 模式下(桌面和服务器内核默认启用),空闲 CPU 可以完全关闭 tick 中断,靠 hrtimer 在真正有事情需要做时才唤醒它。

这个过程是这样的6

CPU 进入 idle:
  1. 检查所有 hrtimer 的下一个到期时间
  2. 如果没有任何即将到期的 hrtimer → 无限期 idle(可被外部中断唤醒)
  3. 如果有 hrtimer 在 50ms 后到期
     → tick_program_event(50ms 后的 TSC)
     → LAPIC 在 TSC deadline 触发时唤醒 CPU

CPU 从 idle 被 hrtimer 中断唤醒:
  1. hrtimer_interrupt() 收割到期的 hrtimer
  2. 重新编程下一个到期时间
  3. 执行可能由于被阻塞的 RCU 回调等
  4. 如果没有更多工作,再次进入 idle
// kernel/time/tick-sched.c: 892
// NOHZ 模式下,tick 被 hrtimer 替代
tick_program_event(expires, 1);

这里 hrtimer 替代了传统的 tick 定时器,实现了”没有工作时完全不打扰 CPU”的效果。

总结

回到最初的问题:hrtimer 的纳秒精度来自三个层次:

  1. 数据结构层(纳秒的”意愿”):ktime_t 以纳秒为单位存储和比较到期时间,红黑树 O(log n) 管理
  2. 时钟源层(纳秒的”眼见”):TSC 以 ~0.3ns 的粒度提供当前时间读数
  3. 中断触发层(纳秒的”手到”):TSC Deadline 模式写入绝对 TSC 值,硬件在确切时刻触发中断

三层叠加,实现了 clock_nanosleep() 在数据层面、读时层面、触发层面均能达到纳秒级精度。但实际触发时间受关中断影响,在普通内核上通常有 1-10μs 的偏差——这是准确性受限,而非精度受限。

hrtimer 和传统 timer_list(jiffies 时间轮)的区别,不仅仅在于分辨率从毫秒级降到纳秒级——更在于hrtimer 的触发完全不依赖 tick,它按需编程时钟事件设备,在 NOHZ 模式下实现了真正的 tickless 空闲。如果把内核时钟系统比作一个厨房:

References

  1. Linux 内核源码,ktime_t 类型定义,include/linux/ktime.hktime_t 定义为 s64,单位纳秒。64 位有符号纳秒可以表示约 ±292 年的时间范围。 

  2. Linux 内核源码,hrtimer 核心实现,kernel/time/hrtimer.cHIGH_RES_NSEC = 1 表示在高精度模式下分辨率可达 1 纳秒。hrtimer_interrupt() 是 hrtimer 中断处理函数,在单个中断中遍历所有到期 hrtimer,支持最多 3 次重试防止 livelock。  2

  3. Linux 内核源码,hrtimer 数据结构定义,include/linux/hrtimer_defs.h。定义了 hrtimer_cpu_base(包含 8 个 clock base、最早到期时间缓存、next_timer 指针等)和 hrtimer_clock_base(红黑树根节点 activeexpires_next 缓存)。  2

  4. Linux 内核源码,hrtimer 入队和软到期机制,kernel/time/hrtimer.cenqueue_hrtimer() 实现红黑树插入,timerqueue_linked_add 返回 true 表示新 timer 成为最左节点。__hrtimer_run_queues() 使用 softexpires 判断——允许在软到期和硬到期之间优化合并中断。  2

  5. Linux 内核源码,hrtimer 重编程流程,kernel/time/hrtimer.chrtimer_reprogram()__hrtimer_reprogram() 组成了从红黑树到硬件编程的桥梁:比较新到期时间和 cpu_base->expires_next,只在更早时触发硬件重编程。 

  6. Linux 内核源码,hrtimer_rearm_event()tick_program_event 调用链,kernel/time/hrtimer.c 第 700-708 行,以及 kernel/time/tick-oneshot.chrtimer_rearm_eventtick_program_eventclockevents_program_event,这是 hrtimer 框架到硬件时钟事件设备的统一出口。  2

  7. Linux 内核源码,__hrtimer_run_queues 的遍历逻辑,kernel/time/hrtimer.c 第 1968-1992 行。使用 for_each_active_base 宏遍历有活跃 timer 的 clock base,对每个 base 从红黑树最左节点开始收割,直到遇到尚未到期的 timer。 

  8. Linux 内核源码,clocksource 抽象层,include/linux/clocksource.hstruct clocksource 定义只读时钟源接口,包含 read()mult/shiftrating 等字段。内核通过 rating 择优选择最佳时钟源。 

  9. Linux 内核源码,x86 LAPIC 定时器实现,arch/x86/kernel/apic/apic.clapic_clockevent 定义 LAPIC 时钟事件设备。lapic_next_eventlapic_next_deadline 分别实现传统模式(APIC_TMICT 相对值)和 TSC Deadline 模式(MSR_IA32_TSC_DEADLINE 绝对值)。setup_APIC_timer() 在支持 TSC Deadline 时替换 set_next_event 指针。deadline_match[] 存储有 bug 的 CPU 型号列表,microcode 不满足时自动禁用 TSC Deadline。  2 3 4 5

  10. Linux 内核源码,X86_FEATURE_TSC_DEADLINE_TIMER 定义,arch/x86/include/asm/cpufeatures.h 第 129 行。MSR_IA32_TSC_DEADLINE 的 MSR 地址为 0x6E0,定义在 arch/x86/include/asm/msr-index.h 第 1119 行。 

  11. Linux 内核源码,clockevents_program_event 实现,kernel/time/clockevents.c 第 332-390 行。支持 CLOCK_EVT_FEAT_CLOCKSOURCE_COUPLED 标志——设置了此标志的设备走 ktime_t → TSC 直接路径,跳过 mult/shift 换算。 

My Github Page: https://github.com/liweinan

B站视频: https://space.bilibili.com/21947620

Powered by Jekyll and Theme by solid

If you have any question want to ask or find bugs regarding with my blog posts, please report it here:
https://github.com/liweinan/liweinan.github.io/issues