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 初始化流程对比与选择

hrtimer 初始化流程对比 hrtimer_init() 传统路径 步骤1:传入 clock_id + mode 步骤2:手动赋值 timer->function 风险:遗漏 function 赋值 → oops hrtimer_setup() 现代路径 步骤1:传入 clock_id + mode 步骤2:传入 callback 函数指针 安全:编译时检查 + 自动绑定 结论:内核 ≥ 5.11 优先使用 hrtimer_setup(),降低遗漏风险 传统路径需手动赋值 function,且无编译时类型校验

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()