Linux hrtimer 纳秒精度从何而来:从 TSC 到红黑树的完整链路
在前面的 tokio 系列文章中,我们提到了 Linux 内核的 hrtimer 能为
epoll_wait(timeout)提供纳秒精度的定时中断。这个说法引出了一个追问:内核里的 hrtimer 如何实现纳秒级别的精度?
引言
“纳秒精度”这个表述很容易被误解。说 hrtimer 精度纳秒,到底是什么意思?
一个 nanosleep(&ts) 系统调用,传入的 timespec 确实是以纳秒为单位的。但内核真的能保证在恰好那个纳秒点唤醒你的进程吗?
答案是否定的。但”纳秒精度”也不是一个营销话术——它是内核在数据结构、时钟硬件和中断机制三层协作下,确实能达到的能力。理解这个协作过程,就能同时理解精度的来源和它的边界。
概念先行:精度来自三层协作
hrtimer 的纳秒精度是三层协作的结果:
- 数据结构层:hrtimer 的到期时间用
ktime_t(纳秒)表示,用红黑树(Red-Black Tree)按到期时间排序——这是”纳秒”作为数据精度的入口 - 时钟源层:CPU 内部的 TSC(Time Stamp Counter)在每个 cycle 递增——现代 CPU 上相邻两次递增的时间间隔在 0.3-0.5ns 之间,这是”纳秒”在硬件上能被读取到的来源
- 中断触发层: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_MONOTONIC、CLOCK_REALTIME、CLOCK_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_base 中3:
// 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 上这些问题已经解决:
- Invariant TSC:所有内核的 TSC 在启动时同步,之后不受频率变化(P-state、C-state)影响
- ARAT(Always Running APIC Timer):即使 CPU 进入 C-state 休眠,TSC 仍然递增
- TSC_ADJUST MSR:允许管理程序修正不同 CPU 核之间的 TSC 偏差
三、中断触发层:从 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_event)9:
// 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_deadline)9:
// 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 的”纳秒精度”和”实际中断是否能准时在纳秒级触发”是两回事。
- 精度(precision):hrtimer 的
ktime_t确实是纳秒粒度的,红黑树按到期时间排序,比较的单位就是纳秒。时钟源(TSC)读出的值分辨率也在亚纳秒级。这是数据层面的精度。 - 准确性(accuracy):中断实际触发时间到理论到期时间的偏差,受中断延迟(interrupt latency)影响。
中断延迟的来源:
理想路径:
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 的纳秒精度来自三个层次:
- 数据结构层(纳秒的”意愿”):
ktime_t以纳秒为单位存储和比较到期时间,红黑树 O(log n) 管理 - 时钟源层(纳秒的”眼见”):TSC 以 ~0.3ns 的粒度提供当前时间读数
- 中断触发层(纳秒的”手到”):TSC Deadline 模式写入绝对 TSC 值,硬件在确切时刻触发中断
三层叠加,实现了 clock_nanosleep() 在数据层面、读时层面、触发层面均能达到纳秒级精度。但实际触发时间受关中断影响,在普通内核上通常有 1-10μs 的偏差——这是准确性受限,而非精度受限。
hrtimer 和传统 timer_list(jiffies 时间轮)的区别,不仅仅在于分辨率从毫秒级降到纳秒级——更在于hrtimer 的触发完全不依赖 tick,它按需编程时钟事件设备,在 NOHZ 模式下实现了真正的 tickless 空闲。如果把内核时钟系统比作一个厨房:
- jiffies 时间轮像一个每隔固定时间响一次的闹钟——无论有没有事,它都响
- hrtimer像一个贴在墙上的倒计时器列表——每个 timer 指定了精确的到期时间,CPU 按需设置最近的一个,到期后响一次,然后设置下一个最近的一个。没事的时候,所有计时器静默,CPU 可以安心睡觉
References
-
Linux 内核源码,
ktime_t类型定义,include/linux/ktime.h。ktime_t定义为s64,单位纳秒。64 位有符号纳秒可以表示约 ±292 年的时间范围。 ↩ -
Linux 内核源码,hrtimer 核心实现,
kernel/time/hrtimer.c。HIGH_RES_NSEC = 1表示在高精度模式下分辨率可达 1 纳秒。hrtimer_interrupt()是 hrtimer 中断处理函数,在单个中断中遍历所有到期 hrtimer,支持最多 3 次重试防止 livelock。 ↩ ↩2 -
Linux 内核源码,hrtimer 数据结构定义,
include/linux/hrtimer_defs.h。定义了hrtimer_cpu_base(包含 8 个 clock base、最早到期时间缓存、next_timer指针等)和hrtimer_clock_base(红黑树根节点active、expires_next缓存)。 ↩ ↩2 -
Linux 内核源码,hrtimer 入队和软到期机制,
kernel/time/hrtimer.c。enqueue_hrtimer()实现红黑树插入,timerqueue_linked_add返回 true 表示新 timer 成为最左节点。__hrtimer_run_queues()使用softexpires判断——允许在软到期和硬到期之间优化合并中断。 ↩ ↩2 -
Linux 内核源码,hrtimer 重编程流程,
kernel/time/hrtimer.c。hrtimer_reprogram()和__hrtimer_reprogram()组成了从红黑树到硬件编程的桥梁:比较新到期时间和cpu_base->expires_next,只在更早时触发硬件重编程。 ↩ -
Linux 内核源码,
hrtimer_rearm_event()和tick_program_event调用链,kernel/time/hrtimer.c第 700-708 行,以及kernel/time/tick-oneshot.c。hrtimer_rearm_event→tick_program_event→clockevents_program_event,这是 hrtimer 框架到硬件时钟事件设备的统一出口。 ↩ ↩2 -
Linux 内核源码,
__hrtimer_run_queues的遍历逻辑,kernel/time/hrtimer.c第 1968-1992 行。使用for_each_active_base宏遍历有活跃 timer 的 clock base,对每个 base 从红黑树最左节点开始收割,直到遇到尚未到期的 timer。 ↩ -
Linux 内核源码,clocksource 抽象层,
include/linux/clocksource.h。struct clocksource定义只读时钟源接口,包含read()、mult/shift、rating等字段。内核通过rating择优选择最佳时钟源。 ↩ -
Linux 内核源码,x86 LAPIC 定时器实现,
arch/x86/kernel/apic/apic.c。lapic_clockevent定义 LAPIC 时钟事件设备。lapic_next_event和lapic_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 -
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 行。 ↩ -
Linux 内核源码,
clockevents_program_event实现,kernel/time/clockevents.c第 332-390 行。支持CLOCK_EVT_FEAT_CLOCKSOURCE_COUPLED标志——设置了此标志的设备走ktime_t→ TSC 直接路径,跳过 mult/shift 换算。 ↩