2. 内核时间度量单位:jiffies详解、HZ配置、时间转换函数
在Linux内核驱动开发中,时间管理是核心基础之一。内核使用一个全局变量 jiffies 作为系统运行时间的基准度量单位。理解 jiffies 的机制、HZ 的配置以及时间转换函数,是编写精确延时和定时器驱动的必备知识。
2.1 jiffies 是什么?
jiffies 是一个全局的 unsigned long volatile 变量,定义在 <linux/jiffies.h> 中。它记录自系统启动以来,内核定时器中断发生的总次数。每次时钟中断触发,jiffies 的值自动加 1。
可以这样理解:jiffies 是内核的“心跳计数器”。驱动通过读取 jiffies 的差值,来判断时间是否流逝了指定的长度。
关键特性:
- 全局性:整个内核共享同一个 jiffies 变量。
- 递增性:每个时钟周期(tick)自动加 1。
- 溢出风险:32 位系统上,约 497 天后会回绕到 0。内核提供了
time_after()、time_before()等宏来安全处理比较。
2.2 HZ 配置:jiffies 的精度基准
HZ 是一个编译内核时确定的常量,定义在 <asm/param.h> 中,表示 每秒产生的时钟中断次数。它直接决定了 jiffies 的精度:
- 1 HZ = 1 秒产生 1 次中断 → 精度 1 秒
- 100 HZ = 1 秒产生 100 次中断 → 精度 10 毫秒
- 1000 HZ = 1 秒产生 1000 次中断 → 精度 1 毫秒
HZ 值越大,时间精度越高,但 CPU 开销也越大。常见配置如下:
| HZ 值 | 每个 tick 时长 | 适用场景 |
|---|---|---|
| 100 | 10 ms | 服务器、桌面系统(低开销) |
| 250 | 4 ms | 通用桌面系统(平衡) |
| 1000 | 1 ms | 实时系统、嵌入式(高精度) |
驱动开发者提示: 不要硬编码 HZ 值。始终使用
HZ 宏或转换函数,以保证驱动在不同内核配置下的可移植性。
2.3 jiffies 与时间转换函数
驱动中经常需要在 jiffies 和 毫秒/微秒 之间进行转换。内核提供了两组标准内联函数:
2.3.1 毫秒 ↔ jiffies
#include <linux/jiffies.h>
// 将毫秒转换为 jiffies
unsigned long msecs_to_jiffies(const unsigned int m);
// 将 jiffies 转换为毫秒
unsigned int jiffies_to_msecs(const unsigned long j);
2.3.2 微秒 ↔ jiffies
// 将微秒转换为 jiffies
unsigned long usecs_to_jiffies(const unsigned int u);
// 将 jiffies 转换为微秒
unsigned int jiffies_to_usecs(const unsigned long j);
注意: 转换函数会向上取整。例如,HZ=1000 时,
msecs_to_jiffies(1) 返回 1;HZ=100 时,msecs_to_jiffies(1) 返回 1(因为 1ms 不足一个 tick,但函数保证至少返回 1 个 jiffy,避免死等)。
2.4 时间转换关系图
下图展示了 jiffies、HZ 与真实时间之间的转换逻辑:
2.5 驱动中的典型用法
在驱动中,jiffies 常用于以下场景:
- 超时判断:记录起始 jiffies,循环中比较当前 jiffies 与起始值的差值。
- 定时器编程:设置定时器到期时间时,使用
jiffies + msecs_to_jiffies(delay_ms)。 - 延时实现:配合
schedule_timeout()或忙等待循环。
代码示例:使用 jiffies 实现毫秒级超时等待
#include <linux/jiffies.h>
#include <linux/delay.h>
int wait_for_flag_ms(struct device *dev, unsigned long timeout_ms)
{
unsigned long timeout_j = jiffies + msecs_to_jiffies(timeout_ms);
while (time_before(jiffies, timeout_j)) {
if (check_device_flag(dev))
return 0; // 成功
msleep(1); // 休眠 1ms 避免忙等
}
return -ETIMEDOUT; // 超时
}
最佳实践:
- 始终使用
time_after()/time_before()宏比较 jiffies,避免溢出问题。 - 在需要高精度(微秒级)时,考虑使用
ktime_t或硬件定时器。 - 不要在中断上下文或持有自旋锁时调用
msleep(),应使用mdelay()或udelay()。
2.6 常见陷阱与对比
| 陷阱 | 说明 | 解决方案 |
|---|---|---|
| jiffies 回绕 | 32 位系统约 497 天后溢出 | 使用 time_after() 宏 |
| HZ 不匹配 | 驱动硬编码 HZ=100,内核实际 HZ=1000 | 使用转换函数,不直接使用 HZ 常量 |
| 忙等待浪费 CPU | while 循环不断读取 jiffies | 使用 schedule_timeout() 或 msleep() |
| 转换精度丢失 | 1ms 在 HZ=100 时被截断为 0 | 函数自动向上取整,但需注意精度下限 |
2.7 时间转换函数速查表
| 函数 | 原型 | 说明 |
|---|---|---|
msecs_to_jiffies |
unsigned long (const unsigned int m) |
毫秒 → jiffies |
jiffies_to_msecs |
unsigned int (const unsigned long j) |
jiffies → 毫秒 |
usecs_to_jiffies |
unsigned long (const unsigned int u) |
微秒 → jiffies |
jiffies_to_usecs |
unsigned int (const unsigned long j) |
jiffies → 微秒 |
timespec_to_jiffies |
unsigned long (const struct timespec *t) |
timespec → jiffies |
jiffies_to_timespec |
void (const unsigned long j, struct timespec *t) |
jiffies → timespec |