4. hrtimer初始化与设置
高精度定时器(hrtimer)在使用前必须经过严格的初始化与配置流程。内核提供了两套初始化接口:传统的 hrtimer_init() 与较新的 hrtimer_setup(),后者在 Linux 5.x 之后逐步引入,简化了回调函数与模式的绑定。本章将深入剖析这两个接口的差异、回调函数的 cb_mode 枚举含义,以及初始化阶段的常见陷阱。
版本提示: 本课程基于 Linux 6.x 内核。若使用 5.10 以下内核,请优先使用
hrtimer_init() + 手动赋值 function 字段的方式。
4.1 hrtimer_init():传统初始化接口
hrtimer_init() 是内核长期使用的标准初始化函数,负责将定时器对象置零并设置时钟基准与模式。其原型定义在 <linux/hrtimer.h>:
void hrtimer_init(struct hrtimer *timer, clockid_t clock_id, enum hrtimer_mode mode);
| 参数 | 类型 | 说明 |
|---|---|---|
timer |
struct hrtimer * |
指向待初始化的 hrtimer 结构体实例 |
clock_id |
clockid_t |
时钟基准,常用 CLOCK_MONOTONIC 或 CLOCK_REALTIME |
mode |
enum hrtimer_mode |
绝对时间 HRTIMER_MODE_ABS 或相对时间 HRTIMER_MODE_REL |
初始化完成后,必须手动赋值回调函数指针 timer->function,否则定时器触发时将调用空指针导致内核崩溃。
struct hrtimer my_timer;
hrtimer_init(&my_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
my_timer.function = my_timer_callback; // 必须显式赋值
常见错误: 忘记赋值
function 字段。内核不会为 hrtimer 提供默认回调,遗漏将导致 oops 或 NULL 指针解引用。
4.2 hrtimer_setup():现代统一接口
从 Linux 5.11 开始,内核引入了 hrtimer_setup(),将初始化、回调绑定、模式设置合并为一次调用,减少遗漏风险:
void hrtimer_setup(struct hrtimer *timer,
void (*callback)(struct hrtimer *),
clockid_t clock_id,
enum hrtimer_mode mode);
| 参数 | 类型 | 说明 |
|---|---|---|
timer |
struct hrtimer * |
待初始化的 hrtimer 实例 |
callback |
void (*)(struct hrtimer *) |
回调函数指针,不可为 NULL |
clock_id |
clockid_t |
时钟基准,同 hrtimer_init() |
mode |
enum hrtimer_mode |
绝对/相对模式 |
struct hrtimer my_timer;
hrtimer_setup(&my_timer, my_timer_callback, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
// 无需再手动赋值 function
推荐做法: 如果内核版本支持(≥ 5.11),优先使用
hrtimer_setup()。它通过编译时类型检查避免回调函数签名错误,且内部自动清零其他字段。
4.3 回调函数与 cb_mode 枚举
hrtimer 的回调函数类型为:
enum hrtimer_restart (*function)(struct hrtimer *);
返回值决定定时器是否自动重启动:
HRTIMER_NORESTART(0):定时器到期后停止,不再触发。HRTIMER_RESTART(1):定时器到期后自动重新启动,需在回调内调用hrtimer_forward_now()或hrtimer_forward()设置下一次到期时间。
内核内部使用 cb_mode 字段(enum hrtimer_cb_mode)标记回调的执行上下文,该字段在初始化时由 hrtimer_setup() 或 hrtimer_init() 根据 mode 参数自动推导:
| cb_mode 枚举值 | 含义 | 典型场景 |
|---|---|---|
HRTIMER_CB_SOFTIRQ |
回调在软中断上下文执行 | 普通周期任务、非原子上下文 |
HRTIMER_CB_IRQSAFE |
回调在硬中断上下文执行 | 需要极低延迟、不可睡眠的原子上下文 |
HRTIMER_CB_IRQSAFE_NO_RESTART |
硬中断上下文 + 禁止自动重启 | 一次性定时器,硬中断中触发 |
关键约束: 在
HRTIMER_CB_IRQSAFE 模式下,回调函数中禁止调用任何可能睡眠的函数(如 kmalloc(GFP_KERNEL)、mutex_lock())。违反会导致内核调度器异常。
4.4 初始化流程对比与选择
4.5 完整初始化示例:周期任务模板
以下代码展示一个使用 hrtimer_setup() 的完整周期任务初始化流程,回调函数通过 hrtimer_forward_now() 实现固定周期重触发:
#include <linux/hrtimer.h>
#include <linux/ktime.h>
struct hrtimer periodic_timer;
u64 period_ns = 1000000; // 1ms 周期
static enum hrtimer_restart timer_callback(struct hrtimer *timer)
{
// 执行周期任务逻辑(不可睡眠)
do_periodic_work();
// 将定时器向前推进一个周期
hrtimer_forward_now(timer, ns_to_ktime(period_ns));
return HRTIMER_RESTART;
}
void start_periodic_timer(void)
{
ktime_t first_expiry = ns_to_ktime(period_ns);
hrtimer_setup(&periodic_timer, timer_callback,
CLOCK_MONOTONIC, HRTIMER_MODE_REL);
hrtimer_start(&periodic_timer, first_expiry, HRTIMER_MODE_REL);
}
设计要点:
- 回调返回
HRTIMER_RESTART并调用hrtimer_forward_now()实现连续周期触发。 - 使用
CLOCK_MONOTONIC避免系统时间跳变影响周期稳定性。 - 首次启动使用相对时间
HRTIMER_MODE_REL,从当前时间开始计时。
4.6 初始化阶段常见陷阱与排查
| 陷阱 | 现象 | 根因 | 解决方案 |
|---|---|---|---|
| 回调函数未赋值 | 定时器到期时内核 oops | timer->function 为 NULL |
使用 hrtimer_setup() 或显式赋值 |
| cb_mode 与上下文不匹配 | 调度器警告或死锁 | IRQSAFE 模式下调用了睡眠函数 | 检查回调中所有函数调用是否原子安全 |
| 时钟基准选择错误 | 周期漂移或时间跳跃 | 使用 CLOCK_REALTIME 受 NTP 影响 |
实时任务固定使用 CLOCK_MONOTONIC |
未调用 hrtimer_forward_now() |
定时器只触发一次 | 返回 HRTIMER_RESTART 但未更新到期时间 |
在回调中调用 hrtimer_forward_now() |