news 2026/6/7 19:09:16

从0开始理解Linux时间管理:jiffies到hrtimer的演进

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从0开始理解Linux时间管理:jiffies到hrtimer的演进

从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.h

jiffies:内核的心跳

内核中有一个全局变量,叫jiffies。它记录了系统启动以来经过的「滴答数」。

你可以把它理解为内核的「心跳计数器」。每隔一段时间(这个间隔由HZ决定),硬件定时器会产生一个中断,jiffies就会加1。

HZ:每秒的滴答数

HZ是一个宏定义,表示每秒有多少次滴答。在我们的ARM Linux系统中,通常HZ=100HZ=200

/* 常见的HZ值 */#defineHZ100/* 每秒100次滴答,即每10ms一次 */#defineHZ200/* 每秒200次滴答,即每5ms一次 */#defineHZ1000/* 每秒1000次滴答,即每1ms一次 */

为什么不能无限提高HZ?

HZ越高,时间精度越高,但开销也越大:

  • 更多的定时器中断
  • 更频繁的调度器运行
  • 更多的功耗

jiffies的精度限制

由于jiffies是基于滴答数的,它的精度受限于HZ值。

HZ滴答间隔精度
10010ms10ms
2005ms5ms
10001ms1ms

如果你的应用需要微秒级的精度,jiffies就不够用了。这时候需要用高精度定时器(hrtimer)。

时间单位转换

内核提供了一组宏来转换时间单位:

#include<linux/jiffies.h>/* 毫秒转jiffies */unsignedlongj=msecs_to_jiffies(100);/* 100ms *//* 秒转jiffies */j=msecs_to_jiffies(1000);/* 1秒 *//* 微秒转jiffies(注意精度损失) */j=usecs_to_jiffies(1000);/* 1ms *//* jiffies转毫秒(用于打印) */unsignedintms=jiffies_to_msecs(j);

jiffies的回绕问题

jiffies是一个32位或64位的无符号整数。当它达到最大值后,会回绕到0。

32位jiffies: 0xFFFFFFFF → 0x00000000 大约每49.7天回绕一次(HZ=100时)

这就是问题:如果你直接比较两个jiffies值,可能会在回绕时出错。

/* ❌ 错误的比较方式 */if(timeout<jiffies){/* 在回绕时,这个判断会出错! */}/* ✓ 正确的比较方式 */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 >= b
time_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内核代码,需要注意以下变化:

  1. init_timer()已废弃,必须使用timer_setup()
  2. data字段已移除,必须使用from_timer()宏获取包含结构体
  3. 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:系统空闲时不会唤醒CPU
  • TIMER_PINNED:定时器总是在指定的CPU上执行
  • TIMER_IRQSAFE:回调在中断关闭的情况下执行

定时器示例

#include<linux/timer.h>#include<linux/jiffies.h>structmy_device{structtimer_listtimer;intcounter;/* ... 其他成员 ... */};/* 定时器回调函数 */staticvoidmy_timer_callback(structtimer_list*timer){/* 使用container_of或from_timer获取包含结构体 */structmy_device*dev=from_timer(dev,timer,timer);dev->counter++;pr_info("Timer fired, counter = %d\n",dev->counter);/* 如果需要周期性执行,重新设置定时器 */mod_timer(&dev->timer,jiffies+msecs_to_jiffies(1000));}/* 初始化定时器 */staticintmy_device_init(structmy_device*dev){/* ✓ Linux 7.0的正确方式 */timer_setup(&dev->timer,my_timer_callback,0);/* 设置1秒后过期 */dev->timer.expires=jiffies+msecs_to_jiffies(1000);/* 激活定时器 */add_timer(&dev->timer);return0;}/* 清理定时器 */staticvoidmy_device_cleanup(structmy_device*dev){/* 删除定时器并等待回调完成 */timer_delete_sync(&dev->timer);}

旧API vs 新API对比

/* ❌ 旧API(Linux 4.14之前) */structtimer_listmy_timer;voidcallback(unsignedlongdata){structmy_device*dev=(structmy_device*)data;/* ... */}init_timer(&my_timer);my_timer.data=(unsignedlong)dev;my_timer.function=callback;my_timer.expires=jiffies+msecs_to_jiffies(1000);add_timer(&my_timer);/* ✓ 新API(Linux 4.14+,包括7.0) */structtimer_listmy_timer;voidcallback(structtimer_list*timer){structmy_device*dev=from_timer(dev,timer,timer);/* ... */}timer_setup(&my_timer,callback,0);my_timer.expires=jiffies+msecs_to_jiffies(1000);add_timer(&my_timer);

高精度定时器:hrtimer

当你需要微秒甚至纳秒级的精度时,jiffiestimer_list就不够用了。这时候需要用高精度定时器(hrtimer)。

hrtimer的特点

  • 纳秒级精度
  • 基于高精度硬件时钟(如ARM的定时器)
  • 不依赖HZ配置

hrtimer API(简介)

#include<linux/hrtimer.h>enumhrtimer_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。

短延迟:忙等待

有时候,你需要极短的延迟(比如微秒级),而且不需要很精确。内核提供了一些忙等待函数:

#include<linux/delay.h>/* 忙等待指定的纳秒/微秒/毫秒数 */voidndelay(unsignedlongnsecs);/* 纳秒延迟 */voidudelay(unsignedlongusecs);/* 微秒延迟 */voidmdelay(unsignedlongmsecs);/* 毫秒延迟 */

⚠️ 注意

  • 这些函数会占用CPU,是忙等待
  • 只在极短延迟时使用(通常小于1ms)
  • 较长延迟应该用定时器或睡眠函数

