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 初始化流程与生命周期
下图展示了定时器从初始化到销毁的完整生命周期,重点突出两种初始化方式的入口差异:
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。