版权信息
warning
本文章为博主原创文章。遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
Linux 内核定时器用于在未来的某个特定时间点(基于系统滴答 jiffies)执行某个函数。它们是低精度定时器,主要用于超时处理、轮询检测等不需要纳秒级精度的场景。它是内核中最基础、最轻量级的异步机制之一。
1. 核心概念
在使用之前,需要理解以下几个关键点:
- 异步执行: 定时器回调函数是在 中断上下文 (Softirq) 中执行的,而不是进程上下文。
- Jiffies: 内核的时间单位。定时器的超时时间是基于
jiffies计数的。 - Struct timer_list: 描述定时器的核心结构体。
2. 核心数据结构
在 Linux 4.14 版本之后,内核定时器的 API 进行了重构,使得结构更加简单安全。
struct timer_list {
/* 核心字段 */
unsigned long expires; // 超时时间(单位:jiffies)
void (*function)(struct timer_list *); // 超时后的回调函数
u32 flags; // 标志位
/* 内部字段,用户通常无需直接操作 */
struct hlist_node entry;
unsigned long data; // 旧版本残留,新版通常通过container_of 获取数据
};
3. 内核定时器的特点
- 低精度(依赖 HZ,典型为 100~1000Hz)
- 软中断上下文执行(不能睡眠)
- 适合轻量的周期性任务
- 适合简单超时处理
如果需要高精度定时,应该使用 hrtimer(高精度定时器)。
4. 基本使用流程
- 定义一个
struct timer_list变量 - 初始化定时器
- 设置回调函数
- 设置超时时间
- 注册定时器(添加到内核)
- 在退出时删除定时器
5. 常用API函数
以下是开发中最常用的 API,请务必掌握:
5.1. timer_setup():初始化
内核 4.15 之后推荐的初始化方法。
void timer_setup(struct timer_list *timer,
void (*callback)(struct timer_list *),
unsigned int flags);
参数说明:
timer:你的定时器对象callback:超时后执行的函数flags:一般填 0 即可
5.2. mod_timer():修改
修改定时器的超时时间(如果未启动则相当于启动)。
int mod_timer(struct timer_list *timer, unsigned long expires);
参数说明:
timer:要设置的定时器expires:过期时间(jiffies 为单位)
例:过 200ms 触发:
mod_timer(&my_timer, jiffies + msecs_to_jiffies(200));
5.3. del_timer():删除
删除一个定时器(不等待当前回调执行完)。
int del_timer(struct timer_list *timer);
5.4. del_timer_sync():删除
删除并确保回调不再执行(常用于模块卸载)。
int del_timer_sync(struct timer_list *timer);
推荐在驱动卸载 (
module_exit) 中使用。
5.5. 时间转换函数(非常重要)
Linux 内核定时器以 jiffies 为单位,但我们通常希望以毫秒、秒表示时间。
直接操作 jiffies 很麻烦,内核提供了转换宏:
unsigned long msecs_to_jiffies(const unsigned int m);
unsigned long jiffies_to_msecs(const unsigned long j);
unsigned long usecs_to_jiffies(const unsigned int u);
示例
unsigned long expires = jiffies + msecs_to_jiffies(500);
6. 使用示例:每 500ms 打印一次消息
#include <linux/module.h>
#include <linux/kernel.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("my_timer: timer fired!\n");
/* 再次启动定时器,形成周期性 */
mod_timer(&my_timer, jiffies + msecs_to_jiffies(500));
}
static int __init timer_example_init(void)
{
pr_info("my_timer: module init\n");
/* 初始化定时器并绑定回调函数 */
timer_setup(&my_timer, my_timer_callback, 0);
/* 启动一个 500ms 的定时器 */
mod_timer(&my_timer, jiffies + msecs_to_jiffies(500));
return 0;
}
static void __exit timer_example_exit(void)
{
pr_info("my_timer: module exit\n");
/* 删除定时器并确保回调不再运行 */
del_timer_sync(&my_timer);
}
module_init(timer_example_init);
module_exit(timer_example_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("GoDm@");
MODULE_DESCRIPTION("Kernel timer example");
7. 常见问题FAQ
7.1. ❓定时器回调能否睡眠?
不能。
定时器回调运行在 软中断(softirq)上下文,不允许调用会睡眠的 API。
例如不能使用:
msleep()mutex_lock()copy_to_user()
如果要在进程上下文处理,使用workqueue更合适。
7.2. ❓定时器精度是多少?
取决于 CONFIG_HZ:
| HZ | 定时器最小精度 |
|---|---|
| 100 | 10ms |
| 250 | 4ms |
| 1000 | 1ms |
如果你要亚毫秒或更高精度 → 使用 hrtimer。
7.3. ❓卸载模块时忘记删除定时器会怎样?
很可能导致:
- 内核崩溃(使用了已释放的内存)
- 难以排查的 BUG
因此,模块退出务必del_timer_sync()。
7.4. ❓内核定时器的时钟源是什么?
- 逻辑源:
jiffies(系统滴答计数)。 - 物理源:
对于ARM来说,ARM Generic Timer (Arch Timer)——ARMv7/v8 架构规范中定义的通用定时器(System Counter),集成在 CPU 内部,频率稳定,是 ARM Linux 的首选。 - 内核子系统: Clock Event Device (负责产生中断的设备)。
8. 更高级:定时器 + 私有数据
如果你需要为每个定时器绑定自己的数据,可以这样:
struct my_data {
int count;
struct timer_list timer;
};
static void my_callback(struct timer_list *t)
{
struct my_data *data = from_timer(data, t, timer);
pr_info("count = %d\n", data->count++);
}
from_timer() 在新内核中非常常用。