🔥作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生,研究方向无线联邦学习
🎬擅长领域:驱动开发,嵌入式软件开发,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 | 数亿年 | 无需考虑 | 基本安全 |
🎯 关键要点总结
- jiffies回绕是必然发生的:32位jiffies在系统运行约50天后必然回绕
- 问题本质:无符号整数的回绕特性与时间方向的矛盾
- 解决方案 :使用内核提供的
time_after/before等宏 - 安全原理:通过有符号数比较来正确判断时间先后
- 现代方案 :使用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_before、time_after宏!"
通过理解jiffies回绕的机制并正确使用内核提供的工具,可以写出健壮的、不会因系统长时间运行而崩溃的驱动程序或内核模块。
参考博客:jiffies回绕问题