本页目录

时间管理

覆盖: timekeeping → CLOCK_MONOTONIC/CLOCK_REALTIME/CLOCK_BOOTTIME → NTP → PTP → vsyscall/vDSO → TSC/HPET/ACPI 等硬件时钟 内核版本: 2.6 ~ 6.x

概述

内核时间管理回答三个问题:"现在几点?"、"过了多久?"、"什么时候唤醒?"。看似简单,但涉及多个时钟源的选择与仲裁、NTP 校时、以及用户空间的快速时间访问。

关键概念是区分 REALTIME (挂钟时间,可被 NTP 调整,受闰秒影响) 和 MONOTONIC (自启动以来的可靠测量,单调递增)。


时钟类型

// include/uapi/linux/time.h

// CLOCK_REALTIME: 挂钟时间 (Unix epoch)
//   受 NTP 调整, 可以往回跳 (闰秒, settimeofday)
//   → 不适合做 delta 测量

// CLOCK_MONOTONIC: 自启动以来的时间
//   单调递增, 不受 NTP 调整, 不会往回跳
//   → 适合测量时间间隔

// CLOCK_MONOTONIC_RAW: 原始硬件计数
//   不受 NTP 频率调整 (比 MONOTONIC 更难被扭曲)
//   → 适合精确基准测试

// CLOCK_BOOTTIME: 包含 suspend 时间
//   与 MONOTONIC 类似,但包含系统睡眠的时间
//   → 适合需要"墙上的真实流逝时间"的场景

// CLOCK_TAI: International Atomic Time
//   与 UTC 偏移 37 秒 (截至 2024)
//   不受闰秒影响

timekeeping 核心

// kernel/time/timekeeping.c
// 核心数据结构:
struct timekeeper {
    struct clocksource          *clock;  // 当前选中的 clocksource
    u64                         cycle_interval;  // NTP tick 对应的 cycles
    u64                         xtime_sec;       // REALTIME 秒数
    unsigned long               xtime_nsec;      // REALTIME 纳秒部分
    ktime_t                     offs_real;       // REALTIME 偏移 (NTP 调整)
    ktime_t                     offs_boot;       // BOOTTIME 偏移
    ktime_t                     offs_tai;        // TAI 偏移
    struct timespec64           wall_to_monotonic; // REALTIME → MONOTONIC 转换
    struct timespec64           total_sleep_time;  // 累计 suspend 时间
};

// timekeeping_advance():
//   每个 tick (或 hrtimer interrupt) 调用一次
//   1. 读 clocksource
//   2. 计算 delta cycles → delta_ns
//   3. 将 delta_ns 累加到 xtime_nsec (处理进位)
//   4. 应用 NTP 频率校正 (mult 调整)
//   5. 更新所有衍生时间 (MONOTONIC, BOOTTIME, TAI)

NTP 校时

// kernel/time/ntp.c
// 内核 NTP daemon:
//   不是用户空间的 ntpd/chronyd — 那是控制者
//   内核 NTP 代码执行实际的频率和相位调整

// adjtimex() 系统调用:
//   用户空间 ntpd 通过 adjtimex 告诉内核:
//     1. 当前 offset (相位误差, 多少纳秒)
//     2. 当前 freq (频率误差, PPM)
//     3. 最大误差
//   内核: 调整 timekeeper->mult (频率) 和 offset (相位)

// PTP (Precision Time Protocol):
//   Linux 通过 SO_TIMESTAMPING 支持硬件时间戳
//   PHC (PTP Hardware Clock) 驱动 → /dev/ptp*
//   → 纳秒级时间同步 (数据中心/金融交易)

vDSO: 快速时间访问

// arch/x86/entry/vdso/
// 问题: clock_gettime() 如果用 syscall → 每次 ~100ns (syscall + context switch)
//       高频调用 (如 profiling) 开销太大

// vDSO (virtual Dynamic Shared Object):
//   内核把一小段代码注入每个进程的地址空间
//   → clock_gettime() 在用户态执行 → 不经过内核!
//   → 读 clocksource, 应用 timekeeper 的偏移和 mult → 返回时间
//   → 延迟: ~20ns (vs ~100ns for syscall)

// vsyscall (旧方式, 已废弃) vs vDSO (新方式):
//   vsyscall: 固定地址, 安全风险 (容易 ROP)
//   vDSO:    ASLR 随机地址, 标准 ELF .so

x86 vDSO 实现

// arch/x86/entry/vdso/vclock_gettime.c
// clock_gettime(CLOCK_MONOTONIC) 在用户态:
notrace int __vdso_clock_gettime(clockid_t clock, struct timespec *ts) {
    // 1. 读 TSC (RDTSC 指令, ~10 cycles)
    cycles = __arch_counter_get_cntvct();

    // 2. 应用 clocksource 转换 (在 vvar 页中)
    ns = (cycles - vd->cycle_last) * vd->mult + vd->xtime_nsec;

    // 3. 处理 seqlock 一致性
    //    vd->seq 是偶→奇→偶的序列号
    //    读前记录 seq, 读后验证 seq 没变 → 数据一致
}

硬件时钟源

时钟源精度延迟稳定性场景
TSC (rdtsc)~0.3ns~10 周期高 (constant/invariant TSC)默认首选
HPET~100ns~500 周期老旧系统或 TSC 不稳定时
ACPI PM Timer~280ns~1μs同上
ARM Generic Timer~10ns~10 周期ARM64 默认
KVM pvclock~20ns~200 周期由 host 保证虚拟化
# 查看当前使用的 clocksource
cat /sys/devices/system/clocksource/clocksource0/current_clocksource

# 查看可用列表
cat /sys/devices/system/clocksource/clocksource0/available_clocksource

参考与延伸

  • 内核文档⁠: Documentation/timers/timekeeping.rst
  • LWN: "The vDSO and time", "Timekeeping in the Linux kernel"
  • 源码⁠:
    • kernel/time/timekeeping.c — timekeeping 核心
    • kernel/time/ntp.c — NTP
    • arch/x86/entry/vdso/ — x86 vDSO
    • arch/arm64/kernel/vdso/ — ARM64 vDSO
    • kernel/time/clocksource.c — clocksource 管理

关键词: CLOCK_MONOTONIC, CLOCK_REALTIME, timekeeper, NTP, PTP, vDSO, TSC, HPET, clocksource