睡眠延迟:让出CPU

如果你的延迟时间较长(毫秒级或更长),应该让出CPU,让其他进程运行。

可中断睡眠

#include<linux/delay.h>/* 睡眠指定毫秒数(可被信号中断) */voidmsleep(unsignedintmsecs);/* 睡眠指定毫秒数(不可中断) */voidmsleep_interruptible(unsignedintmsecs);/* 睡眠指定微秒数(上限2000us) */voidusleep_range(unsignedlongmin,unsignedlongmax);

usleep_range:推荐的选择

usleep_range()是现代内核推荐的高精度睡眠函数:

/* 睡眠100-150微秒 */usleep_range(100,150);

它给出一个范围,让调度器可以在范围内选择最佳唤醒时间,有助于省电和减少调度开销。

等待事件

有时候,你需要等待某个条件成立,而不是固定的延迟。这时候用等待队列:

#include<linux/wait.h>/* 等待条件成立,超时时间为jiffies */wait_event_timeout(wait_queue_head_twq,condition,timeout);/* 可中断版本 */wait_event_interruptible_timeout(wq,condition,timeout);

实战:在驱动中使用定时器

让我们用一个实际例子来总结这些时间管理机制。

场景:LED闪烁驱动

#include<linux/module.h>#include<linux/timer.h>#include<linux/gpio/consumer.h>#include<linux/platform_device.h>structblink_led{structgpio_desc*gpio;structtimer_listtimer;bool led_on;unsignedlonginterval_ms;};staticvoidblink_timer_callback(structtimer_list*timer){structblink_led*bled=from_timer(bled,timer,timer);/* 切换LED状态 */bled->led_on=!bled->led_on;gpiod_set_value(bled->gpio,bled->led_on);/* 重新设置定时器 */mod_timer(&bled->timer,jiffies+msecs_to_jiffies(bled->interval_ms));}staticintblink_led_probe(structplatform_device*pdev){structblink_led*bled;bled=devm_kzalloc(&pdev->dev,sizeof(*bled),GFP_KERNEL);if(!bled)return-ENOMEM;/* 获取GPIO */bled->gpio=devm_gpiod_get(&pdev->dev,NULL,GPIOD_OUT_LOW);if(IS_ERR(bled->gpio))returnPTR_ERR(bled->gpio);/* 初始化定时器 */bled->interval_ms=500;/* 500ms闪烁 */bled->led_on=false;timer_setup(&bled->timer,blink_timer_callback,0);/* 启动定时器 */bled->timer.expires=jiffies+msecs_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*bled=platform_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

下一节,我们会在实战中深入使用定时器,写一个完整的定时器驱动示例。


本章要点

  1. jiffies是内核的心跳计数器,精度受HZ限制(通常5-10ms)。
  2. 时间比较必须用宏time_after等),因为jiffies会回绕。
  3. Linux 7.0使用新的timer APItimer_setup()替代init_timer()from_timer()替代data字段。
  4. timer_delete_sync()替代del_timer_sync(),确保回调完成后才返回。
  5. 忙等待(udelay)用于极短延迟,睡眠(usleep_range/msleep)用于较长延迟。
  6. hrtimer提供纳秒级精度,但使用复杂,大多数驱动用timer_list足够。

相关阅读

  1. 嵌入式Linux嵌入式Linux驱动开发:设备树驱动改造——从硬编码到设备树的实战之旅 - 相似度 100%
  2. 嵌入式Linux驱动开发pinctrl篇(1)——从寄存器到子系统:驱动演进之路 - 相似度 100%
  3. 通用GUI编程技术——图形渲染实战(四十五)——D3D12资源与堆管理:从上传到驻留 - 相似度 100%
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/7 19:02:44

Eclipse一键导入就能跑的百度地图JS集成工程(含定位/标注/路线)

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;直接在Eclipse里导入就能编译运行的百度地图前端实战项目&#xff0c;结构完整&#xff1a;包含WebContent标准目录、WEB-INF配置、js功能脚本、jsp页面、image资源和index.html入口。已预设好.classpath构建路…

作者头像 李华
网站建设 2026/6/7 18:55:46

Notepad-- 终极指南:5个技巧让你成为跨平台文本编辑高手

Notepad-- 终极指南&#xff1a;5个技巧让你成为跨平台文本编辑高手 【免费下载链接】notepad-- 一个支持windows/linux/mac的文本编辑器&#xff0c;目标是做中国人自己的编辑器&#xff0c;来自中国。 项目地址: https://gitcode.com/GitHub_Trending/no/notepad-- No…

作者头像 李华
网站建设 2026/6/7 18:51:37

微信数据守护者:WechatBakTool带你轻松备份珍贵聊天记录

微信数据守护者&#xff1a;WechatBakTool带你轻松备份珍贵聊天记录 【免费下载链接】WechatBakTool 基于C#的微信PC版聊天记录备份工具&#xff0c;提供图形界面&#xff0c;解密微信数据库并导出聊天记录。 项目地址: https://gitcode.com/gh_mirrors/we/WechatBakTool …

作者头像 李华
网站建设 2026/6/7 18:46:47

RISC-V处理器设计:从模块化指令集到工程实践的精简之道

1. 从“复杂”到“简单”&#xff1a;为什么我们需要重新审视处理器设计 在嵌入式开发、芯片设计甚至物联网硬件领域摸爬滚打十几年&#xff0c;我经手过不少架构的处理器&#xff0c;从早期的8051、AVR&#xff0c;到后来的ARM Cortex-M系列&#xff0c;再到复杂的x86应用处理…

作者头像 李华