
从0开始理解Linux时间管理jiffies到hrtimer的演进仓库已经开源所有教程主线内核移植跑新版本imx-linux/uboot都在这里或者一起来尝试跑7.0的Linux欢迎各位大佬观摩喜欢的话点个⭐仓库地址https://github.com/Awesome-Embedded-Learning-Studio/imx-forge静态网页https://awesome-embedded-learning-studio.github.io/imx-forge/前言内核中的时间是什么在用户空间编程时我们习惯了用sleep()、usleep()这样的函数来等待或者用gettimeofday()来获取当前时间。但在内核里这些都不能用。为什么因为内核是整个系统的管理者它需要更高精度、更低开销的时间管理机制。而且内核中的时间管理涉及到底层的硬件定时器、调度器、中断处理比用户空间复杂得多。老实说我刚开始写内核驱动的时候对时间管理一窍不通。我以为直接调用个delay()就行结果编译都过不了。后来我花了很长时间才理解内核中的时间管理是一套完整的体系从硬件定时器到软件定时器层层抽象。这一节我们从最基础的jiffies开始逐步理解内核是如何管理时间的。环境基于Linux 7.0-rc4项目版本/信息内核版本Linux 7.0-rc4 (主线内核)架构ARMv7-A (Cortex-A7)相关头文件include/linux/jiffies.h,include/linux/timer.hjiffies内核的心跳内核中有一个全局变量叫jiffies。它记录了系统启动以来经过的「滴答数」。你可以把它理解为内核的「心跳计数器」。每隔一段时间这个间隔由HZ决定硬件定时器会产生一个中断jiffies就会加1。HZ每秒的滴答数HZ是一个宏定义表示每秒有多少次滴答。在我们的ARM Linux系统中通常HZ100或HZ200。/* 常见的HZ值 */#defineHZ100/* 每秒100次滴答即每10ms一次 */#defineHZ200/* 每秒200次滴答即每5ms一次 */#defineHZ1000/* 每秒1000次滴答即每1ms一次 */为什么不能无限提高HZHZ越高时间精度越高但开销也越大更多的定时器中断更频繁的调度器运行更多的功耗jiffies的精度限制由于jiffies是基于滴答数的它的精度受限于HZ值。HZ滴答间隔精度10010ms10ms2005ms5ms10001ms1ms如果你的应用需要微秒级的精度jiffies就不够用了。这时候需要用高精度定时器hrtimer。时间单位转换内核提供了一组宏来转换时间单位#includelinux/jiffies.h/* 毫秒转jiffies */unsignedlongjmsecs_to_jiffies(100);/* 100ms *//* 秒转jiffies */jmsecs_to_jiffies(1000);/* 1秒 *//* 微秒转jiffies注意精度损失 */jusecs_to_jiffies(1000);/* 1ms *//* jiffies转毫秒用于打印 */unsignedintmsjiffies_to_msecs(j);jiffies的回绕问题jiffies是一个32位或64位的无符号整数。当它达到最大值后会回绕到0。32位jiffies 0xFFFFFFFF → 0x00000000 大约每49.7天回绕一次HZ100时这就是问题如果你直接比较两个jiffies值可能会在回绕时出错。/* ❌ 错误的比较方式 */if(timeoutjiffies){/* 在回绕时这个判断会出错 */}/* ✓ 正确的比较方式 */if(time_after(jiffies,timeout)){/* 这个宏正确处理了回绕 */}if(time_before(jiffies,timeout)){/* 同样正确 */}时间比较宏宏描述time_after(a, b)a b正确处理回绕time_before(a, b)a b正确处理回绕time_after_eq(a, b)a btime_before_eq(a, b)a b内核定时器在指定时间执行回调内核定时器是内核中最常用的延时执行机制。它允许你在指定的时间后执行一个回调函数。定时器的结构在Linux 7.0中定时器用timer_list结构体表示structtimer_list{/* 内部字段 */structhlist_nodeentry;unsignedlongexpires;void(*function)(structtimer_list*timer);u32 flags;/* ... */};Linux 7.0的重大变化⚠️ 重要如果你有旧的4.x内核代码需要注意以下变化init_timer()已废弃必须使用timer_setup()data字段已移除必须使用from_timer()宏获取包含结构体del_timer_sync()被timer_delete_sync()替代虽然旧名字仍可用新的定时器API函数描述timer_setup(timer, callback, flags)初始化定时器void (*callback)(struct timer_list *)定时器回调函数签名add_timer(timer)激活定时器mod_timer(timer, expires)修改定时器的过期时间timer_delete(timer)删除定时器可能返回还在运行timer_delete_sync(timer)删除定时器并等待回调完成timer_pending(timer)检测定时器是否 pending定时器标志#defineTIMER_DEFERRABLE0x00080000/* 可延迟定时器 */#defineTIMER_PINNED0x00100000/* 固定在当前CPU */#defineTIMER_IRQSAFE0x00200000/* 中断安全定时器 */TIMER_DEFERRABLE系统空闲时不会唤醒CPUTIMER_PINNED定时器总是在指定的CPU上执行TIMER_IRQSAFE回调在中断关闭的情况下执行定时器示例#includelinux/timer.h#includelinux/jiffies.hstructmy_device{structtimer_listtimer;intcounter;/* ... 其他成员 ... */};/* 定时器回调函数 */staticvoidmy_timer_callback(structtimer_list*timer){/* 使用container_of或from_timer获取包含结构体 */structmy_device*devfrom_timer(dev,timer,timer);dev-counter;pr_info(Timer fired, counter %d\n,dev-counter);/* 如果需要周期性执行重新设置定时器 */mod_timer(dev-timer,jiffiesmsecs_to_jiffies(1000));}/* 初始化定时器 */staticintmy_device_init(structmy_device*dev){/* ✓ Linux 7.0的正确方式 */timer_setup(dev-timer,my_timer_callback,0);/* 设置1秒后过期 */dev-timer.expiresjiffiesmsecs_to_jiffies(1000);/* 激活定时器 */add_timer(dev-timer);return0;}/* 清理定时器 */staticvoidmy_device_cleanup(structmy_device*dev){/* 删除定时器并等待回调完成 */timer_delete_sync(dev-timer);}旧API vs 新API对比/* ❌ 旧APILinux 4.14之前 */structtimer_listmy_timer;voidcallback(unsignedlongdata){structmy_device*dev(structmy_device*)data;/* ... */}init_timer(my_timer);my_timer.data(unsignedlong)dev;my_timer.functioncallback;my_timer.expiresjiffiesmsecs_to_jiffies(1000);add_timer(my_timer);/* ✓ 新APILinux 4.14包括7.0 */structtimer_listmy_timer;voidcallback(structtimer_list*timer){structmy_device*devfrom_timer(dev,timer,timer);/* ... */}timer_setup(my_timer,callback,0);my_timer.expiresjiffiesmsecs_to_jiffies(1000);add_timer(my_timer);高精度定时器hrtimer当你需要微秒甚至纳秒级的精度时jiffies和timer_list就不够用了。这时候需要用高精度定时器hrtimer。hrtimer的特点纳秒级精度基于高精度硬件时钟如ARM的定时器不依赖HZ配置hrtimer API简介#includelinux/hrtimer.henumhrtimer_restartcallback(structhrtimer*timer);structhrtimer{/* ... */};/* 初始化hrtimer */voidhrtimer_init(structhrtimer*timer,clockid_tclock_id,enumhrtimer_modemode);/* 启动hrtimer */voidhrtimer_start(structhrtimer*timer,ktime_ttime,constenumhrtimer_modemode);/* 取消hrtimer */inthrtimer_cancel(structhrtimer*timer);clock_id选项CLOCK_MONOTONIC单调时钟不受系统时间调整影响CLOCK_REALTIME实时时钟可能被NTP调整CLOCK_BOOTTIME包含休眠时间的单调时钟⚠️ 注意hrtimer的使用比较复杂通常驱动代码用timer_list就够了。只有在需要极高精度时才考虑hrtimer。短延迟忙等待有时候你需要极短的延迟比如微秒级而且不需要很精确。内核提供了一些忙等待函数#includelinux/delay.h/* 忙等待指定的纳秒/微秒/毫秒数 */voidndelay(unsignedlongnsecs);/* 纳秒延迟 */voidudelay(unsignedlongusecs);/* 微秒延迟 */voidmdelay(unsignedlongmsecs);/* 毫秒延迟 */⚠️ 注意这些函数会占用CPU是忙等待只在极短延迟时使用通常小于1ms较长延迟应该用定时器或睡眠函数睡眠延迟让出CPU如果你的延迟时间较长毫秒级或更长应该让出CPU让其他进程运行。可中断睡眠#includelinux/delay.h/* 睡眠指定毫秒数可被信号中断 */voidmsleep(unsignedintmsecs);/* 睡眠指定毫秒数不可中断 */voidmsleep_interruptible(unsignedintmsecs);/* 睡眠指定微秒数上限2000us */voidusleep_range(unsignedlongmin,unsignedlongmax);usleep_range推荐的选择usleep_range()是现代内核推荐的高精度睡眠函数/* 睡眠100-150微秒 */usleep_range(100,150);它给出一个范围让调度器可以在范围内选择最佳唤醒时间有助于省电和减少调度开销。等待事件有时候你需要等待某个条件成立而不是固定的延迟。这时候用等待队列#includelinux/wait.h/* 等待条件成立超时时间为jiffies */wait_event_timeout(wait_queue_head_twq,condition,timeout);/* 可中断版本 */wait_event_interruptible_timeout(wq,condition,timeout);实战在驱动中使用定时器让我们用一个实际例子来总结这些时间管理机制。场景LED闪烁驱动#includelinux/module.h#includelinux/timer.h#includelinux/gpio/consumer.h#includelinux/platform_device.hstructblink_led{structgpio_desc*gpio;structtimer_listtimer;bool led_on;unsignedlonginterval_ms;};staticvoidblink_timer_callback(structtimer_list*timer){structblink_led*bledfrom_timer(bled,timer,timer);/* 切换LED状态 */bled-led_on!bled-led_on;gpiod_set_value(bled-gpio,bled-led_on);/* 重新设置定时器 */mod_timer(bled-timer,jiffiesmsecs_to_jiffies(bled-interval_ms));}staticintblink_led_probe(structplatform_device*pdev){structblink_led*bled;bleddevm_kzalloc(pdev-dev,sizeof(*bled),GFP_KERNEL);if(!bled)return-ENOMEM;/* 获取GPIO */bled-gpiodevm_gpiod_get(pdev-dev,NULL,GPIOD_OUT_LOW);if(IS_ERR(bled-gpio))returnPTR_ERR(bled-gpio);/* 初始化定时器 */bled-interval_ms500;/* 500ms闪烁 */bled-led_onfalse;timer_setup(bled-timer,blink_timer_callback,0);/* 启动定时器 */bled-timer.expiresjiffiesmsecs_to_jiffies(bled-interval_ms);add_timer(bled-timer);platform_set_drvdata(pdev,bled);pr_info(Blink LED driver loaded\n);return0;}staticintblink_led_remove(structplatform_device*pdev){structblink_led*bledplatform_get_drvdata(pdev);/* 删除定时器 */timer_delete_sync(bled-timer);pr_info(Blink LED driver unloaded\n);return0;}/* ... platform_driver定义 ... */时间管理决策树需要延时/定时 ├─ 极短延迟10微秒 │ └─ 用 ndelay/udelay忙等待 ├─ 短延迟10us - 1ms │ └─ 用 usleep_range睡眠 ├─ 中等延迟1ms - 1秒 │ └─ 用 msleep 或 timer_list ├─ 长延迟1秒 │ └─ 用 timer_list 或 wait_queue └─ 高精度需求1us └─ 用 hrtimer这一小节就到这里Linux内核的时间管理是一套完整的体系从基于滴答的jiffies到高精度的hrtimer满足不同场景的需求。对于大多数驱动代码短延迟用usleep_range()定时任务用timer_list极高精度才用hrtimer下一节我们会在实战中深入使用定时器写一个完整的定时器驱动示例。本章要点jiffies是内核的心跳计数器精度受HZ限制通常5-10ms。时间比较必须用宏time_after等因为jiffies会回绕。Linux 7.0使用新的timer APItimer_setup()替代init_timer()from_timer()替代data字段。timer_delete_sync()替代del_timer_sync()确保回调完成后才返回。忙等待udelay用于极短延迟睡眠usleep_range/msleep用于较长延迟。hrtimer提供纳秒级精度但使用复杂大多数驱动用timer_list足够。相关阅读嵌入式Linux嵌入式Linux驱动开发设备树驱动改造——从硬编码到设备树的实战之旅 - 相似度 100%嵌入式Linux驱动开发pinctrl篇1——从寄存器到子系统驱动演进之路 - 相似度 100%通用GUI编程技术——图形渲染实战四十五——D3D12资源与堆管理从上传到驻留 - 相似度 100%