[toc]
kernel/time/jiffies.c 内核心跳(Kernel Heartbeat) 低分辨率定时器的基础
历史与背景
这项技术是为了解决什么特定问题而诞生的?
jiffies
是Linux内核中最古老、最基础的时间测量机制。它的诞生是为了解决内核内部需要一个**简单、廉价、全局统一的“心跳”或“滴答(tick)”**来驱动各种基于时间的活动。
在操作系统内核中,大量活动都不是由外部事件触发,而是需要在一个相对的时间点后发生。例如:
- 超时(Timeouts):一个网络驱动发送了一个数据包,它需要在一个超时时间(如200毫秒)后检查是否收到了确认。
- 调度(Scheduling):一个
SCHED_RR
(轮转)策略的进程,其运行时间片用完后需要被抢占。 - 周期性任务(Periodic Tasks):某些内核任务需要周期性地运行。
- 简单的延迟(Delays):代码中需要一个短暂的、非精确的延迟。
jiffies
提供了一个极其简单的解决方案:它是一个在系统启动后不断递增的全局计数器。通过读取这个计数器的值,内核的任何部分都可以获得一个关于“时间流逝”的基本概念,并以此来安排未来的事件。
它的发展经历了哪些重要的里程碑或版本迭代?
jiffies
的发展与内核定时器子系统的演进紧密相连。
- HZ 常量与周期性中断:
jiffies
的核心是与一个名为HZ
的编译时常量绑定的。HZ
代表每秒钟硬件定时器中断发生的次数,也就是jiffies
计数器每秒增加的数值。HZ
的值经历过多次变化(如100, 250, 1000),反映了在定时器精度和中断开销之间的权衡。 - 32位回绕(Wraparound)问题:在32位系统上,
jiffies
是一个unsigned long
(32位)变量。它会以一个可预测的时间间隔(例如,在HZ=100
时,大约497天后)溢出并从0重新开始。这是一个严重的问题,因为简单地比较jiffies
的值来判断时间是否到期会出错。为了解决这个问题,内核引入了一组宏,如time_after(a, b)
和time_before(a, b)
。这些宏使用特殊的有符号整数算术来正确处理回绕,是正确使用jiffies
的关键。 jiffies_64
的引入:为了从根本上解决回绕问题,内核引入了一个64位的全局变量jiffies_64
。在64位系统上,jiffies
就是jiffies_64
的别名,其回绕周期长得在宇宙生命周期内都不会发生。在32位系统上,jiffies
仍然是32位的(为了性能和兼容性),但可以通过get_jiffies_64()
安全地读取完整的64位值。- 角色的转变:随着高精度定时器(High-Resolution Timers, hrtimers)的引入,
jiffies
的角色从内核唯一的定时器机制,转变为低分辨率、低开销的定时器基础。对于需要微秒级或更高精度的应用,现在都使用hrtimers
。 - 无滴答内核(Tickless Kernel,
NO_HZ
):在空闲(idle)状态下,周期性的时钟中断会不必要地唤醒CPU,消耗电力。Tickless内核允许在CPU空闲时停止这个周期性中断。这对jiffies
的更新机制产生了影响:当CPU从长时间的空闲中被唤醒时,jiffies
的值需要被一次性地“追赶”上已经流逝的真实时间。
目前该技术的社区活跃度和主流应用情况如何?
jiffies
是内核中一个极其稳定和成熟的“遗留但基础”的组件。
- 主流应用:尽管有了
hrtimers
,jiffies
仍然被广泛用于所有不需要高精度定时的场景,因为它非常廉价。主要应用包括:- 内核定时器轮(Timer Wheels):
struct timer_list
所代表的传统内核定时器,其超时值就是基于jiffies
的。 - 网络协议栈:大量的超时,如TCP的重传定时器,都是基于
jiffies
。 - 调度器:
SCHED_RR
的时间片。 - 驱动程序:许多硬件的超时逻辑(如等待设备响应)并不需要高精度。
- 内核定时器轮(Timer Wheels):
核心原理与设计
它的核心工作原理是什么?
jiffies
的原理极其简单:
- 硬件定时器中断:系统中的一个硬件定时器被编程为以
HZ
赫兹的频率周期性地触发中断。 - 中断服务程序:每次这个中断发生时,其中断服务程序会调用
tick_periodic()
,最终会执行do_timer()
函数。 - 全局变量递增:在
do_timer()
中,全局的64位变量jiffies_64
的值会被原子地加一。 jiffies
的访问:jiffies
变量本身是一个unsigned long
,它被定义为jiffies_64
的低32位或直接就是jiffies_64
(取决于系统位数)。内核代码通过直接读取这个变量来获取当前的“滴答数”。- 回绕安全比较:为了安全地比较两个
jiffies
值(尤其是在32位系统上),必须使用time_after(a, b)
或time_before(a, b)
宏。这些宏的实现巧妙地将unsigned long
转换为long
进行比较,利用有符号数的溢出行为来确保即使在jiffies
回绕点附近,比较结果也是正确的。例如,time_after(a, b)
大致等价于(long)(b) - (long)(a) < 0
。
它的主要优势体现在哪些方面?
- 低开销:读取一个全局变量几乎是零开销的。其更新开销被分摊在每次时钟中断中,也非常低。
- 简单性:模型简单直观,易于理解和使用(只要记住使用比较宏)。
- 全局可用性:在内核的任何地方(除了极少数早期启动阶段)都可以安全地访问
jiffies
。 - 单调性:它是一个单调递增的计数器(在回绕前),非常适合用来测量时间间隔。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
- 低分辨率:这是其最大的缺点。
jiffies
的精度受限于HZ
。例如,在HZ=250
时,其分辨率只有4毫秒。任何需要比1/HZ
秒更精确的定时都不能使用jiffies
。 - 32位回绕:在32位系统上,如果开发者忘记使用
time_after
/before
宏,就会引入非常难以调试的、与时间相关的bug。 - 与Tickless内核的交互:在Tickless内核的空闲期间,
jiffies
的值不会变化。依赖于在循环中观察jiffies
值变化的“忙等待”代码在这种情况下会失效。
使用场景
在哪些具体的业务或技术场景下,它是首选解决方案?
jiffies
是低分辨率、毫秒级定时的首选解决方案。
- 设置
timer_list
定时器:这是最经典的使用场景。timer->expires = jiffies + msecs_to_jiffies(100);
// 设置一个100毫秒后的定时器。 - 检查超时:
if (time_after(jiffies, start_jiffies + timeout)) { /* 超时了 */ }
- 驱动中的轮询超时:一个驱动在等待硬件状态位时,不能永远等下去,通常会使用
jiffies
来设置一个超时上限。
是否有不推荐使用该技术的场景?为什么?
- 高精度定时:任何需要亚毫秒级(sub-millisecond)精度的场景,都必须使用**高精度定时器(hrtimers)**和
ktime
API。 - 测量真实世界时间(Wall Time):
jiffies
只与系统启动后的时间有关,不涉及年月日。需要真实世界时间的场景应使用专门的时间管理API。 - 长时间间隔测量:在32位系统上测量超过
jiffies
回绕周期的长时间间隔,应始终使用get_jiffies_64()
。
对比分析
请将其 与 其他相似技术 进行详细对比。
特性 | jiffies |
高精度定时器 (hrtimers / ktime ) |
真实世界时间 (Wall Time) |
---|---|---|---|
核心用途 | 低分辨率的内核内部时间流逝测量。 | 高分辨率的内核内部时间流逝测量。 | 与现实世界同步的绝对时间。 |
分辨率 | 低,由HZ 决定(通常是1-10毫秒)。 |
高,可达纳秒级,受硬件限制。 | 高,可达纳秒级。 |
单调性 | 是(在回绕前)。 | 是。 | 否(可通过NTP或date 命令向后调整)。 |
开销 | 极低。 | 较高。涉及更复杂的数据结构(红黑树)和硬件编程。 | 较高。通常需要从ktime 转换。 |
适用场景 | 传统内核定时器、网络超时、调度器时间片。 | 亚毫秒级延迟、nanosleep 、需要精确触发的事件。 |
日志时间戳、文件时间戳、需要与外部世界同步的事件。 |
include/linux/jiffies.h
- __jiffy_arch_data:这个修饰符是一个架构特定的标记,用于在不同的架构上处理 jiffies 变量的特殊需求。它可能在不同的架构上有不同的定义,但在这里它主要起到标记作用。
1 | /* |
INITIAL_JIFFIES
- jiffies自减 300*HZ, 使得jiffies在5分钟后换行从0开始
1 | /* |
time_eq 定时器时间比较
1 | /** |
kernel/time/jiffies.c
Jiffies Clocksource: 内核的基础回退时钟源
此代码片段定义了一个基于内核全局变量 jiffies
的时钟源(clocksource)。jiffies
是一个自系统启动以来发生的定时器滴答(tick)的总数, 它的更新频率由内核配置的 HZ
值决定。这个时钟源是内核时间子系统中最基础、最不可靠但保证存在的一个。它的主要作用是在系统启动早期、或在更高精度的硬件时钟源不可用或发生故障时, 提供一个最低限度的时间基准, 确保系统时间能够继续前进。
- 在单核无MMU的STM32H750平台上的原理与作用
在STM32H750这样的微控制器上, 拥有多个高精度的硬件定时器(例如通用定时器TIMx, 或ARM Cortex-M核心自带的SysTick定时器)。Linux内核的ARM移植版本会专门为这些硬件定时器提供驱动, 并将它们注册为高精度的时钟源。
- 角色定位:
clocksource_jiffies
在STM32平台上的角色是 “备胎”或“启动时钟”。当内核的定时器驱动(例如clocksource-systick.c
)被初始化时, 它会注册一个基于SysTick硬件定时器的时钟源。这个硬件时钟源的评级(rating)会远高于jiffies
的评级(1)。内核的时间管理框架会自动选择评级最高的时钟源作为当前活动的时钟源。 - 工作流程:
- 系统启动初期, 在高精度定时器驱动加载前,
clocksource_default_clock()
会提供clocksource_jiffies
作为默认时钟源, 保证内核有基本的时间概念。 - 当STM32的SysTick驱动初始化成功后, 它会注册一个高评级的
clocksource
。 - 内核发现了一个更好的时钟源, 就会自动切换过去, 使用SysTick来驱动系统时间。
- 此后,
jiffies
时钟源虽然仍然存在, 但不再被用于主要的计时任务, 仅作为一种安全回退机制保留。
- 系统启动初期, 在高精度定时器驱动加载前,
因此, 尽管这段代码在STM32内核中存在, 但jiffies
时钟源的实际工作时间非常短暂, 很快就会被精确得多的片上硬件定时器所取代。
1 | /* |
jiffies_seq jiffies_lock
1 | /* |
init_jiffies_clocksource
1 | static int __init init_jiffies_clocksource(void) |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 wdfk-prog的个人博客!
评论