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 与真实时间之间的转换逻辑:

jiffies 时间转换关系图 jiffies 与时间单位转换关系 jiffies HZ (每秒 tick 数) 毫秒 (ms) 1 tick = 1000/HZ ms jiffies_to_msecs() msecs_to_jiffies() 转换示例(HZ = 1000) • 1 秒 = 1000 ms = 1000 jiffies • 10 ms = 10 jiffies • 1 ms = 1 jiffy(精度极限) • 500 jiffies = 500 ms

2.5 驱动中的典型用法

在驱动中,jiffies 常用于以下场景:

  1. 超时判断:记录起始 jiffies,循环中比较当前 jiffies 与起始值的差值。
  2. 定时器编程:设置定时器到期时间时,使用 jiffies + msecs_to_jiffies(delay_ms)。
  3. 延时实现:配合 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