4. 初始化定时器:timer_setup 与 DEFINE_TIMER 宏、静态初始化与动态初始化方法

内核定时器在使用前必须完成初始化,将 timer_list 结构体的各字段(尤其是回调函数、数据和到期时间)设置为有效值。Linux 内核提供了两套初始化路径:静态初始化(编译时完成)和 动态初始化(运行时完成)。自 Linux 4.15 起,init_timer() 被标记为废弃,统一由 timer_setup() 和 DEFINE_TIMER 宏替代。

核心原则:新驱动一律使用 timer_setup() 或 DEFINE_TIMER,不再调用 init_timer() 或直接操作 timer_list.data 字段。

4.1 timer_list 结构体回顾

初始化操作的本质是填充 struct timer_list 的关键成员。该结构体定义在 <linux/timer.h> 中,核心字段如下:

字段 类型 说明
entry struct hlist_node 内核定时器链表节点,由内核维护,驱动不应直接修改
expires unsigned long 定时器到期时的 jiffies 值
function void (*)(struct timer_list *) 定时器到期时执行的回调函数(新接口)
flags unsigned int 定时器标志位,如 TIMER_IRQSAFE、TIMER_DEFERRABLE
slack int 定时器松弛度,用于合并中断以节省功耗
注意:旧版回调函数签名为 void (*function)(unsigned long),通过 data 字段传参。新接口将 data 字段移除,改用 from_timer() 宏从 timer_list 指针反推包含该定时器的结构体。

4.2 静态初始化:DEFINE_TIMER 宏

DEFINE_TIMER 宏在编译时完成定时器的全部初始化工作,适用于全局或静态分配的定时器。其原型为:

#include <linux/timer.h>

DEFINE_TIMER(timer_name, callback_function);

展开后等价于:

struct timer_list timer_name = {
    .function = callback_function,
    .flags    = 0,
    .expires  = 0,
    .slack    = -1,
};

使用示例:

#include <linux/module.h>
#include <linux/timer.h>
#include <linux/jiffies.h>

static void my_timer_callback(struct timer_list *t)
{
    pr_info("Timer expired!\n");
}

/* 静态定义定时器 */
DEFINE_TIMER(my_timer, my_timer_callback);

static int __init my_init(void)
{
    /* 设置到期时间为当前时间 + 2 秒 */
    mod_timer(&my_timer, jiffies + msecs_to_jiffies(2000));
    return 0;
}

static void __exit my_exit(void)
{
    del_timer_sync(&my_timer);
}

module_init(my_init);
module_exit(my_exit);
注意:静态初始化后定时器处于 未激活 状态,必须调用 add_timer() 或 mod_timer() 将其加入内核定时器链表后才能生效。

4.3 动态初始化:timer_setup 宏

对于在运行时动态分配(如 kmalloc、kzalloc)或作为结构体成员存在的定时器,使用 timer_setup() 宏进行初始化。其原型为:

#include <linux/timer.h>

timer_setup(timer_ptr, callback_function, flags);

参数说明:

  • timer_ptr:指向 struct timer_list 的指针
  • callback_function:回调函数,签名为 void (*)(struct timer_list *)
  • flags:定时器标志位,常用值包括 0(默认)、TIMER_IRQSAFE(在中断上下文安全执行)、TIMER_DEFERRABLE(可延迟触发,用于节能)

使用示例(定时器作为设备结构体成员):

#include <linux/module.h>
#include <linux/timer.h>
#include <linux/slab.h>

struct my_device {
    int id;
    struct timer_list timer;
};

static void my_dev_timer_callback(struct timer_list *t)
{
    struct my_device *dev = from_timer(dev, t, timer);
    pr_info("Device %d timer expired\n", dev->id);
}

static int __init my_init(void)
{
    struct my_device *dev;

    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    if (!dev)
        return -ENOMEM;

    dev->id = 42;

    /* 动态初始化定时器 */
    timer_setup(&dev->timer, my_dev_timer_callback, 0);

    /* 启动定时器:2 秒后到期 */
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(2000));

    return 0;
}

static void __exit my_exit(void)
{
    /* 清理工作略 */
}

module_init(my_init);
module_exit(my_exit);
关键技巧:在回调函数中通过 from_timer(container, timer_ptr, timer_field) 宏获取包含该定时器的结构体指针,这是新接口的标准用法,避免了旧版 data 字段的类型安全问题。

4.4 静态初始化 vs 动态初始化对比

对比维度 静态初始化(DEFINE_TIMER) 动态初始化(timer_setup)
分配时机 编译时,全局/静态分配 运行时,堆/栈/结构体成员
使用场景 模块级全局定时器、驱动单例 每设备定时器、动态创建/销毁
内存位置 .data 或 .bss 段 堆或栈
初始化方式 宏展开为结构体赋值 宏调用,内部设置 function/flags
标志位设置 需手动修改 .flags 通过第三个参数直接传入
灵活性 较低,编译时固定 高,可在运行时决定标志位

4.5 初始化流程与生命周期

下图展示了定时器从初始化到销毁的完整生命周期,重点突出两种初始化方式的入口差异:

内核定时器初始化与生命周期 DEFINE_TIMER 宏 timer_setup 宏 定时器已初始化(未激活) add_timer / mod_timer 定时器激活(挂入链表) 到期 → 回调执行 del_timer / del_timer_sync 说明:两种初始化方式最终都进入“已初始化但未激活”状态,后续必须通过 add_timer/mod_timer 激活

4.6 初始化后的关键操作

无论采用哪种初始化方式,定时器初始化完成后都需要执行以下操作之一才能生效:

  • add_timer(struct timer_list *timer):将定时器加入内核链表,使用初始化时设置的 expires 值。如果 expires 未设置(值为 0),定时器会立即到期。
  • mod_timer(struct timer_list *timer, unsigned long expires):修改定时器的到期时间并激活(如果尚未激活则激活)。这是最常用的启动方式,因为它同时完成了设置到期时间和激活两个操作。
常见错误:在调用 timer_setup() 后直接使用 add_timer() 但忘记设置 expires 字段,会导致定时器立即触发,可能引发竞态条件。推荐始终使用 mod_timer() 来启动定时器。

4.7 标志位(flags)详解

timer_setup 的第三个参数和 DEFINE_TIMER 的 .flags 字段支持以下标志位组合:

标志位 值 含义 使用场景
0 0x00000000 默认行为,回调在软中断上下文执行 大多数驱动场景
TIMER_IRQSAFE 0x00000001 回调可在硬中断上下文安全执行,内核不会为其启用软中断锁 高精度定时器、中断处理中触发的定时器
TIMER_DEFERRABLE 0x00000002 定时器可延迟触发,系统空闲时不会为了该定时器而唤醒 CPU 非关键性周期任务,用于省电
TIMER_PINNED 0x00000004 定时器绑定到当前 CPU,不会迁移到其他核心 CPU 亲和性要求高的场景
最佳实践:对于大多数设备驱动,使用 timer_setup(&timer, callback, 0) 即可。只有在需要中断安全或省电特性时才设置额外标志位。多个标志位可通过按位或组合,如 TIMER_IRQSAFE | TIMER_PINNED。