3. 内核定时器核心数据结构:struct timer_list 成员详解与回调函数原型
在 Linux 内核中,struct timer_list 是驱动开发者使用标准内核定时器(低精度定时器,基于 jiffies)的核心数据结构。理解其每个成员的含义、生命周期以及回调函数的编写规范,是正确使用内核定时器的基础。本章将深入剖析该结构体,并给出标准的回调函数原型。
3.1 struct timer_list 结构体全景
该结构体定义在 <linux/timer.h> 中。不同内核版本(如 4.x 与 5.x/6.x)在成员命名上略有差异,但核心逻辑一致。以下以较新的内核版本(5.x/6.x)为例进行说明。
| 成员名称 | 类型 | 描述 | 驱动开发者职责 |
|---|---|---|---|
entry |
struct list_head |
用于将定时器挂接到内核定时器链表(bucket)的链表节点。 | 由内核定时器框架管理,驱动通常不直接操作。 |
expires |
unsigned long |
定时器的到期时间,以 jiffies 为单位。表示定时器期望在哪个 jiffies 时刻触发。 | 必须设置:通常使用 jiffies + delay 或 msecs_to_jiffies(delay_ms) 赋值。 |
function |
void (*function)(struct timer_list *) |
定时器到期时被内核调用的回调函数指针。 | 必须设置:指向驱动中定义的定时器处理函数。 |
flags |
unsigned int |
控制定时器行为的标志位。例如 TIMER_IRQSAFE、TIMER_DEFERRABLE 等。 |
通常设置为 0 或按需设置特定标志。 |
slack |
unsigned int |
定时器允许的松弛量(以 jiffies 为单位)。用于内核合并定时器以减少唤醒次数,提升系统性能。 | 通常由内核自动计算,驱动可调用 timer_setup() 时指定。 |
debug |
struct timer_debug_entry |
仅在启用了 CONFIG_TIMER_STATS 或调试选项时存在,用于记录定时器注册信息。 |
驱动开发者无需关心,主要用于内核调试。 |
版本兼容提示: 在较老的内核版本(如 3.x/4.x)中,
struct timer_list 包含 data(unsigned long)成员,用于向回调函数传递参数。新版本中该成员已被移除,回调函数直接通过 struct timer_list * 指针获取上下文,推荐使用 container_of 宏或 from_timer 宏来访问包含该定时器的自定义结构体。
3.2 回调函数原型与编写规范
定时器回调函数是驱动开发者实现的核心逻辑,其函数原型固定如下:
void my_timer_callback(struct timer_list *t);
该函数由内核在软中断(SoftIRQ)上下文中调用,因此必须遵守以下严格的约束条件:
- 不可睡眠:回调函数运行在原子上下文中,禁止调用任何可能导致睡眠的函数(如
msleep()、wait_event()、kmalloc(GFP_KERNEL)、copy_from_user()等)。 - 不可持有互斥锁:不能获取
mutex或semaphore,但可以使用自旋锁(spin_lock)。 - 执行时间应尽可能短:长时间占用 CPU 会阻塞其他软中断和定时器处理,导致系统延迟增加。
- 可重入性:如果定时器被设置为周期性触发,回调函数可能在上一轮执行未完时再次被调用,需确保逻辑正确。
最佳实践: 回调函数通常只做快速的状态检查、标志位设置、唤醒等待队列或提交工作队列(workqueue)。将耗时的业务逻辑(如数据拷贝、硬件操作)推迟到工作队列或内核线程中执行。
3.3 从回调函数获取驱动上下文:from_timer 宏
由于新版本内核移除了 data 成员,驱动开发者需要一种机制从 struct timer_list * 指针反推出包含它的自定义结构体。内核提供了 from_timer 宏,其本质是对 container_of 的封装。
// 驱动自定义结构体
struct my_device {
int id;
struct timer_list my_timer;
// ... 其他成员
};
// 定时器回调函数
void my_timer_callback(struct timer_list *t) {
// 从 timer_list 指针获取 my_device 结构体指针
struct my_device *dev = from_timer(dev, t, my_timer);
// 现在可以安全地访问 dev->id 等成员
pr_info("Timer expired for device %d\n", dev->id);
// 如果需要重新启动定时器,在此处调用 mod_timer()
}
from_timer 宏的原型为:
#define from_timer(var, callback_timer, timer_field_name) \
container_of(callback_timer, typeof(*(var)), timer_field_name)
它接受三个参数:
var:用于接收结果的自定义结构体指针变量名。callback_timer:回调函数中传入的struct timer_list *指针。timer_field_name:struct timer_list在自定义结构体中的成员名称。
3.4 内核定时器生命周期与状态转换
下图展示了内核定时器从初始化到触发的完整生命周期,以及关键 API 的作用位置。
3.5 关键 API 与成员操作速查
| API / 操作 | 作用 | 影响的主要成员 |
|---|---|---|
timer_setup(timer, callback, flags) |
初始化 struct timer_list,设置回调函数和标志。 |
function, flags, entry |
add_timer(timer) |
将已初始化的定时器激活并加入内核定时器链表。 | expires(需提前设置), entry |
mod_timer(timer, expires) |
修改定时器的到期时间。如果定时器未激活,则激活它。 | expires, entry |
del_timer(timer) |
从内核定时器链表中删除一个已激活的定时器。 | entry |
del_timer_sync(timer) |
删除定时器,并确保在 SMP 系统上回调函数不会正在其他 CPU 上执行。 | entry |
timer_pending(timer) |
检查定时器是否正在等待执行(即是否已激活)。 | 读取 entry 状态 |
关键注意事项:
- 在调用
add_timer()或mod_timer()之前,必须确保expires成员已被正确赋值。 - 在驱动卸载(
remove)或模块退出时,必须调用del_timer_sync()确保定时器被完全删除且回调函数不再运行,否则可能导致访问已释放的内存。 - 回调函数中不能直接调用
add_timer()来重新启动定时器,应使用mod_timer()。
3.6 完整初始化与使用示例片段
#include <linux/timer.h>
#include <linux/jiffies.h>
struct my_driver_data {
int sample_value;
struct timer_list poll_timer;
};
static void poll_timer_callback(struct timer_list *t)
{
struct my_driver_data *data = from_timer(data, t, poll_timer);
// 快速处理:读取硬件状态,更新 data->sample_value
// 注意:此处不可睡眠
// 重新启动定时器,实现周期性轮询(例如每 100ms)
mod_timer(&data->poll_timer, jiffies + msecs_to_jiffies(100));
}
static int my_driver_probe(struct platform_device *pdev)
{
struct my_driver_data *data;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
// 1. 初始化定时器
timer_setup(&data->poll_timer, poll_timer_callback, 0);
// 2. 设置首次到期时间(例如 100ms 后首次触发)
data->poll_timer.expires = jiffies + msecs_to_jiffies(100);
// 3. 激活定时器
add_timer(&data->poll_timer);
// ... 其他初始化
return 0;
}
static int my_driver_remove(struct platform_device *pdev)
{
struct my_driver_data *data = platform_get_drvdata(pdev);
// 4. 删除定时器,确保安全
del_timer_sync(&data->poll_timer);
// ... 其他清理
return 0;
}