Linux时间子系统学习笔记
时间子系统
概述: 主要负责时间管理、时钟源选择、计时器事件处理、时间同步等核心功能。
时钟源
简介
概述: 是硬件计时器,用于提供高精度、连续不断的计数值,常用的时钟源包括TSC(跟cpu频率相关,且通过hpet等其他时钟源校准tsc频率)、HPET、ACPI定时器等。内核在启动过程中会探测可用的时钟源,并选择精度高、稳定性好的作为系统的主要时间计数器。
作用: 内核利用时钟源的计数值计算经过的时间,通过将计数器的变化量转换为实际时间(通常通过除以配置的时钟频率CONFIG_HZ),从而维护系统时间和其他时间相关功能。
物理时钟源:
- rtc:独立于CPU的实时时钟,通常用于保存系统时间。由电池供电,即使在系统断电时也能持续运行。系统会在启动时从RTC读取当前时间,作为系统时间的初始值,并在关机或定期校正时将系统时间写回RTC。
- tsc:x86 CPU内置的时间戳计数器,精度高但可能不稳定(多核同步问题)
- hpet:高精度定时器
- acpi pm timer:用于acpi电源管理,精度低但稳定
虚拟时钟源:
- kvm-clock:KVM提供的高效时钟源,适用于KVM/QEMU虚拟机,减少VM-Exit,提高精度
- xen-clocksource:Xen虚拟机环境下的时钟源
- hypervclock:Microsoft Hyper-V提供的时钟源
时钟源选择:
- 通过/sys/devices/system/clocksource/clocksource0/current_clocksource选择时钟源
- cat /proc/uptime也可以间接反映时钟源的精度
TSC
概述: 现代cpu通常支持invariant tsc,即TSC的计数速率固定,不受CPU频率变化影响,如果硬件满足这一条件,linux内核通常会优先选择TSC作为主时钟源,因为其开销低且读取迅速
怎么读取tsc值:
- 直接使用汇编指令
rdtsc
:tsc是一个64位的寄存器,自系统启动后开始计数,每个时钟周期递增。rdtsc的作用就是将这个计数器的当前值读取出来。 - 内核态中可以使用
tsc_read_refs
函数:这个在上面汇编指令的基础上进一步做一个高级抽象的时钟源,原因:- linux内核不仅可能使用tsc,也可能使用hpet等其他时钟源,这样做抽象方便管理
- 校准与转换:直接读取返回的是时钟周期数,要需要进一步进行转换为标准的时间单位(如纳秒),以便在调度、定时器和用户空间应用中使用一致的时间格式
- 保证单调性与跨核一致性:多核系统中,各个核心tsc可能不同步或存在细微差别,在tsc抽象实现中会采取相应措施保证单调性和一致性(比如rdtsc指令本身是非序列化的,可以配合其他指令(CPUID或RDTSCP)确保时间戳读取的顺序)
- 用户态可以使用
clock_gettime
系统调用:linux内核一般会选tsc
稳定性检测
频率漂移检测:
- 内核会定期测量不同时钟源的频率,并对比它们之间的偏差
- 例如,TSC可能会受到CPU频率调节影响,因此会与HPET、ACPI PM Timer等参考时钟进行对比
- 若发现某个时钟源的频率偏差超出允许范围,则可能会被降级或禁用
单调性检查: 内核会确保时钟源的时间值是单调递增的,不能出现回退现象
时间跳变检测: 通过定期对比系统时间与参考时钟(如NTP服务器),检测是否出现了异常跳变
多核一致性检测: 对于TSC,linux会检测多个CPU核心的TSC是否同步,以防止不同核心返回的TSC不一致
定期重新校准: linux可能会周期性地使用hpet活外部时钟(NTP)重新校准TSC
kvm-clock
概述: KVM提供的一个半虚拟化时钟源,其核心目的是为guest os提供一个高效、低开销、尽可能准确的计时机制。避免传统硬件时钟(如TSC、HPET)在虚拟化场景中可能遇到的同步、漂移或性能问题。【本质上其实也依赖于TSC吧,只是依赖的host os的tsc,可能误差更小】
工作原理:
- 共享内存区域:
- Guest OS在初始化时会为kvm-clock分配一段共享内存,这块内存区域是由hypervisor(例如qemu/kvm)映射给guest
- 该区域存储一个数据结构(
pvclock_vcpu_time_info
),其中包含当前的tsc值,系统时间、以及一些转换参数(如tsc_to_system_mul
和tsc_shift
) - Guest通过读取这个共享区域,就可以直接获得当前时间,而不需要进行昂贵的系统调用或VM Exit
- 主机与客机时间同步:
- Hypervisor定期更新这块共享内存区域,确保guest看到的时间与host系统时间保持一致
- 不仅降低了guest os对硬件时钟的依赖,而且可以减少虚拟化带来的时间漂移问题
- 效率与精度:
- 由于 kvm-clock 利用共享内存而非每次调用硬件寄存器(如 TSC),因此能够避免频繁的虚拟机退出(VM Exit),降低延迟。
- 同时,通过对 TSC 值进行适当转换,kvm-clock 能够提供一个相对稳定、单调递增的时钟源,即使在 CPU 频率变动或多核环境下也能维持较高的精度。
实现细节:
数据结构:
1
2
3
4
5
6
7
8
9
10struct pvclock_vcpu_time_info {
u32 version;
u32 pad0;
u64 tsc_timestamp;
u64 system_time;
u32 tsc_to_system_mul;
s8 tsc_shift;
u8 flags;
u8 pad[2];
} __attribute__((__packed__));- version:用于保证读取时的一致性(类似双缓冲机制)
- tsc_timestamp:记录了上次更新时间时的 TSC 值。
- system_time:对应的系统时间(通常以纳秒为单位)
- tsc_to_system_mul和tsc_shift:用于将 TSC 增量转换为系统时间的比例因子
更新策略:Hypervisor 并不会在每次时间查询时都更新这块区域,而是在关键事件(如 VM 启动、迁移、或其他时间敏感操作后)进行更新,以减少性能开销。
优缺点:
优点:
- 低开销:共享内存的访问非常快,避免了 VM Exit
- 高效性:减少了依赖传统硬件时钟带来的额外延迟
- 单调性:提供一个单调递增的时间源,对时间敏感型应用(例如网络协议、会话管理)非常关键
缺点:
依赖host os更新:如果 hypervisor 更新不及时,guest 可能会遇到时间漂移问题。
特殊场景问题:在某些情况下(如热添加 CPU 等操作),kvm-clock 可能会暴露出时间回退或不一致的问题,需要内核补丁或额外配置加以解决
新加入的vCPU并不会立即与已有的vCPU同步它们的kvm-clock状态
时钟事件
简介
功能: 时钟事件设备负责生成定时中断,用以触发内核中断各类定时任务,例如,任务调度、定时器超时处理以及高精度定时器的触发都依赖于时钟事件设备(比如APIC定时器设备)
抽象接口: linux为了兼容不同硬件平台,定义了统一的时钟事件接口,这样无论底层硬件如何差异化,内核上层的定时器管理和调度代码都能以相同的方式调用,极大提高代码的可移植性和维护性
计时器
周期性计时器:
- jiffies:是一个全局计时变量,用来记录自系统启动以来经过的时钟tick数,虽然jiffies本身不是以秒为单位的,但通过与时钟频率(HZ)的结合,可以方便地将其转换为秒数或其他时间单位。本身单位是ticks,在内核中通过HZ来定义,一个tick通常是100或1000,这意味着每秒会有100或1000次jiffies更新,可以说是0.1ms或1ms(而HPET和ACPI PM Timer的单位为ns,可见精度更高)
- tickless(NO_HZ模式):允许系统在空闲时关闭定时器,降低功耗。系统在空闲状态下不再产生周期性中断,而是根据定时器的到期时间动态安排中断,从而减少不必要的中断开销,并改善系统在低功耗状态下的表现。
高精度计时器:
- 提供纳秒级精度,例如hpet等
定时器中断:
- 由硬件触发,如APIC timer、PIT(可编程间隔定时器)
时间管理
系统时间
墙上时间:
指的是系统的“当前时间”,通常由CLOCK_REALTIME
表示。反映了真实世界的时间,这个时间可以通过系统管理员手动设置、从硬件实时时钟(RTC)读取,或者通过网络时间协议(NTP)以及HPET等进行矫正
单调时间: 是一个只会不断递增、不受外部时间调整影响的时间计数。通常由CLOCK_MONOTONIC提供,主要特点:
- 始终递增:无论系统时间如何调整,它总是保证不会倒退
- 适合测量时间间隔:用于计算事件之间的持续时间、超时或调度,而不必担心因墙上时间被修改而导致错误
二者区别:
- 使用场景:
- 墙上时间:适合需要显示或记录“真实”时间的应用,如日志记录、文件时间戳、用户界面显示等
- 单调时间:适合需要精确测量事件间隔的长i纪念馆,如性能测试、任务调度、超时管理等
- 底层实现:
- 墙上时间:依赖于硬件时钟(RTC)以及系统软件对时间的同步和矫正机制
- 单调时间:依赖于系统内部的高精度计时器(如TSC、HPET或其他硬件时钟),并经过内核抽象层保证其单调性和一致性
时钟同步
NTP与时间平滑调整: 系统时间可能会因网络时间同步(NTP)而进行调整,为了防止时间突然跳变(如突然回调或前移),内核采用slewing算法来平滑调整系统时间,使得时间校正过程对应用层的影响降到最低
多处理器同步: 在SMP系统中,确保各个CPU核心时间一致性是非常关键的。内核通过同步机制使得所有核心共享同一时间基准,避免因各核心时间不同步而引发调度和资源竞争问题
Watchdog
目的: 确保系统使用的时钟源稳定且精确。因为时钟源直接影响到系统时间的计算和更新,所以必须排除那些在短时间内发生大幅偏差的不可靠时钟源
背景: 在系统启动过程中,内核会注册多个clocksource,每个clocksource都会被赋予一个rating,表示它的精度和稳定性。为了避免由于硬件故障或环境因素导致某个时钟源计数异常,内核引入了watchdog机制来动态监控各个clocksource的表现
实现原理:
- 周期性检测:watchdog会定期检查所有支持watchdog功能的clocksource。他会读取当前的周期计数值,并与前一次检查时保存的值进行比较
- 预期增量计算:根据clocksource的已知频率、mult和shift参数,内核可以将计数器的增量转换为经过的时间,此时就有一个“预期”的时间增量值
- 偏差比较:如果实际读出的增量与预期值之间的差异超过了预定义的阈值,则认为该时钟源存在问题。
- 动态降级与选择:在检测到偏差过大时,watchdog会降低clocksource的rating,从而防止它被选为当前系统的主要时钟源。然后watchdog会选择rating更高的时钟源作为系统时间的基础
简单来说,所有支持watchdog的clocksource都会被加入一个list,内核利用定时器或专用内核线程定期遍历该列表