Linux时间子系统学习笔记
时间子系统
硬件时钟(RTC)
概述: 通常由电池供电,即时在系统断电时也能持续运行。系统会在启动时从RTC读取当前时间,作为系统时间的初始值,并在关机或定期校正时将系统时间写回RTC。
系统时间
墙上时间: 指的是系统的“当前时间”,通常由CLOCK_REALTIME
表示。反映了真实世界的时间,这个时间可以通过系统管理员手动设置、从硬件实时时钟读取,或者通过网络时间协议(NTP)进行矫正
单调时间: 是一个只会不断递增、不受外部时间调整影响的时间计数。通常由CLOCK_MONOTONIC提供,主要特点:
- 始终递增:无论系统时间如何调整,它总是保证不会倒退
- 适合测量时间间隔:用于计算事件之间的持续时间、超时或调度,而不必担心因墙上时间被修改而导致错误
二者区别:
- 使用场景:
- 墙上时间:适合需要显示或记录“真实”时间的应用,如日志记录、文件时间戳、用户界面显示等
- 单调时间:适合需要精确测量事件间隔的长i纪念馆,如性能测试、任务调度、超时管理等
- 底层实现:
- 墙上时间:依赖于硬件时钟(RTC)以及系统软件对时间的同步和矫正机制
- 单调时间:依赖于系统内部的高精度计时器(如TSC、HPET或其他硬件时钟),并经过内核抽象层保证其单调性和一致性
时钟源
概述: 是硬件计时器,用于提供高精度、连续不断的计数值,常用的时钟源包括TSC(跟cpu频率相关,且通过hpet等其他时钟源校准tsc频率)、HPET、ACPI定时器等。内核在启动过程中会探测可用的时钟源,并选择精度高、稳定性好的作为系统的主要时间计数器。
作用: 内核利用时钟源的计数值计算经过的时间,通过将计数器的变化量转换为实际时间(通常通过除以配置的时钟频率CONFIG_HZ),从而维护系统时间和其他时间相关功能。
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
时钟事件
简介
功能: 时钟事件设备负责生成定时中断,用以触发内核中断各类定时任务,例如,任务调度、定时器超时处理以及高精度定时器的触发都依赖于时钟事件设备(比如APIC定时器设备)
抽象接口: linux为了兼容不同硬件平台,定义了统一的时钟事件接口,这样无论底层硬件如何差异化,内核上层的定时器管理和调度代码都能以相同的方式调用,极大提高代码的可移植性和维护性
中断
上半部
定义: 中断处理的最初阶段,通常是在中断发生后立即执行的代码,上半部的目标是尽可能快速地处理中断,避免阻塞其他中断的发生
主要任务:
- 中断的快速响应
- 禁用本地中断
- 处理硬件或时钟中断源的标识和清除
- 避免长时间占用CPU
例如:
- 在clockevent场景中,硬件定时器会触发时钟中断,系统会立即进入上半部的中断处理流程,执行必要的硬件中断清除操作
- 在上半部中,中断管理程序会检查是否需要更新当前的时钟值,可能会触发任务调度
下半部
定义: 下半部为了延迟执行的任务,它在中断处理完后,上半部的执行完成后执行。下半部允许内核将一些需要较长时间执行的任务移到稍后完成,从而避免中断处理过程中阻塞系统响应。
主要任务:
- 处理上半部不能立即执行的任务
- 例如任务调度、定时器超时回调、更新调度队列等
- 使用软中断、任务队列、工作队列等机制来延迟处理
例如: 在clockevent中断后,可能需要将某些操作(如调度任务或更新系统状态)推迟到下半部来处理,避免在中断处理中阻塞其他中断
为什么要这样分
避免长时间阻塞: 一些耗时的任务应该放到下半部,避免影响系统的实时响应,从而提高中断响应性
优化CPU使用: 避免长时间在中断处理上下文中占用CPU,而是将一些工作延迟到非紧急的时机去做。
避免中断处理时间过长: 时间过长的ISR会对其他硬件中断的处理产生影响
例子: 假设有一个定时器中断,它每隔一定时间就会触发。这个定时器中断的上半部会处理一些简单的操作,比如清除中断标志位、更新时间戳,立即完成硬件状态的更新等。然后,它会将需要更复杂的操作(例如调度任务、更新进程状态等)推迟到下半部。
定时器机制
linux提供了多种定时器机制,以满足不同应用场景下的时间精度要求:
- 传统定时器:适用于对精度要求较低的任务,以链表的形式管理,每个定时器结构包含触发事件、回调函数等信息,定时器到期后相应的回调函数将被调用
- 高精度定时器:随着实时性和精度要求的提高,内核引入了高精度定时器。hrtimer利用时钟源提供的纳秒级计时信息,支持比传统定时器更精确的定时操作,广泛应用于实时调度、网络数据包处理、多媒体处理等对时间精度要求较高的场景
Tick机制
传统Tick机制: 采用周期性中断(tick)来维护jiffies计数器,这个中断频率由CONFIG_HZ定义,每一次tick中断都会驱动内核更新时间、处理定时器以及执行调度操作。然而, 固定频率的中断在系统空闲时仍会触发,导致不必要的CPU资源浪费和功耗增加
Tickless内核: 为了提高能效,linux内核引入了tickless模式(CONFIG_NO_HZ)。在这种模式下, 系统在空闲状态下不再产生周期性中断,而是根据定时器的到期时间动态安排中断,从而减少不必要的中断开销,并改善系统在低功耗状态下的表现。
jiffies与时间转换
jiffies
概述: 是一个全局计时变量,用来记录自系统启动以来经过的时钟tick数,虽然jiffies本身不是以秒为单位的,但通过与时钟频率(HZ)的结合,可以方便地将其转换为秒数或其他时间单位
单位: 本身单位是ticks,在内核中通过HZ来定义,一个tick通常是100或1000,这意味着每秒会有100或1000次jiffies更新,可以说是0.1ms或1ms(而HPET和ACPI PM Timer的单位为ns,可见精度更高)
时间转换
概述: 内核提供了多种辅助函数,将硬件时钟计数转换为标准时间格式,同时处理进位、舍入和溢出问题,这些转换确保了内核内部以及用户空间应用对时间的读取与处理都能保持一致性和准确性
时钟调整与同步
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,内核利用定时器或专用内核线程定期遍历该列表
内核实现与架构相关
代码模块: 主要分布在kernel/time目录下
架构特定实现: 在X86架构中,TSC的使用和校准是一个重要的环境,而在其他架构上可能会优先采用HPET或其他硬件计时器。但总体框架和抽象接口在各架构之间基本保持一致,确保内核时间子系统的统一性