4. 初始化定时器:timer_setup() 与 DEFINE_TIMER 宏的使用与区别
在 Linux 内核中,初始化一个低精度定时器(基于 timer_list 结构体)有两种标准方式:静态初始化(编译时)使用 DEFINE_TIMER 宏,动态初始化(运行时)使用 timer_setup() 函数。两者最终都完成对 timer_list 结构体的字段填充,但在使用场景、灵活性以及内核版本兼容性上存在显著差异。
内核版本说明:自 Linux 4.15 起,内核引入了
timer_setup() 和 from_timer() 机制,逐步淘汰了旧的 setup_timer() 和 init_timer() API。本课程基于 4.15+ 内核,推荐使用 timer_setup()。
4.1 DEFINE_TIMER 宏:编译时静态初始化
DEFINE_TIMER 是一个宏,用于在编译阶段定义并初始化一个 timer_list 变量。它适用于定时器数量固定、全局或静态作用域的场景。
原型定义:
#define DEFINE_TIMER(_name, _function) \
struct timer_list _name = \
__TIMER_INITIALIZER(_function, 0)
使用示例:
#include <linux/timer.h>
// 定时器回调函数
void my_timer_callback(struct timer_list *t)
{
pr_info("定时器到期!\n");
}
// 静态定义并初始化定时器
DEFINE_TIMER(my_timer, my_timer_callback);
static int __init my_init(void)
{
// 设置定时器超时值为 1 秒后
mod_timer(&my_timer, jiffies + msecs_to_jiffies(1000));
return 0;
}
特点与限制:
- 编译时分配:定时器结构体在编译阶段即被分配在数据段或 BSS 段。
- 无法指定数据指针:旧版
DEFINE_TIMER不提供设置.data或私有数据的接口;回调函数只能通过container_of或全局变量访问上下文。 - 作用域受限:通常用于模块全局或文件静态作用域,不适合动态分配的定时器。
- 简洁性:一行代码完成定义与初始化,代码量少。
4.2 timer_setup() 函数:运行时动态初始化
timer_setup() 是内核提供的内联函数,用于在运行时初始化一个已经分配(静态或动态)的 timer_list 结构体。它支持将驱动私有数据直接传递给回调函数,是当前内核推荐的方式。
原型定义:
static inline void timer_setup(struct timer_list *timer,
void (*callback)(struct timer_list *),
unsigned int flags)
使用示例:
#include <linux/timer.h>
#include <linux/slab.h>
struct my_device {
struct timer_list timer;
int data;
};
void my_timer_callback(struct timer_list *t)
{
struct my_device *dev = from_timer(dev, t, timer);
pr_info("定时器到期, data = %d\n", dev->data);
}
static int __init my_init(void)
{
struct my_device *dev;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
dev->data = 42;
// 动态初始化定时器,并将 dev 作为上下文
timer_setup(&dev->timer, my_timer_callback, 0);
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(1000));
return 0;
}
特点与优势:
- 运行时灵活:可以在任意上下文(如 probe 函数)中初始化定时器。
- 支持私有数据传递:配合
from_timer()宏,回调函数可以直接获取包含定时器的父结构体指针,避免全局变量。 - 兼容动态分配:适用于
kmalloc、devm_kzalloc等动态内存中的定时器。 - flags 参数:当前内核保留,通常传入 0;未来可用于定时器行为修饰。
4.3 核心区别对比
| 对比维度 | DEFINE_TIMER |
timer_setup() |
|---|---|---|
| 初始化时机 | 编译时(静态) | 运行时(动态) |
| 适用作用域 | 全局 / 文件静态 | 任意(全局、局部、动态分配) |
| 私有数据传递 | 不直接支持(需全局变量或 container_of) |
通过 from_timer() 原生支持 |
| 代码简洁性 | 一行完成定义+初始化 | 需先分配结构体,再调用函数 |
| 内核版本要求 | 所有版本 | 4.15+(推荐) |
| 典型使用场景 | 模块级固定定时器 | 设备驱动 probe 中的定时器 |
4.4 初始化流程对比图
下图展示了两种初始化方式在代码执行路径上的差异:
4.5 回调函数中获取私有数据:from_timer() 宏
使用 timer_setup() 时,回调函数接收的是 struct timer_list * 指针。要获取包含该定时器的父结构体,必须使用 from_timer() 宏,它本质上是 container_of() 的封装。
宏定义:
#define from_timer(var, callback_timer, timer_field) \
container_of(callback_timer, typeof(*(var)), timer_field)
使用模式:
struct my_driver {
struct timer_list gc_timer;
spinlock_t lock;
int pending_ops;
};
void gc_timer_callback(struct timer_list *t)
{
struct my_driver *drv = from_timer(drv, t, gc_timer);
unsigned long flags;
spin_lock_irqsave(&drv->lock, flags);
// 访问 drv->pending_ops 等字段
spin_unlock_irqrestore(&drv->lock, flags);
// 重新设置定时器(如果需要周期性执行)
mod_timer(&drv->gc_timer, jiffies + HZ);
}
最佳实践:在设备驱动中,始终将
timer_list 嵌入到设备结构体中,并使用 timer_setup() + from_timer() 组合。这避免了全局变量,使代码更模块化、可测试。
4.6 选择指南
- 选择
DEFINE_TIMER:当定时器是模块全局的、不需要携带私有数据、且数量固定时。例如内核中的周期性的全局统计定时器。 - 选择
timer_setup():在绝大多数设备驱动场景中,定时器与特定设备实例绑定,需要访问设备私有数据。这是 4.15+ 内核的推荐做法。 - 避免混用:不要对同一个定时器同时使用两种初始化方式,会导致未定义行为。
注意:旧版内核中的
setup_timer() 和 init_timer() 已在 4.15+ 中被标记为废弃。如果你的代码需要向后兼容旧内核,请使用条件编译;否则请统一使用 timer_setup()。