嵌入式Linux内核定时器与延迟工作:jiffies、hrtimer、delayed_work实战
内核时间子系统是驱动开发绕不开的基础。本文从jiffies/HZ出发,拆解timer_list、hrtimer、delayed_work三种定时机制,配合API实例和选型流程图,解决定时器选型和常见踩坑问题。
一、原理简析
Linux内核用jiffies全局变量记录系统启动以来的时钟滴答数,每来一次硬件定时器中断,jiffies加1。HZ是编译期配置的常量,表示每秒的滴答数,直接决定了定时器的精度上限。
| CONFIG_HZ | 滴答周期 | 典型场景 |
|---|---|---|
| 100 | 10ms | 服务器、嵌入式低功耗 |
| 250 | 4ms | 桌面默认 |
| 1000 | 1ms | 低延迟桌面、实时场景 |
内核提供一组转换辅助函数,避免手动计算HZ:
#include <linux/jiffies.h>
msecs_to_jiffies(500); /* 毫秒 → jiffies */
usecs_to_jiffies(100); /* 微秒 → jiffies(向上取整) */
jiffies_to_msecs(jiffies); /* jiffies → 毫秒 */
nsecs_to_jiffies(1000000UL); /* 纳秒 → jiffies */
jiffies是unsigned long类型,约49.7天回绕一次(HZ=1000时)。内核提供了安全的时间比较宏,自动处理回绕:
time_after(a, b); /* a 在 b 之后? */
time_before(a, b); /* a 在 b 之前? */
time_after_eq(a, b); /* a 在 b 之后或相等? */
time_before_eq(a, b);/* a 在 b 之前或相等? */
一条规则:永远不要用a > b比较jiffies,必须用上述宏。
二、低精度定时器(timer_list)
timer_list基于分层时间轮(Timer Wheel)实现,插入和删除复杂度O(1),精度受限于HZ(1~10ms),适合超时检测、看门狗等"大概率被取消"的场景。
2.1 数据结构
struct timer_list {
struct hlist_node entry; /* 时间轮桶链表 */
unsigned long expires; /* 绝对到期时间(jiffies) */
void (*function)(struct timer_list *); /* 回调函数 */
u32 flags; /* CPU亲和性 + TIMER_*标志 */
};
2.2 API用法
#include <linux/timer.h>
struct my_dev {
int counter;
struct timer_list tick_timer;
};
/* 回调函数:4.15+ 签名为 void (*)(struct timer_list *) */
static void tick_callback(struct timer_list *t)
{
/* from_timer:从timer_list反查包含它的父结构体 */
struct my_dev *dev = from_timer(dev, t, tick_timer);
dev->counter++;
printk("tick #%d\n", dev->counter);
/* 重新定时,实现周期触发 */
mod_timer(&dev->tick_timer, jiffies + msecs_to_jiffies(1000));
}
static int my_probe(struct platform_device *pdev)
{
struct my_dev *dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
/* 初始化定时器(4.15+ 替代了旧的 setup_timer) */
timer_setup(&dev->tick_timer, tick_callback, 0);
/* 首次启动:mod_timer 同时完成 add + 设定expires */
mod_timer(&dev->tick_timer, jiffies + msecs_to_jiffies(1000));
return 0;
}
static void my_remove(struct platform_device *pdev)
{
struct my_dev *dev = platform_get_drvdata(pdev);
/* 同步删除:等待回调执行完毕再返回 */
del_timer_sync(&dev->tick_timer);
}
2.3 API速查
| API | 功能 | 返回值 |
|---|---|---|
timer_setup(timer, cb, flags) |
初始化定时器并绑定回调 | 无 |
add_timer(timer) |
添加定时器到时间轮 | 无 |
mod_timer(timer, expires) |
修改/激活定时器 | 1=原pending,0=原不pending |
del_timer(timer) |
删除定时器(不等回调完成) | 1=已删除,0=未pending |
del_timer_sync(timer) |
同步删除(等回调完成) | 同del_timer |
timer_pending(timer) |
检查定时器是否pending | 布尔值 |
注意:
del_timer_sync()会睡眠,不能在中断上下文调用。6.2+ 内核新增timer_shutdown_sync(),删除后标记定时器为永久停用,后续mod_timer()静默忽略,适合驱动卸载场景。
三、高精度定时器(hrtimer)
hrtimer基于每CPU红黑树实现,精度可达纳秒级,直接编程硬件clockevent设备。适合音频处理、网络 pacing、实时控制等低延迟场景。
3.1 数据结构
struct hrtimer {
struct timerqueue_node node; /* 红黑树节点 + 到期时间 */
ktime_t _softexpires; /* 最早到期时间 */
enum hrtimer_restart (*function)(struct hrtimer *); /* 回调 */
struct hrtimer_clock_base *base; /* 所属时钟基 */
u8 state; /* INACTIVE / ENQUEUED */
u8 is_rel; /* 是否相对定时器 */
u8 is_soft; /* 是否软中断投递 */
u8 is_hard; /* 是否硬中断投递 */
};
3.2 API用法
#include <linux/hrtimer.h>
#include <linux/ktime.h>
struct my_dev {
struct hrtimer poll_timer;
};
static enum hrtimer_restart poll_callback(struct hrtimer *timer)
{
struct my_dev *dev = container_of(timer, struct my_dev, poll_timer);
/* 执行业务逻辑 */
do_poll(dev);
/* 前进到下一个到期时间,实现周期触发 */
hrtimer_forward_now(timer, ms_to_ktime(10));
return HRTIMER_RESTART; /* 重新调度 */
/* 若只需单次触发,返回 HRTIMER_NORESTART */
}
static int my_probe(struct platform_device *pdev)
{
struct my_dev *dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
/* 传统初始化方式(6.14之前) */
hrtimer_init(&dev->poll_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
dev->poll_timer.function = poll_callback;
/* 新版初始化方式(6.14+,推荐) */
/* hrtimer_setup(&dev->poll_timer, poll_callback,
CLOCK_MONOTONIC, HRTIMER_MODE_REL); */
/* 启动:10ms后触发 */
hrtimer_start(&dev->poll_timer, ms_to_ktime(10), HRTIMER_MODE_REL);
return 0;
}
static void my_remove(struct platform_device *pdev)
{
struct my_dev *dev = platform_get_drvdata(pdev);
hrtimer_cancel(&dev->poll_timer); /* 取消并等待回调完成 */
}
3.3 定时器模式
| 模式 | 含义 | 典型场景 |
|---|---|---|
HRTIMER_MODE_ABS |
绝对时间触发 | 定时闹钟 |
HRTIMER_MODE_REL |
相对当前时间触发 | 周期轮询 |
HRTIMER_MODE_PINNED |
绑定当前CPU | 避免迁移开销 |
HRTIMER_MODE_SOFT |
软中断上下文执行(4.16+) | 回调可稍延迟 |
HRTIMER_MODE_HARD |
硬中断上下文执行 | 最低延迟需求 |
模式可组合使用,如 HRTIMER_MODE_REL_SOFT 表示相对时间 + 软中断投递。
3.4 时钟基选择
| 时钟基 | 含义 | 说明 |
|---|---|---|
CLOCK_MONOTONIC |
单调递增时钟 | 不受NTP调整影响,最常用 |
CLOCK_REALTIME |
系统实时时钟 | 受settimeofday影响 |
CLOCK_BOOTTIME |
含休眠的单调时钟 | 休眠期间仍计时 |
CLOCK_TAI |
国际原子时 | 不含闰秒 |
约束:hrtimer回调默认在硬中断上下文执行,不能睡眠。如需在回调中执行可能睡眠的操作,使用
HRTIMER_MODE_SOFT(4.16+),回调在软中断上下文执行,但仍不能睡眠。如需睡眠,应结合workqueue使用。
四、延迟工作队列(delayed_work)
delayed_work是timer_list + workqueue的组合封装:到期后由定时器触发,将work投递到kworker线程执行。回调运行在进程上下文,可以睡眠,这是它和裸timer_list/hrtimer的根本区别。
4.1 数据结构
struct delayed_work {
struct work_struct work; /* 工作项(最终由kworker执行) */
struct timer_list timer; /* 内部定时器(仅负责延时投递) */
struct workqueue_struct *wq; /* 目标工作队列 */
int cpu; /* 目标CPU */
};
4.2 API用法
#include <linux/workqueue.h>
struct my_dev {
int count;
struct delayed_work dwork;
};
static void dwork_handler(struct work_struct *work)
{
struct my_dev *dev = container_of(work, struct my_dev, dwork.work);
dev->count++;
printk("delayed work #%d\n", dev->count);
/* 周期执行:在工作函数中重新调度 */
schedule_delayed_work(&dev->dwork, msecs_to_jiffies(500));
}
static int my_init(void)
{
struct my_dev *dev = kzalloc(sizeof(*dev), GFP_KERNEL);
/* 初始化delayed_work并绑定回调 */
INIT_DELAYED_WORK(&dev->dwork, dwork_handler);
/* 首次调度:500ms后执行 */
schedule_delayed_work(&dev->dwork, msecs_to_jiffies(500));
return 0;
}
static void my_exit(void)
{
/* 同步取消:等待正在执行的回调完成 */
cancel_delayed_work_sync(&dev->dwork);
kfree(dev);
}
4.3 API速查
| API | 功能 | 上下文限制 |
|---|---|---|
INIT_DELAYED_WORK(dwork, fn) |
初始化并绑定回调 | 任意 |
DECLARE_DELAYED_WORK(name, fn) |
静态声明+初始化 | 任意 |
schedule_delayed_work(dwork, delay) |
调度到system_wq | 任意 |
queue_delayed_work(wq, dwork, delay) |
调度到指定wq | 任意 |
cancel_delayed_work(dwork) |
异步取消 | 任意(含中断) |
cancel_delayed_work_sync(dwork) |
同步取消 | 进程上下文 |
注意:
schedule_delayed_work()的delay参数单位是jiffies,用msecs_to_jiffies()转换。若delay为0,立即投递到workqueue异步执行。
五、短延迟函数
当需要在代码中短暂等待时,内核提供两类延迟机制:忙等待和睡眠等待。
5.1 忙等待类(占用CPU)
#include <linux/delay.h>
udelay(us); /* 微秒级忙等待,基于BogoMIPS校准 */
ndelay(ns); /* 纳秒级忙等待,精度有限 */
mdelay(ms); /* 毫秒级忙等待,本质是udelay循环 */
udelay()基于启动时校准的BogoMIPS值做忙循环,精度受CPU频率和缓存影响- 仅用于微秒级短延迟,超过1ms应改用睡眠方式
- 可在中断上下文使用(不睡眠)
5.2 睡眠类(释放CPU)
#include <linux/delay.h>
msleep(ms); /* 不可中断的毫秒级睡眠 */
msleep_interruptible(ms); /* 可中断的毫秒级睡眠,返回剩余ms */
ssleep(sec); /* 秒级睡眠(不可中断) */
msleep()基于schedule_timeout_uninterruptible()实现- 不能在中断上下文使用(会睡眠)
- 实际睡眠时间可能略长于请求值
5.3 推荐选择
#include <linux/delay.h>
/* 5.10+ 统一延迟API,自动选择最优实现 */
fsleep(us); /* 内部自动判断:短延迟用udelay,长延迟用msleep */
六、对比表格
| 维度 | timer_list | hrtimer | delayed_work |
|---|---|---|---|
| 精度 | ~1/HZ(1~10ms) | 纳秒级 | ~1/HZ(1~10ms) |
| 数据结构 | 分层时间轮 | 红黑树 | 时间轮 + 工作队列 |
| 插入复杂度 | O(1) | O(log n) | O(1) |
| 回调上下文 | 软中断 | 硬中断/软中断 | 进程(kworker线程) |
| 回调能否睡眠 | 否 | 否 | 是 |
| 时间单位 | jiffies | ktime_t(纳秒) | jiffies |
| 周期重调度 | mod_timer | hrtimer_forward_now + RESTART | schedule_delayed_work |
| 典型场景 | 超时检测、看门狗 | 音频、实时控制 | 按键消抖、延时重试 |
| 适用内核版本 | 全版本 | 2.6.16+ | 全版本 |
七、流程图
7.1 定时机制选择决策树
7.2 hrtimer工作流程
7.3 短延迟函数选择流程
八、常见问题解决
Q1:del_timer_sync() 调用时内核崩溃?
del_timer_sync() 内部会睡眠等待回调完成,不能在中断上下文或持有自旋锁时调用。中断上下文应使用 del_timer(),但要注意回调可能仍在其他CPU上运行,存在use-after-free风险。
Q2:timer_setup() 和旧版 setup_timer() 的区别?
4.15起 timer_setup() 替代了 setup_timer()。变化:回调签名从 void (*func)(unsigned long data) 变为 void (*func)(struct timer_list *t),传参方式从 data 字段改为 from_timer() 宏反查父结构体。
Q3:hrtimer回调中能调用msleep吗?
不能。hrtimer回调默认在硬中断上下文执行,即使使用 HRTIMER_MODE_SOFT 也是软中断上下文,都不能睡眠。如需睡眠操作,应在回调中唤醒workqueue,在进程上下文中执行。
Q4:schedule_delayed_work的delay为0会怎样?
delay为0时,work立即投递到workqueue异步执行(不等当前函数返回)。这等价于 schedule_work(&dwork->work)。
Q5:udelay() 精度不准怎么办?
udelay() 基于BogoMIPS校准的忙循环,受CPU频率缩放和缓存影响。精度要求高的场景应使用hrtimer,或确保CPU频率锁定(禁用节能模式)。
Q6:6.2+内核应该用什么替代del_timer_sync?
6.2+ 新增 timer_shutdown_sync(),删除定时器后标记为永久停用,后续 mod_timer() 调用被静默忽略。驱动卸载时推荐使用,防止卸载后定时器被重新激活。5.x内核仍用 del_timer_sync()。
九、总结
内核定时与延迟机制的选择,看三个维度:精度需求、回调上下文限制、操作复杂度。毫秒级超时用timer_list,纳秒级实时用hrtimer,回调需睡眠用delayed_work,微秒忙等待用udelay。记住一条红线:中断上下文不能睡眠,del_timer_sync不能在中断中调用。下期拆解内核中断下半部机制。