3. hrtimer 核心数据结构:struct hrtimer、struct hrtimer_clock_base、struct hrtimer_cpu_base
高精度定时器(hrtimer)的整个运行机制建立在三层核心数据结构之上。理解这三层结构之间的层级关系与字段含义,是编写可靠实时周期任务的基石。本章从最底层的定时器对象开始,逐层向上剖析,最终构建出完整的 per-CPU 定时器管理视图。
3.1 三层结构总览
hrtimer 子系统采用 “对象 → 时钟基 → CPU 上下文” 的三层架构。每个定时器对象(struct hrtimer)归属于一个时钟基(struct hrtimer_clock_base),而所有时钟基又统一由 per-CPU 的管理结构(struct hrtimer_cpu_base)组织。
设计哲学: 将定时器按时钟源分类管理,利用红黑树实现 O(log n) 的插入与删除,并通过 per-CPU 结构避免锁竞争,满足实时系统对确定性的严苛要求。
3.2 struct hrtimer:定时器对象
struct hrtimer 是驱动开发者直接操作的核心结构体。每个定时器实例代表一个即将到期的事件。
struct hrtimer {
struct timerqueue_node node; /* 红黑树节点,包含 expire 时间 */
ktime_t _softexpires; /* 软到期时间(允许的宽松边界) */
enum hrtimer_restart (*function)(struct hrtimer *); /* 回调函数 */
struct hrtimer_clock_base *base; /* 所属时钟基指针 */
u8 state; /* 状态位掩码 */
u8 is_rel; /* 是否为相对时间 */
u8 is_soft; /* 是否为软中断上下文 */
u8 is_hard; /* 是否为硬中断上下文 */
};
| 字段 | 说明 | 实时编程要点 |
|---|---|---|
| node | 红黑树节点,内含 expires 字段(绝对到期时间) | 内核通过该节点将定时器插入时钟基的红黑树 |
| _softexpires | 软到期时间,允许回调在 expires 之后触发 | 用于合并中断,减少唤醒次数;实时任务建议与 expires 一致 |
| function | 定时器到期时执行的回调函数 | 运行在中断上下文,必须快速、不可睡眠、不可持有锁 |
| base | 指向所属的 hrtimer_clock_base | 内核通过该指针定位时钟基和红黑树 |
| state | 定时器状态(HRTIMER_STATE_INACTIVE / ENQUEUED / CALLBACK 等) | 检查状态可判断定时器是否正在运行 |
关键约束: 回调函数
function 在 硬中断上下文 或 软中断上下文 中执行。禁止调用可能导致睡眠的 API(如 kmalloc(GFP_KERNEL)、mutex_lock())。回调的返回值决定定时器是否重新启动。
3.3 struct hrtimer_clock_base:时钟基管理
每个时钟基管理同一时钟源下的所有定时器。内核支持 4 种时钟基,分别对应不同的时间语义。
struct hrtimer_clock_base {
struct hrtimer_cpu_base *cpu_base; /* 所属 CPU 上下文 */
struct timerqueue_head active; /* 红黑树根节点(含缓存的最左节点) */
ktime_t (*get_time)(void); /* 获取当前时间的函数指针 */
ktime_t offset; /* 时钟偏移量(如 REALTIME 与 MONOTONIC 的差值) */
clockid_t clockid; /* 时钟 ID */
u8 index; /* 在 cpu_base->clock_base[] 中的索引 */
};
| 时钟基类型 | clockid | index | 特点 |
|---|---|---|---|
| CLOCK_MONOTONIC | 1 | 0 | 单调递增,不受系统时间跳变影响;实时任务首选 |
| CLOCK_REALTIME | 0 | 1 | 墙上时钟,可能因 NTP 或用户调整而跳变 |
| CLOCK_BOOTTIME | 7 | 2 | 类似 MONOTONIC,但包含系统挂起时间 |
| CLOCK_TAI | 11 | 3 | 国际原子时,用于精确时间同步协议 |
实时周期任务建议: 始终使用
CLOCK_MONOTONIC。它不受 NTP 时间调整影响,能保证定时器间隔的确定性。如果任务需要在系统挂起时继续计时,则选择 CLOCK_BOOTTIME。
3.4 struct hrtimer_cpu_base:Per-CPU 定时器上下文
每个 CPU 核心拥有一个独立的 hrtimer_cpu_base 实例,集中管理该 CPU 上的所有时钟基和定时器。这种 per-CPU 设计避免了多核间的锁竞争,是 hrtimer 高性能的关键。
struct hrtimer_cpu_base {
raw_spinlock_t lock; /* 保护本结构体的自旋锁 */
struct hrtimer_clock_base clock_base[HRTIMER_MAX_CLOCK_BASES]; /* 时钟基数组 */
unsigned int active_bases; /* 位掩码,标记哪些时钟基非空 */
unsigned int nr_events; /* 定时器到期事件计数 */
unsigned int nr_retries; /* 重试次数统计 */
unsigned int nr_hangs; /* 挂起次数统计 */
ktime_t expires_next; /* 下一个到期时间(用于编程硬件定时器) */
struct hrtimer *next_timer; /* 指向下一个到期的定时器 */
ktime_t softirq_expires_next; /* 软中断下一个到期时间 */
struct hrtimer *softirq_next_timer; /* 软中断下一个到期定时器 */
};
3.5 关键设计要点
- 红黑树组织: 每个时钟基的
active字段是一个struct timerqueue_head,它封装了红黑树根节点并缓存最左节点(即最早到期的定时器),实现 O(1) 获取下一个到期定时器。 - Per-CPU 无锁设计: 每个 CPU 操作自己的
hrtimer_cpu_base,仅在迁移定时器时涉及跨 CPU 锁。实时任务应绑定 CPU 以避免迁移开销。 - 软到期与硬到期:
_softexpires允许内核将多个定时器合并到同一个硬件中断,减少中断频率。对于需要严格周期性的实时任务,应将_softexpires设置为与expires相同。 - 状态机转换: 定时器状态在
INACTIVE → ENQUEUED → CALLBACK → INACTIVE之间转换。回调函数返回HRTIMER_RESTART时,内核自动重新将定时器插入红黑树。
驱动开发经验: 在编写 hrtimer 回调时,通过
hrtimer_cb_get_time() 获取当前时间,计算与 hrtimer->node.expires 的差值,可以精确测量调度延迟(jitter)。这是评估实时系统性能的常用手段。
3.6 数据结构关系总结
| 层级 | 结构体 | 职责 | 关键字段 |
|---|---|---|---|
| 对象层 | struct hrtimer | 表示一个定时器事件 | node, function, base, state |
| 时钟基管理层 | struct hrtimer_clock_base | 按时钟源分类管理定时器 | active, get_time, offset, clockid |
| CPU 上下文层 | struct hrtimer_cpu_base | per-CPU 全局定时器状态 | lock, clock_base[], expires_next |