定时器与延时:内核定时器、hrtimer 与延时函数

发布时间:2026/7/10 14:30:37
定时器与延时:内核定时器、hrtimer 与延时函数 定时器与延时内核定时器、hrtimer 与延时函数驱动里经常需要定时执行任务、等待硬件就绪。这篇讲 Linux 内核的定时机制低精度定时器 timer_list、高精度定时器 hrtimer、以及各种延时函数的区别和用法。大家好我是黒漂技术佬。驱动开发里定时和延时很常见定时轮询传感器、LED 闪烁、等待硬件寄存器就绪、超时检测……都要用。Linux 内核里有两套定时器低精度的 timer_list基于 jiffies毫秒级和高精度的 hrtimer纳秒级。还有各种延时函数忙等待和睡眠延时要分清。这篇讲定时器和延时的用法和区别。一、jiffies 和 HZ概念内核有个全局滴答计数器jiffies每次时钟中断加 1。HZ是每秒的滴答数也就是时钟中断的频率。常见值100、250、300、1000。HZ1000 → 每1ms一次中断jiffies 每秒加 1000 HZ100 → 每10ms一次中断jiffies 的类型#includelinux/jiffies.hexternunsignedlongvolatilejiffies;时间转换// 毫秒转 jiffiesmsecs_to_jiffies(100)// 100ms 等于多少个 jiffies// 微秒转 jiffiesusecs_to_jiffies(500)// jiffies 转毫秒jiffies_to_msecs(j)// 秒转 jiffiesHZ*2// 2秒时间比较jiffies 会溢出不能直接比大小要用专门的宏time_after(a,b)// a 在 b 之后time_before(a,b)// a 在 b 之前time_after_eq(a,b)time_before_eq(a,b)超时判断unsignedlongtimeoutjiffiesHZ;// 1秒后超时// 循环等待超时退出while(!is_ready()){if(time_after(jiffies,timeout)){return-ETIMEDOUT;}}二、低精度定时器timer_list特点基于 jiffies精度取决于 HZ一般毫秒级软中断上下文执行到期后在时钟中断底半部处理不能睡眠简单易用大部分场景够用定义#includelinux/timer.hstructtimer_listmy_timer;初始化voidtimer_func(structtimer_list*t);timer_setup(my_timer,timer_func,0);老内核用 init_timer .function 赋值新内核用 timer_setup。启动定时器// 从现在起 expires 个 jiffies 后到期mod_timer(my_timer,jiffiesmsecs_to_jiffies(1000));删除定时器del_timer(my_timer);del_timer_sync(my_timer);// 等正在执行的处理函数完成安全推荐卸载时用处理函数voidtimer_func(structtimer_list*t){structmy_dev*devfrom_timer(dev,t,timer);// 类似container_of// 处理定时任务do_something(dev);// 如果需要周期性触发重新启动mod_timer(dev-timer,jiffiesmsecs_to_jiffies(1000));}from_timer 宏从 timer_list 指针拿到外层结构体和 container_of 类似from_timer(外层指针变量名,timer指针,成员名)完整示例周期性定时器structmy_dev{structtimer_listtimer;intcount;};staticvoidmy_timer_func(structtimer_list*t){structmy_dev*devfrom_timer(dev,t,timer);dev-count;pr_info(定时器触发第%d次\n,dev-count);// 重新启动实现周期性mod_timer(dev-timer,jiffiesmsecs_to_jiffies(1000));}// 初始化structmy_dev*dev;timer_setup(dev-timer,my_timer_func,0);mod_timer(dev-timer,jiffiesmsecs_to_jiffies(1000));// 卸载时del_timer_sync(dev-timer);注意事项定时器只触发一次要周期性的话在处理函数里重新 mod_timer软中断上下文不能睡眠精度不高毫秒级误差跟 HZ 和系统负载有关删除要同步卸载模块用 del_timer_sync确保处理函数执行完了再删三、高精度定时器hrtimer特点高精度纳秒级基于硬件时钟不依赖 jiffies可以支持比 HZ 更高的精度适合对定时精度要求高的场景音视频、PWM 模拟等时间单位ktime_t#includelinux/ktime.hktime_tt;// 常用构造ktime_set(sec,nsec)// 秒纳秒ms_to_ktime(ms)// 毫秒ns_to_ktime(ns)// 纳秒ktime_get()// 当前时间定义#includelinux/hrtimer.hstructhrtimermy_hrtimer;初始化enumhrtimer_restartmy_hrtimer_func(structhrtimer*timer);hrtimer_init(my_hrtimer,CLOCK_MONOTONIC,HRTIMER_MODE_REL);my_hrtimer.functionmy_hrtimer_func;时钟类型CLOCK_MONOTONIC单调时间系统启动后一直递增常用CLOCK_REALTIME墙上时间可以被修改模式HRTIMER_MODE_REL相对时间从现在开始算HRTIMER_MODE_ABS绝对时间启动hrtimer_start(my_hrtimer,ms_to_ktime(100),HRTIMER_MODE_REL);取消hrtimer_cancel(my_hrtimer);处理函数enumhrtimer_restartmy_hrtimer_func(structhrtimer*timer){structmy_dev*devcontainer_of(timer,structmy_dev,hrtimer);do_something(dev);// 重新启动周期性hrtimer_forward_now(timer,ms_to_ktime(100));returnHRTIMER_RESTART;// 或者只触发一次// return HRTIMER_NORESTART;}返回值HRTIMER_RESTART重新启动HRTIMER_NORESTART不重启注意也是中断上下文软中断不能睡眠精度高但系统开销也大一点普通驱动用 timer_list 就够了精度要求高再用 hrtimer四、延时函数驱动里经常要等一小段时间比如写寄存器后等硬件就绪。1. 忙等待忙循环CPU 一直等不释放 CPU。中断上下文里只能用这个因为不能睡眠。ndelay / udelay / mdelay// 纳秒级忙等ndelay(100);// 等100纳秒// 微秒级忙等udelay(50);// 等50微秒// 毫秒级忙等mdelay(10);// 等10毫秒特点忙等待占着 CPU任何上下文都能用包括中断短延时一般毫秒以内用长时间忙等浪费 CPU不推荐2. 睡眠延时让出 CPU进程上下文可以用睡眠期间 CPU 干别的去了。msleepmsleep(100);// 睡眠100毫秒至少睡这么久可能更长精度不高进程上下文用msleep_interruptiblemsleep_interruptible(100);可以被信号打断的睡眠。ssleepssleep(2);// 睡眠2秒秒级的 msleep 封装。3. 等待队列延时配合等待队列等事件或者超时wait_event_interruptible_timeout(wq,condition,msecs_to_jiffies(1000));条件满足了就返回超时也返回。最常用的等待方式。延时函数选型函数上下文精度CPU占用适用ndelay/udelay任意高忙等占CPU短延时微秒级、中断上下文mdelay任意一般忙等占CPU短延时毫秒级、中断上下文msleep进程低释放CPU长延时、不要求精确usleep_range进程较高释放CPU微秒级睡眠延时wait_event_timeout进程一般释放CPU等事件超时原则中断上下文 / 不能睡眠的场景用 udelay/mdelay 忙等进程上下文、时间较长用 msleep 睡眠别忙等浪费 CPU等某个条件用等待队列 超时别自己循环查五、超时检测模式模式 1忙等超时短时间unsignedlongtimeoutjiffiesmsecs_to_jiffies(100);while(!hw_ready()){if(time_after(jiffies,timeout)){return-ETIMEDOUT;}cpu_relax();// 让出一点减少总线压力}很短时间的等待用这个。模式 2睡眠等待超时进程上下文if(wait_event_interruptible_timeout(wait_q,ready_flag,msecs_to_jiffies(1000))0){return-ETIMEDOUT;// 超时了}配合等待队列用最常用。模式 3定时器 等待队列异步超时定时器到了设置超时标志唤醒等待队列。六、内核延时的精度问题为什么 msleep 不准msleep 是基于 jiffies 的至少睡你指定的时间可能多睡一点刚好错过一个 tick就多睡一个 tick系统负载高调度不及时要高精度短延时用 usleep_range 或者 udelay。HZ 越低精度越差HZ100 的内核msleep(1) 可能睡 10ms。嵌入式系统一般 HZ100 或 250。hrtimer 精度高真正需要高精度定时用 hrtimer硬件支持的话能到微秒甚至纳秒级。七、常见坑坑 1中断上下文用 msleepmsleep 会睡眠中断上下文里用直接 panic。中断里要用 udelay/mdelay 忙等。坑 2长时间用 mdelay 忙等mdelay(1000) 忙等 1 秒CPU 被占死了系统都卡了。长延时必须用 msleep进程上下文。坑 3jiffies 直接比较大小jiffies 溢出后会回绕直接if (jiffies timeout)在溢出边界会错。要用 time_after/time_before 宏。坑 4定时器处理函数里睡眠timer_list 和 hrtimer 都是软中断上下文不能睡眠。要睡眠的活放 workqueue 里。坑 5卸载模块没删定时器模块卸载了定时器还在到期了访问已经释放的内存直接 panic。卸载一定要 del_timer_sync 或 hrtimer_cancel。坑 6mod_timer 重复启动定时器已经在运行也可以 mod_timer会重新设置到期时间不会叠加。八、本篇小结jiffies内核滴答计数器HZ 是每秒滴答数低精度定时的基础time_after / time_before安全比较 jiffies防止溢出错误timer_list低精度定时器毫秒级软中断上下文不能睡眠简单常用hrtimer高精度定时器纳秒级适合精度要求高的场景延时函数分两类忙等udelay/mdelay任意上下文和睡眠msleep进程上下文原则短时间/中断用忙等长时间/进程用睡眠等事件用等待队列超时定时器处理函数不能睡眠要睡眠的活放 workqueue卸载模块一定要删除定时器防止悬空指针下一篇讲并发与同步自旋锁、信号量、互斥锁驱动里怎么处理多核并发问题。我是黒漂技术佬。