内核需要”过一会儿做某事”——TCP 重传、watchdog、调度器时间片。定时器是底层时间机制的核心。
一、先看图
flowchart TD
subgraph 低精度
WHEEL[Timer Wheel<br/>层次哈希<br/>jiffies 精度]
end
subgraph 高精度
HRTIMER[hrtimer<br/>红黑树<br/>纳秒精度]
end
WHEEL --> JIFFIES[基于 jiffies<br/>毫秒级]
HRTIMER --> CLOCKEV[clock_event_device<br/>硬件定时器]
classDef low fill:#f0883e22,stroke:#f0883e,color:#adbac7;
classDef high fill:#3fb95022,stroke:#3fb950,color:#adbac7;
class WHEEL,JIFFIES low
class HRTIMER,CLOCKEV high
二、Timer Wheel(低精度)
2.1 层次结构
struct timer_list {
struct hlist_node entry;
unsigned long expires; // jiffies
void (*function)(struct timer_list *);
};分层 wheel:
- Level 0:1 tick(1-4ms)
- Level 1:8 ticks
- Level 2:64 ticks
- …
越远的定时器放在越粗的层 → 到期时再细化。
2.2 使用
struct timer_list timer;
timer_setup(&timer, callback, 0);
mod_timer(&timer, jiffies + msecs_to_jiffies(100));
del_timer_sync(&timer);2.3 精度
jiffies 精度 = HZ 配置(100/250/1000 Hz)→ 1-10ms。
三、hrtimer(高精度)
3.1 数据结构
struct hrtimer {
struct timerqueue_node node; // 红黑树节点
ktime_t _softexpires; // 最早到期时间
enum hrtimer_restart (*function)(struct hrtimer *);
};3.2 使用
struct hrtimer timer;
hrtimer_init(&timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
timer.function = callback;
hrtimer_start(&timer, ktime_set(0, 100000), HRTIMER_MODE_REL); // 100μs3.3 精度
取决于硬件(TSC、LAPIC timer、ARM generic timer)→ 纳秒级。
四、clock_event_device
硬件定时器的抽象:
struct clock_event_device {
void (*event_handler)(struct clock_event_device *);
int (*set_next_event)(unsigned long, struct clock_event_device *);
// ...
};hrtimer 编程下一个到期时间到 clock_event_device → 硬件在到期时触发中断。
五、tick 与无 tick
5.1 周期 tick
每 HZ 次/秒产生 tick 中断 → 更新 jiffies、检查定时器、调度。
5.2 动态 tick(NO_HZ_IDLE)
空闲时跳过 tick → 省电。
5.3 全无 tick(NO_HZ_FULL)
运行中也跳过 tick → 减少干扰。
六、Slack 与 Coalescing
timer_setup(&timer, callback, 0);
timer.slack = msecs_to_jiffies(50); // 允许 50ms 偏差内核合并临近的定时器到期 → 减少唤醒次数 → 省电。
/proc/timer_stats(已废弃)→
perf tracepoints 替代。
七、定时器迁移
CPU 即将下线 → 迁移该 CPU 的定时器到其他 CPU。
NO_HZ_FULL:把非关键定时器迁移到 housekeeping CPU → 隔离 CPU 无 tick 干扰。
八、用户态定时器
| API | 精度 | 通知方式 |
|---|---|---|
| sleep/usleep | 低 | 阻塞 |
| nanosleep | 高 | 阻塞 |
| setitimer | 低 | 信号 |
| timer_create | 高 | 信号/线程 |
| timerfd_create | 高 | fd(可 epoll) |
推荐 timerfd → 与事件循环组合。
九、观察
cat /proc/timer_list # 所有定时器(巨大输出)
cat /proc/timer_list | head -50 # 活跃 hrtimer
perf stat -e timer:hrtimer_start -a sleep 1
bpftrace -e 'tracepoint:timer:hrtimer_start { @[kstack(3)] = count(); }'十、小结
- Timer wheel:低精度(ms)、大量超时场景(TCP、缓存过期)
- hrtimer:高精度(ns)、调度时间片、timerfd
- clock_event_device 是硬件定时器抽象
- NO_HZ 跳过 tick → 省电、隔离
- 定时器迁移支持 CPU 热插拔和隔离
参考文献
kernel/time/timer.c(wheel)kernel/time/hrtimer.cDocumentation/timers/- Thomas Gleixner, “hrtimers and beyond.” OLS 2006
工具
/proc/timer_listperftimer tracepoints- bpftrace
- timerfd_create(2)
文章导航
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
CPU 调度:CFS 的虚拟运行时间与 EEVDF 的虚拟截止期
从比例份额调度的论文谱系出发,对照 Linux v6.6 与 v6.12 fair.c 源码,解释 CFS 的 vruntime、EEVDF 的 lag/eligible/deadline,并用确定性模拟复现延迟与 lag 的差异。
I/O 调度:在寻道、队列深度与公平性之间取舍
从电梯算法到 blk-mq,解释 Linux I/O 调度器为何在 HDD 上排序、在共享设备上保公平、在 NVMe 上常选择 none,并用确定性模拟展示寻道距离与队列尾延迟的取舍。
用户态内存分配器:size class、线程缓存与碎片边界
从 Wilson 分配器综述到 jemalloc、gperftools tcmalloc 与 mimalloc 的源码路径,解释 size class、线程缓存、跨线程释放和 RSS 碎片;用可复现实验说明不能只凭 allocator 名字下结论。
伙伴系统与 SLUB:Linux 物理页和小对象分配的边界
以 Linux v6.6 源码和确定性模拟为准,解释伙伴系统的 split/coalesce、migratetype 反碎片策略,以及 SLUB 的 per-CPU freelist、partial list 与安全加固。