Linux内核定时器与延迟工作:四种机制选型与实。
内核驱动开发中,过一会儿再做是最常见的需求之一:轮询传感器数据、超时重传、周期性心跳、延迟初始化——每种场景对精度、上下文、CPU占用的要求不同,Linux提供了timer_list、hrtimer、delayed_work、短延迟函数四种机制。本文基于Linux 6.8内核,逐一拆解原理和API,给出可编译运行的完整示例,最后用决策流程图帮你快速选型。
一、内核时间概。
1.1 jiffies:内核的时钟心跳
jiffies是内核全局变量,每发生一次时钟中断就。。时钟中断频率由HZ宏决定,编译内核时配置:
| HZ | 中断周期 | 典型场景 |
|---|---|---|
| 100 | 10ms | 嵌入式低功耗设备 |
| 250 | 4ms | Ubuntu 24.04默认 |
| 1000 | 1ms | 桌面/服务器低延迟 |
#include <linux/jiffies.h>
unsigned long timeout = jiffies + 5 * HZ; // 5秒后的jiffies```
`HZ`是个频率值,`5 * HZ`就是5秒对应的滴答数,用jiffies做超时判断时*绝对不能直接用`>`或`<`比较**——jiffies会回绕(32位系统约49天回绕一次),必须用内核提供的宏。
### 1.2 安全比较。
```c
#include <linux/jiffies.h>
// time_after(a, b):a在b之后为真(处理回绕)
if (time_after(jiffies, timeout)) {
// 超时。}
// time_before(a, b):a在b之前为真
if (time_before(jiffies, deadline)) {
// 还没到截止时。}
// time_in_range(a, b, c):a在[b, c]区间内为真
if (time_in_range(jiffies, start, end)) {
// 在时间窗口内
}
这三个宏内部处理了回绕问题,不管jiffies是否溢出都能正确判断。
1.3 jiffies_64
64位系统上jiffies仍是unsigned long。4位),但32位系统上只有32位,需64位时用get_jiffies_64()。
#include <linux/jiffies.h>
u64 j64 = get_jiffies_64();
直接读jiffies_64变量不是原子操作集2位系统上),必须通过get_jiffies_64()读取。
1.4 ktime_t:纳秒级时间
ktime_t是内核高精度时间子系统的时间表示,内部是64位纳秒值:
#include <linux/ktime.h>
ktime_t kt = ktime_set(1, 500000000); // 1。+ 500毫秒 = 1.5。ktime_t kt_ns = ns_to_ktime(1000000); // 1毫秒 = 1000000纳秒
s64 ns = ktime_to_ns(kt); // 转纳秒
s64 us = ktime_to_us(kt); // 转微秒
s64 ms = ktime_to_ms(kt); // 转毫秒
ktime_t是hrtimer的基础,也是内核时间子系统的核心数据类型。
二、低精度定时器(timer_list)
2.1 timer_list结构建
struct timer_list {
struct hlist_node entry; // 内核链表节点
unsigned long expires; // 到期jiffies。 void (*function)(struct timer_list *t); // 回调函数
u32 flags; // 标志。 // ... 内核内部字段
};
6.8内核中timer_list的回调函数签名是void (*function)(struct timer_list *t),从t可以用from_timer宏反推出自定义结构体。
2.2 timer_setup:初始化定时。
Linux 6.8已经移除了旧版setup_timer()和init_timer(),统一用timer_setup()。
// 6.8 API:初始化定时器并绑定回调
void timer_setup(struct timer_list *timer,
void (*callback)(struct timer_list *t),
unsigned int flags);
2.3 核心API
| API | 功能 | 说明 |
|---|---|---|
timer_setup(timer, cb, flags) |
初始化定时器 | 绑定回调函数,flags一般填0 |
mod_timer(timer, expires) |
修改/激活定时器 | 定时器未激活则激活,已激活则修改到期时间 |
del_timer(timer) |
删除定时器 | 不等待回调执行完毕,可能正在其他CPU上跑 |
del_timer_sync(timer) |
同步删除定时器 | 等待回调执行完毕再返回,多CPU安全 |
2.4 完整示例:周期性定时器
// timer_periodic.c - 周期性定时器示例(Linux 6.8验证:#include <linux/module.h>
#include <linux/init.h>
#include <linux/timer.h>
#define TIMER_INTERVAL_MS 1000 // 1秒周。
struct my_timer_dev {
struct timer_list timer;
int count;
};
static struct my_timer_dev tdev;
/* 定时器回调函数- 运行在中断上下文件*/
static void timer_callback(struct timer_list *t)
{
struct my_timer_dev *dev = from_timer(dev, t, timer);
dev->count++;
pr_info("timer: tick #%d\n", dev->count);
/* 重新设置到期时间,实现周期触*/
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(TIMER_INTERVAL_MS));
}
static int __init timer_init(void)
{
tdev.count = 0;
/* 初始化定时器,绑定回调*/
timer_setup(&tdev.timer, timer_callback, 0);
/* 设置首次到期时间并激*/
mod_timer(&tdev.timer, jiffies + msecs_to_jiffies(TIMER_INTERVAL_MS));
pr_info("timer: periodic timer started, interval=%dms\n", TIMER_INTERVAL_MS);
return 0;
}
static void __exit timer_exit(void)
{
/* 同步删除:等待可能正在执行的回调完成 */
del_timer_sync(&tdev.timer);
pr_info("timer: stopped after %d ticks\n", tdev.count);
}
module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("linuxros");
MODULE_DESCRIPTION("Periodic timer_list example");
Makefile。
obj-m += timer_periodic.o
KDIR := /lib/modules/$(shell uname -r)/build
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
测试。
make && sudo insmod timer_periodic.ko
dmesg | tail -10
# timer: periodic timer started, interval=1000ms
# timer: tick #1
# timer: tick #2
# ...
sudo rmmod timer_periodic
2.5 关键约束:中断上下文
timer_list的回调运行在中断上下*(准确说是软中断上下文),这意味着。
- 禁止睡眠:不能调用msleep()、schedule()、mutex_lock()等可能阻塞的函数
- 禁止访问用户空间:没有进程上下文,copy_to_user/copy_from_user不可。- 执行要快**:长时间占用会影响整个系统的软中断处。
需要睡眠或长时间处理?用第四节介绍的delayed_work。
三、高精度定时器(hrtimer)
3.1 为什么需要hrtimer
timer_list的精度受限于HZ,HZ=250时最小粒度4ms。很多场景需要微秒甚至纳秒级精度:音频采样、硬件PWM、精确计时。hrtimer基于高精度时钟源(TSC/HPET等),不依赖jiffies,精度可达纳秒级别
3.2 hrtimer结构体与初始化
struct hrtimer {
struct timerqueue_node node; // 红黑树节。 ktime_t _softexpires; // 软到期时。 enum hrtimer_restart (*function)(struct hrtimer *t); // 回调
struct hrtimer_clock_base *base; // 时钟。 u8 state; // 状态 // ...
};
#include <linux/hrtimer.h>
void hrtimer_init(struct hrtimer *timer, clockid_t clock_id,
enum hrtimer_mode mode);
clock_id:时钟源,一般用CLOCK_MONOTONIC(单调递增,不受系统时间调整影响)mode:HRTIMER_MODE_REL(相对时间)或HRTIMER_MODE_ABS(绝对时间)
3.3 核心API
| API | 功能 |
|---|---|
hrtimer_init(timer, clock, mode) |
初始化hrtimer |
hrtimer_start(timer, time, mode) |
启动定时器 |
hrtimer_cancel(timer) |
取消定时器(等待回调完成) |
hrtimer_forward(timer, now, interval) |
推进到下一个到期时间 |
3.4 回调函数返回调
enum hrtimer_restart my_callback(struct hrtimer *t)
{
// 处理逻辑...
return HRTIMER_RESTART; // 重新启动,周期定。 // return HRTIMER_NORESTART; // 不重启,一次性定。}
周期性hrtimer的标准写法:
static enum hrtimer_restart my_hrtimer_callback(struct hrtimer *t)
{
struct my_dev *dev = container_of(t, struct my_dev, htimer);
/* 业务处理 */
/* 推进到下一个周*/
hrtimer_forward_now(t, ms_to_ktime(1)); // 1ms周期
return HRTIMER_RESTART;
}
3.5 完整示例:1ms精度周期定时器
// hrtimer_periodic.c - 高精度1ms周期定时器(Linux 6.8验证)#include <linux/module.h>
#include <linux/init.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>
#define PERIOD_MS 1 // 1ms周期
struct hrtimer_dev {
struct hrtimer htimer;
ktime_t period;
int count;
};
static struct hrtimer_dev hdev;
static enum hrtimer_restart hrtimer_callback(struct hrtimer *t)
{
struct hrtimer_dev *dev = container_of(t, struct hrtimer_dev, htimer);
dev->count++;
/* 推进到下一个周期点 */
hrtimer_forward(t, hrtimer_cb_get_time(t), dev->period);
return HRTIMER_RESTART;
}
static int __init hrtimer_init_mod(void)
{
hdev.count = 0;
hdev.period = ms_to_ktime(PERIOD_MS);
/* 初始化:单调时钟,相对模块*/
hrtimer_init(&hdev.htimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
hdev.htimer.function = hrtimer_callback;
/* 启动定时*/
hrtimer_start(&hdev.htimer, hdev.period, HRTIMER_MODE_REL);
pr_info("hrtimer: started, period=%dms\n", PERIOD_MS);
return 0;
}
static void __exit hrtimer_exit_mod(void)
{
/* 取消定时器,等待回调完成 */
hrtimer_cancel(&hdev.htimer);
pr_info("hrtimer: stopped after %d ticks\n", hdev.count);
}
module_init(hrtimer_init_mod);
module_exit(hrtimer_exit_mod);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("linuxros");
MODULE_DESCRIPTION("High-resolution periodic timer example");
3.6 hrtimer vs timer_list对比
| 对比 | timer_list | hrtimer |
|---|---|---|
| 精度 | jiffies级别。-10ms | 纳秒 |
| 底层实现 | 轮转桶(wheel) | 红黑树 |
| 回调上下文 | 软中断 | 硬中断(可配置软中断号 |
| 周期重置 | 手动mod_timer | HRTIMER_RESTART + hrtimer_forward |
| 内核配置 | 默认启用 | 需CONFIG_HIGH_RES_TIMERS |
| 适用场景 | 秒级超时、低精度轮询 | 精确计时、音频、PWM |
| CPU开销 | 略高(红黑树插入) |
四、延迟工作队。
4.1 delayed_work结构建
struct delayed_work {
struct work_struct work; // 实际工作者 struct timer_list timer; // 内部定时器(驱动延迟。 struct workqueue_struct *wq; // 所属工作队。 int cpu; // 目标CPU
};
delayed_work本质上是timer_list + work_struct的组合:到期后由内部定时器触发,把work_struct提交到工作队列执行关键区别:回调运行在进程上下文。
4.2 核心API
| API | 功能 |
|---|---|
INIT_DELAYED_WORK(dw, fn) |
初始化delayed_work |
schedule_delayed_work(dw, delay) |
调度延迟执行,delay是jiffies |
cancel_delayed_work_sync(dw) |
同步取消,等待执行完成 |
flush_scheduled_work() |
等待所有scheduled work完成 |
4.3 完整示例:延迟N秒后执行任务
// delayed_work_demo.c - 延迟工作队列示例(Linux 6.8验证:#include <linux/module.h>
#include <linux/init.h>
#include <linux/workqueue.h>
#include <linux/slab.h>
struct dw_dev {
struct delayed_work dwork;
int data;
};
static struct dw_dev *dwdev;
/* 工作函数 - 运行在进程上下文,可以睡眠! */
static void work_handler(struct work_struct *work)
{
struct dw_dev *dev = container_of(work, struct dw_dev, dwork.work);
dev->data++;
pr_info("delayed_work: executed, data=%d, pid=%d, comm=%s\n",
dev->data, current->pid, current->comm);
/* 进程上下文:可以睡眠、可以访问用户空间、可以持*/
msleep(100); // 这里睡眠完全没问题 pr_info("delayed_work: after sleep, data=%d\n", dev->data);
}
static int __init dw_init(void)
{
dwdev = kzalloc(sizeof(*dwdev), GFP_KERNEL);
if (!dwdev)
return -ENOMEM;
dwdev->data = 0;
/* 初始化延迟工*/
INIT_DELAYED_WORK(&dwdev->dwork, work_handler);
/* 调度5秒后执行 */
schedule_delayed_work(&dwdev->dwork, 5 * HZ);
pr_info("delayed_work: scheduled, will run in 5 seconds\n");
return 0;
}
static void __exit dw_exit(void)
{
/* 同步取消:如果工作正在执行,等待完成 */
cancel_delayed_work_sync(&dwdev->dwork);
kfree(dwdev);
pr_info("delayed_work: module unloaded\n");
}
module_init(dw_init);
module_exit(dw_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("linuxros");
MODULE_DESCRIPTION("Delayed workqueue example");
4.4 delayed_work vs timer_list适用场景
| 场景 | timer_list | delayed_work |
|---|---|---|
| 简单超时检查 | ,轻量 | ,杀鸡用牛刀 |
| 回调中需要睡眠 | ,死锁 | ,进程上下文 |
| 回调中访问用户空 | 。无进程上下文 | 。可以 |
| 回调中持mutex | 。可能睡眠 | 。允许 |
| 需要高精度 | 。jiffies | 。也是jiffies |
| 周期性任务(轻量 | 。简 | 。也可 |
| 周期性任务(重活 | 。不能耗时 | ,进程上下文 |
简单判断:回调里干轻活用timer_list,回调里干重活或需要睡眠用delayed_work。
五、短延迟函数
有些场景不需要定时器,只需要等一小会。内核提供了几组短延迟函数。
5.1 忙等待延迟:ndelay/udelay/mdelay
#include <linux/delay.h>
ndelay(100); // 纳秒级延迟,忙等
udelay(100); // 微秒级延迟,忙等
mdelay(10); // 毫秒级延迟,忙等
这三个函数的实现方式*忙等待(busy-wait):CPU死循环数圈,不释放CPU。精度高,但浪费CPU时间。udelay和ndelay用于微秒/纳秒级短延迟(比如硬件复位时序),mdelay一般不推荐——毫秒级延迟应该用睡眠方式。
5.2 睡眠延迟:usleep_range
#include <linux/delay.h>
usleep_range(1000, 2000); // 睡眠1ms~2ms
usleep_range让出CPU,进程进入睡眠状态。内核会在这个范围内选择合适的唤醒时机,有利于合并定时器中断、省电。min和max越接近,延迟越精确;范围越大,省电效果越好。
5.3 智能延迟:fsleep
Linux 6.8推荐的短延迟函数,根据延迟时长自动选择最优策略:
#include <linux/delay.h>
fsleep(100); // 100纳秒 → 忙等
fsleep(10000); // 10微秒 → 忙等
fsleep(2000000); // 2毫秒 → 睡眠
fsleep内部逻辑:短延迟用udelay,长延迟用usleep_range,不需要开发者自己判断。
5.4 短延迟函数对。
| 函数 | 精度范围 | 上下 | CPU占用 | 适用场景 |
|---|---|---|---|---|
ndelay(n) |
纳秒 | 任意 | 100% | 硬件时序(极少用途 |
udelay(n) |
微秒 | 任意 | 100% | 硬件复位、总线时序 |
mdelay(n) |
毫秒 | 任意 | 100% | 极少推荐,改用usleep_range |
usleep_range(min, max) |
微秒~毫秒 | 进程 | 0% | 毫秒级延迟、省电场 |
fsleep(us) |
纳秒~毫秒 | 进程 | 自动 | *6.8推荐,通用短延 |
注意:udelay/ndelay可以在中断上下文使用(不睡眠),usleep_range/fsleep只能在进程上下文使用。
六、定时器选型指南
6.1 决策流程。
6.2 四种机制对比总表
| 维度 | timer_list | hrtimer | delayed_work | 短延迟函数 |
|---|---|---|---|---|
| 精度 | jiffies。-10ms | 纳秒 | jiffies。-10ms | 纳秒~毫秒 |
| 上下 | 软中 | 硬中 | 进程 | 任意/进程 |
| 可睡眠 | 看函数 | |||
| 周期 | 手动mod_timer | HRTIMER_RESTART | 手动重新调度 | 。一次 |
| CPU开销 | 忙等。 | |||
| 内核配置 | 默认启用 | CONFIG_HIGH_RES_TIMERS | 默认启用 | 默认启用 |
| 典型用途 | 超时检测、心 | 精确计时、PWM | 延迟初始化、重启 | 硬件时序、短暂等 |
七、常见问题
Q1:timer回调里调了msleep会怎样。
死锁或内核崩溃。timer_list回调运行在软中断上下文,msleep()会让当前"进程"睡眠——但软中断上下文没有进程,调度器无法处理。解决方案:改用delayed_work,它运行在进程上下文,可以安全睡眠。
Q2:hrtimer精度达不到预期怎么办?
检查内核配置:
grep CONFIG_HIGH_RES_TIMERS /boot/config-$(uname -r)
必须为CONFIG_HIGH_RES_TIMERS=y。如果没开,hrtimer会退化为jiffies精度。另外,hrtimer回调虽然精度高,但实际触发时间受中断延迟影响,回调里不要做耗时操作。集
Q3:del_timer和del_timer_sync怎么选?
单CPU环境下两者等价。SMP多核环境下,del_timer只从定时器链表中移除,不等待正在其他CPU上执行的回调——回调可能还在跑。del_timer_sync会等待回调执行完毕再返回。模块卸载时必须用del_timer_sync,否则回调可能访问已释放的数据导致崩溃。注意:del_timer_sync不能在定时器回调内部调用(会死锁),回调里用del_timer。
八、总结
速查看
| 需 | 选用 | 一句话理由 |
|---|---|---|
| 毫秒级超时检 | timer_list |
轻量够用 |
| 微秒/纳秒级精确计数 | hrtimer |
唯一选择 |
| 回调需要睡眠或干重启 | delayed_work |
进程上下文 |
| 硬件时序短等待(中断上下文) | udelay |
忙等待,不睡 |
| 通用短延迟(进程上下文) | fsleep |
6.8推荐,自动选策 |
| 周期性轻量任 | timer_list + mod_timer |
简单直 |
| 周期性重活任 | delayed_work + 重新调度 |
可睡眠 |
记住一条主线:中断上下文不能睡眠。timer_list和hrtimer的回调都在中断上下文,delayed_work在进程上下文。选型的核心就是搞清楚你的回调里要干什么。
本文首发于linuxros.cn,转载请注明出处。