Jiffies回绕问题详解

🔥作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生,研究方向无线联邦学习

🎬擅长领域:驱动开发,嵌入式软件开发,BSP开发

❄️作者主页:一个平凡而乐于分享的小比特的个人主页

✨收录专栏:Linux,本专栏目的在于,记录学习Linux操作系统的总结

欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖💖

Jiffies回绕问题详解

📖 什么是Jiffies?

Jiffies 是Linux内核中的一个全局变量,用来记录系统启动以来的时钟滴答数(时钟中断次数)。每次时钟中断发生,jiffies就增加1。

基本信息速览

项目 说明
定义 extern unsigned long volatile jiffies;
更新频率 HZ决定,通常为100、250或1000Hz
数据类型 32位或64位无符号整数
32位最大值 约49.7天(HZ=1000时)
主要用途 记录时间戳、计算时间间隔、实现超时机制

为什么会出现回绕?

核心原因:计数器溢出

就像汽车的里程表 一样,当数值达到最大值后,会从0重新开始

复制代码
普通32位jiffies(HZ=1000):
开始:0x00000000 → ... → 0xFFFFFFFF → 0x00000000(回绕!)
时间:0天 → 49.7天 → 0天(重新计数)

时间线图解

复制代码
系统启动
    │
    ├─── jiffies = 0
    │      (时间: 0天)
    │
    ├─── jiffies = 2,147,483,647
    │      (时间: 24.85天)
    │
    ├─── jiffies = 4,294,967,295 ← **最大值**
    │      (时间: 49.7天)   ↗
    │                      │
    └─── jiffies = 0       │ **回绕发生!**
          (重新开始)       │
                           │
                   计数器溢出,从0重新开始

⚠️ 回绕引发的问题

场景对比:正常的超时检查 vs 回绕时的错误

正常情况(无回绕)
c 复制代码
unsigned long timeout = jiffies + HZ*5;  // 5秒后超时

// 循环检查是否超时
while (time_before(jiffies, timeout)) {
    // 正常工作...
}
// jiffies达到timeout,正常退出循环

时间轴图示

复制代码
jiffies: 1000 → 1001 → ... → 5999 → 6000
timeout: 6000 (固定值)
关系:jiffies < timeout ✅ 始终成立,直到超时
回绕发生时
c 复制代码
// 假设jiffies即将回绕
unsigned long jiffies = 0xFFFFFFFE;  // 即将溢出
unsigned long timeout = jiffies + HZ*5;  // 计算得到 0x00000003

// 使用简单比较(错误的做法)
while (jiffies < timeout) {  // 这里会出问题!
    // ...
}

时间轴图示

复制代码
阶段1(回绕前):
jiffies: 0xFFFFFFFE → 0xFFFFFFFF → 0x00000000
timeout: 0x00000003
关系:回绕前 jiffies > timeout ❌

阶段2(回绕后):
jiffies: 0x00000000 → 0x00000001 → 0x00000002 → 0x00000003
timeout: 0x00000003
关系:jiffies < timeout ✅

问题 :在回绕发生前,由于jiffies > timeout,循环会提前退出

🔧 解决方案

Linux内核提供的安全宏

功能 等价的数学表达式 是否防回绕
time_after(a,b) 判断a是否在b之后 (long)(b) - (long)(a) < 0
time_before(a,b) 判断a是否在b之前 (long)(a) - (long)(b) < 0
time_after_eq(a,b) a是否在b之后或相等 -
time_before_eq(a,b) a是否在b之前或相等 -

原理揭秘:有符号整数比较

c 复制代码
// 安全比较的原理
#define time_after(a,b) \
    ((long)((b) - (a)) < 0)

// 假设:
// a = 0x00000003 (回绕后的小值)
// b = 0xFFFFFFFE (回绕前的大值)

// 计算:(long)(b - a) = (long)(0xFFFFFFFE - 0x00000003)
//                   = (long)(0xFFFFFFFB)
// 转换为有符号:-5 < 0 ✅

图解原理

复制代码
无符号视角(错误):
小值(3) < 大值(0xFFFFFFFE)?  ✓ 但这是回绕后的错误判断

有符号视角(正确):
将差值看作有符号数:
0xFFFFFFFB = -5(负数)
负数表示 a 在 b 之后 ✓

正确使用示例

c 复制代码
// 正确的超时检查
unsigned long timeout = jiffies + HZ*5;

do {
    // 执行任务...
} while (time_before(jiffies, timeout));  // 使用安全宏

// 或者检查是否超时
if (time_after(jiffies, timeout)) {
    printk("已超时!\n");
}

📊 实战场景对比表

场景 错误代码示例 问题 正确代码示例
超时检查 if (jiffies > timeout) 回绕时提前触发 if (time_after(jiffies, timeout))
等待时间 while (current < end) 回绕时无限循环 while (time_before(current, end))
时间间隔 interval = now - last 回绕时得到巨大差值 使用time_after宏保护
定时器调度 直接比较时间戳 定时器调度错误 使用内核定时器API

🛡️ 最佳实践指南

1. 总是使用内核提供的宏

c 复制代码
// ✅ 推荐
#include <linux/jiffies.h>
unsigned long start = jiffies;
unsigned long timeout = start + HZ*2;

// 安全等待
while (time_before(jiffies, timeout)) {
    // ...
}

// 检查是否超时
if (time_after(jiffies, timeout)) {
    handle_timeout();
}

2. 计算时间间隔的正确方法

c 复制代码
// ✅ 安全的时间差计算
unsigned long interval = jiffies - last_time;
// 即使回绕,差值也会正确(无符号减法自动处理回绕)

// 但判断是否超过某个间隔要小心:
if (time_after(jiffies, last_time + HZ*5)) {
    // 超过5秒
}

3. 64位jiffies避免回绕

c 复制代码
// 现代内核提供jiffies_64
extern u64 jiffies_64;

// 32位架构上也能安全访问
u64 current_jiffies = get_jiffies_64();

// 64位的回绕时间:
// 如果HZ=1000,回绕时间约5.8亿年!

📈 回绕影响总结表

系统配置 32位jiffies回绕周期 是否常见 建议
HZ=100 约497天 较常见 必须处理
HZ=250 约198天 常见 必须处理
HZ=1000 约49.7天 很常见 必须处理
64位jiffies 数亿年 无需考虑 基本安全

🎯 关键要点总结

  1. jiffies回绕是必然发生的:32位jiffies在系统运行约50天后必然回绕
  2. 问题本质:无符号整数的回绕特性与时间方向的矛盾
  3. 解决方案 :使用内核提供的time_after/before等宏
  4. 安全原理:通过有符号数比较来正确判断时间先后
  5. 现代方案 :使用64位jiffies(jiffies_64)可以避免此问题

🔍 实际调试技巧

如何检测回绕相关bug?

c 复制代码
// 添加调试代码
#define DEBUG_JIFFIES_WRAPAROUND

#ifdef DEBUG_JIFFIES_WRAPAROUND
    if (jiffies < last_check) {
        printk("检测到jiffies回绕!\n");
    }
    last_check = jiffies;
#endif

模拟测试回绕

bash 复制代码
# 强制设置jiffies接近回绕点(仅用于测试)
echo $((0xFFFFFFF0)) > /sys/kernel/debug/jiffies_reset

💡 一句话记住

"处理jiffies,永远不要用<>直接比较,要使用time_beforetime_after宏!"

通过理解jiffies回绕的机制并正确使用内核提供的工具,可以写出健壮的、不会因系统长时间运行而崩溃的驱动程序或内核模块。

参考博客:jiffies回绕问题