3. 内核定时器数据结构:struct timer_list 与 timer_setup 宏
内核定时器的核心数据结构是 struct timer_list,它描述了一个动态定时器的全部属性。从 Linux 4.15 开始,内核引入了 timer_setup 宏来简化定时器的初始化,并废弃了老旧的 setup_timer 和 init_timer 接口。本节将详细解析该结构体的每个成员,并演示 timer_setup 的标准用法。
3.1 struct timer_list 结构体成员解析
struct timer_list 定义在 <linux/timer.h> 中,其核心成员如下表所示:
| 成员名称 | 类型 | 说明 |
|---|---|---|
entry |
struct list_head |
链表节点,用于将定时器挂接到内核的定时器链表中(开发者无需直接操作) |
expires |
unsigned long |
定时器到期时的 jiffies 值。设置后内核会在该时刻执行回调函数 |
function |
void (*function)(struct timer_list *) |
定时器到期时调用的回调函数指针。新接口中参数为 struct timer_list * |
flags |
unsigned long |
定时器标志位,用于控制定时器行为(如 TIMER_IRQSAFE、TIMER_DEFERRABLE) |
slack |
int |
定时器到期时间的松弛量(以 jiffies 为单位),用于合并定时器以减少 CPU 唤醒次数 |
start_pid |
pid_t |
启动该定时器的进程 PID(调试用,通常不直接访问) |
start_site |
void * |
启动该定时器的调用点地址(调试用,用于跟踪定时器来源) |
start_comm |
char[16] |
启动该定时器的进程名称(调试用) |
lock |
spinlock_t |
保护定时器操作的内部自旋锁(开发者无需手动加锁) |
关键理解: 开发者最常操作的三个成员是 expires、function 和 flags。entry 和 lock 由内核内部管理,slack 用于节能优化,start_pid/start_site/start_comm 仅在内核配置了 CONFIG_TIMER_STATS 时有效。
3.2 timer_setup 宏的使用
timer_setup 宏是内核推荐的定时器初始化方式,它一次性完成结构体清零、回调函数绑定和 flags 设置。其原型如下:
#define timer_setup(timer, callback, flags) \
__init_timer((timer), (callback), (flags))
使用 timer_setup 的典型步骤分为三步:
- 定义定时器对象:声明一个
struct timer_list变量(静态或动态分配)。 - 调用
timer_setup:传入定时器指针、回调函数和 flags。 - 设置到期时间:通过
mod_timer或直接赋值expires并调用add_timer。
以下是一个完整的驱动模块示例:
#include <linux/module.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
static struct timer_list my_timer;
static void my_timer_callback(struct timer_list *t)
{
pr_info("定时器到期!当前 jiffies: %lu\n", jiffies);
/* 如果需要周期性触发,在此重新设置 expires 并 add_timer */
}
static int __init timer_demo_init(void)
{
pr_info("初始化定时器...\n");
/* 步骤1: 使用 timer_setup 初始化定时器 */
timer_setup(&my_timer, my_timer_callback, 0);
/* 步骤2: 设置到期时间为当前 jiffies + 2 秒 */
mod_timer(&my_timer, jiffies + msecs_to_jiffies(2000));
return 0;
}
static void __exit timer_demo_exit(void)
{
/* 删除定时器,确保回调不再执行 */
del_timer_sync(&my_timer);
pr_info("定时器已删除\n");
}
module_init(timer_demo_init);
module_exit(timer_demo_exit);
MODULE_LICENSE("GPL");
最佳实践: 在模块退出函数中务必调用 del_timer_sync 而非 del_timer。del_timer_sync 会等待定时器回调执行完毕(如果正在其他 CPU 上运行),避免竞态条件。
3.3 回调函数签名与 from_timer 宏
新式回调函数的签名统一为 void (*function)(struct timer_list *)。这意味着回调函数只能接收定时器指针作为参数。如果需要在回调中访问包含该定时器的自定义结构体,可以使用 from_timer 宏进行反向转换:
struct my_device {
int id;
struct timer_list timer;
/* 其他成员 */
};
static void my_device_timer_callback(struct timer_list *t)
{
/* 从 timer 指针反推出 my_device 指针 */
struct my_device *dev = from_timer(dev, t, timer);
pr_info("设备 %d 的定时器到期\n", dev->id);
}
from_timer 宏利用 container_of 机制,根据结构体中 timer 成员的偏移量计算出外层结构体的起始地址。这是驱动开发中非常常见的模式。
3.4 flags 常用选项
timer_setup 的第三个参数 flags 可以控制定时器的特殊行为,常用标志如下:
| 标志 | 值 | 作用 |
|---|---|---|
0 |
0 | 默认行为,定时器在普通软中断上下文中执行 |
TIMER_IRQSAFE |
0x00000001 | 定时器回调在硬中断安全上下文中执行(禁用本地中断),用于时间要求极高的场景 |
TIMER_DEFERRABLE |
0x00000002 | 定时器可延迟执行,允许内核在 CPU 空闲时合并处理,用于节能 |
TIMER_PINNED |
0x00000004 | 将定时器绑定到当前 CPU,不迁移到其他核心 |
注意: 使用 TIMER_IRQSAFE 时,回调函数中不能调用任何可能睡眠的函数(如 msleep、mutex_lock),因为此时本地中断已被禁用。绝大多数驱动场景使用 flags = 0 即可。
3.5 定时器初始化与使用流程
下图展示了从定义定时器到删除定时器的完整生命周期:
流程总结: 定义 → 初始化 → 设置到期 → 等待回调 → 删除。其中 timer_setup 将前两步合并为一步,mod_timer 同时完成设置到期时间和激活定时器两个动作,是最常用的接口。