应用里写个 sleep(1),进程真的睡了精确到 1 秒。但内核里没有"秒"这个东西——它只有三套不同的时间表示法,做着不同的事。
第一套:jiffies,内核自己的节拍计数器
内核最朴素的时间概念是一个全局计数器 jiffies。时钟中断的频率由 HZ 决定,x86_64 默认配置是 CONFIG_HZ_1000,也就是每秒中断 1000 次:
// arch/x86/configs/x86_64_defconfigCONFIG_HZ_1000=y
如果系统开了 CONFIG_HZ_PERIODIC,每次时钟中断 jiffies 就加 1,jiffies 自增一次 = 过去 1 毫秒。但现代 x86_64 发行版默认开的是 CONFIG_NO_HZ_IDLE=y(动态 tick),CPU 空闲时周期性时钟中断会被关闭。等 CPU 被事件唤醒时,内核读取 ktime 计算出空闲了多少纳秒,一次性补上缺失的节拍数:
jiffies += delta / (NSEC_PER_SEC / HZ)
逻辑语义没变(1 个 jiffies 代表 1ms),但实现上不是简单的"每次中断加 1"了。它是个"计数器",不表示真实日期,系统重启就归零。内核里很多低精度的时间判断就基于它——比如"这个锁我等了超过 5 秒吗",算成 jiffies + 5*HZ 个节拍来比。
第二套:ktime_t,纳秒时间戳
高精度场景(比如网络包时间戳、调度延迟统计)需要纳秒级。内核用 ktime_t 表示:
// linux/ktime.htypedef s64 ktime_t;
就是个有符号 64 位整数,单位是纳秒。加减乘除直接用运算符:
#define ktime_sub(lhs, rhs) ((lhs) - (rhs))#define ktime_add_ns(kt, nsval) ((kt) + (nsval))
ktime_sub(a, b) 得到两个时间点相差多少纳秒。它是纯数值,本身不携带时钟源信息——你拿它存"上次检查的时间点"也行,"两个事件相差多少纳秒"也行,语义由调用方约定。
当然,数值来源是有根的:ktime_get() 系列函数经过 timekeeping 层,而 timekeeping 绑定了当前选中的硬件 clocksource(通常是 TSC),最终从真实的硬件计数器换算得来。
内核内部所有高精度的时间计算、传递,用的都是它。
第三套:timespec64,用户态能看懂的墙钟
应用调用 clock_gettime(CLOCK_MONOTONIC, &ts),拿到的是这种结构:
// linux/time64.hstruct timespec64 { time64_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */};
秒 + 纳秒,跟 ktime_t 不冲突——ktime_t 是单一纳秒整数,timespec64 是秒和纳秒拆开。内核内部计算用 ktime_t,要给用户态时就转成 timespec64。
clock_gettime 的第一个参数决定你读的是哪座钟。内核定义了十几种时钟源:
// include/uapi/linux/time.h#define CLOCK_REALTIME 0 // 墙上时钟,可以被 NTP 调快调慢,甚至跳变#define CLOCK_MONOTONIC 1 // 单调时钟,从启动开始,永不回退#define CLOCK_MONOTONIC_RAW 4 // 裸单调,不受 NTP 调整影响#define CLOCK_BOOTTIME 7 // 包含 suspend 时长#define CLOCK_TAI 11 // 国际原子时,不带闰秒补偿
最关键的区分是 CLOCK_REALTIME 和 CLOCK_MONOTONIC。REALTIME 是墙上挂钟,NTP 同步时可能被往前拨或往后拨——你刚记录 10:00:00,下一秒读出来可能是 09:59:58。MONOTONIC 从系统启动开始单向增长,不跳变。做超时判断、性能计时永远用 MONOTONIC,用 REALTIME 会因为时钟回拨导致逻辑出错。
硬件时钟源:三套怎么串起来的
底层的精度不来自软件,来自硬件。CPU 有 TSC(时间戳计数器),主板有 HPET、ACPI PM Timer。内核启动时选一个最快最稳的注册成 clocksource:
硬件 TSC/HPET ↓ 读计数器clocksource (抽象层) ↓ 统一接口timekeeping (维护系统时间) ↓ 提供读接口CLOCK_MONOTONIC / CLOCK_REALTIME
timekeeping 是中枢,它把硬件计数器的原始 tick 换算成纳秒,再分发给各个 clock id。应用读 clock_gettime 时,最终落到 timekeeping_get_ns(),读一下当前 clocksource 的计数值,算一下偏移,返回纳秒。
这就是为什么 clock_gettime 很快——它基本就是读一下 TSC 然后做一道乘法,不进内核态切换(vDSO 机制把这段代码映射到用户态直接跑)。
三套表示法,各用各的地盘
总结一下它们的分工:
- • jiffies:专供内核低精度逻辑(调度时间片、锁超时判断),基于时钟中断,1ms 精度,重启归零
- • ktime_t:内核内部传递时间用的标准货币,s64 纳秒整数,从 TSC → clocksource → timekeeping 换算得来,高精度、高频访问
- • timespec64:分发给用户态时的表示形式(秒+纳秒),通过
clock_gettime 返回给应用,vDSO 加速
应用层一句 clock_gettime(CLOCK_MONOTONIC, &ts),内核背后跑了三层转换:硬件 TSC → timekeeping 换算纳秒 → 拆成 timespec64 的秒和纳秒返回给你。
源码:linux/ktime.h、linux/time64.h、linux/jiffies.h、include/uapi/linux/time.h(v6.6)。x86_64 默认 HZ 见 arch/x86/configs/x86_64_defconfig。