Linux时钟机制
时钟机制
基础
虚拟时钟
时间
和程序
tick couting 滴答计数: 设备发起周期性中断,操作系统处理中断,确定已经过了多
久
tickless counting 非滴答计数: 通过计数器来确定系统运行时间
wall-clock time: 绝对时间,
虚拟化上的问题
使用滴答计数的方式
1. 中断丢失, 虚拟机中时钟同物理机必然不一致
2. 横向扩展问题,N个虚拟机需要N*100 interrupts / s
使用非滴答计时
时钟能同真实时钟保持一致
一般情况下需要显式地通过hypercall通知虚拟机
对于运行时间敏感型应用,比如在周期时间计算迭代次数,可能使用滴答
计时方法更加合适,因为虚拟机可能会有时停止运行
PC时钟硬件
PIT
CMOS RTC
Local APIC
ACPI
TSC
HPET
最古老的PC时钟设备, 1.193182MHz 16位寄存器
timer 0 产生中断,用于系统计时, timer 1 历史上用于内存刷新(15us) timer 2用于连接
PC扬声器,产生拨号音
带电池内存设备,用于PC关机后保持PC BIOS配置稳定
2 ---> 8192 HZ 产生周期中断 计数器不能被读写
update inerrupt和alarm interrupt
输入频率同前端内存总线频率一致 但是软件无法确定其频率 需要借助于PIT和CMOS
时钟做近似估计
Power Management timer 24位计数器 通过编程使其当高位改变时产生中断
3.579545MHz
timestamp counter 时间戳计数器 目前最精准的也是最方便的计时设备 以CPU主频为
频率
类似APIC时钟,软件无法获取精确的TSC频率,需要借助PIT和CMOS做近似估计
对于某些CPU,电源管理机制可能影响到CPU的时钟频率,进而影响TSC,最新CPU没
有这点限制
某些CPU在低功耗模式下会停止TSC
对于共享总线的SMP系统,所有TSC以相同的时钟运行,可以认为是系统级的时钟,但
对于NUMA系统,各个Node上的TSC可能会有2%以内的误差
可通过rdtsc指令读取
需要1-2us的时间读取
提供多个timer
设置超时会同counter竞争,导致短时间超时会很久很久以后才会被触发
设计用于替代PIT和CMOS的周期时钟,但大多数现代硬件系统仍然使用PIT和CMOS,
无需HPET
Linux内核时钟处理机制
入口点 update_wall_time()
两处调用 以谁为准?
CLOCK类型
REALTIME 墙上时间
MONOTONIC 单调时间,受adjtime()和NTP影响
MONOTONIC_RAW 单调时间,不受adjtime()和NTP影响
CLOCK_BOOTTIME 类似CLOCK_MONOTONIC,但是会算上系统suspend时间
timekeeper shadow_timekeeper
用于更新系统时间过程,在update wall clock时首先将时间调整值设定
shadow_timekeeper,然后再一次性copy到真正的timekeeper,如此减少持有
timekeeper_seq锁时间,再其他过程,需要sync shadow timekeeper
初始化
start_kernel()
time_keeping()函数read_persistent_time()读取时间
time_init()函数将x86_late_time_init()赋值给late_time_init函数指针
调用x86_late_time_init()
默认调用x86_init.timers.timer_init() 该函数默认为hpet_time_init()
对于其他的环境,比如说XEN、KVM,会设置为其他相应函数
hpet_time_init()首先会使能hpet,在使能hpet时钟失败的情况下,
会创建pit timer,之后会创建时钟中断处理入口,即为irq0中断号
设置中断处理函数及中断标志,该timer_interrupt()即调用global_clock_event-
>event_handler(global_clock_event)
调用tsc_init()初始化tsc 这一函数过程中,会
获取cpu频率和tsc频率
clocksource
问题
1. global_clock_event->event_handler(global_clock_event) 该函数callback
在哪里赋值
tsc
frequency
tsc_init()中会调用x86_platform.calibrate_tsc() 即native_calibrate_tsc()函数通过
cpuid命令从CPU获取tsc freq
worker_thread tsc_refine_calibration_work() 中会进行修正
1. 内核维护update_wall_time()
tick_periodic()调用
tick_do_update_jiffies64()
墙上时间更新
由timer_interrupt()调用 tick_handle_periodic()
系统初始化时钟setup_pit()或者setup_hpet()时,会判断此时CPU0的time_tick_device
尚未初始化,由此,其会设置模式未TICKDEV_MODE_PEERIODIC,由此模式,会将
tick_handle_periodic函数赋值刚给该设备(i8253_clockevent), 而clockevent_i8253_init()
完成后,会将global_clock_event设置为&i8253_clockevent,由此,irq0 中断到达后,会最
终调用tick_periodic(),然后update_wall_time()
cpu lapic timer 中断触发调用
#0 tick_broadcast_setup_oneshot (bc=0xffffffff81ba6c80 <i8253_clockevent>) at kernel/time/tick-
broadcast.c:838
#1 0xffffffff810f2784 in tick_broadcast_switch_to_oneshot () at kernel/time/tick-broadcast.c:890
#2 0xffffffff810f2a7a in tick_switch_to_oneshot (handler=<optimized out>) at kernel/time/tick-oneshot.c:85
#3 0xffffffff810f2b45 in tick_init_highres () at kernel/time/tick-oneshot.c:114
#4 0xffffffff810b5454 in hrtimer_switch_to_hres () at kernel/hrtimer.c:705
#5 hrtimer_run_queues () at kernel/hrtimer.c:1417
#6 0xffffffff8109a2e1 in run_local_timers () at kernel/timer.c:1402
#7 update_process_times (user_tick=0) at kernel/timer.c:1376
#8 0xffffffff810f0f8b in tick_periodic (cpu=cpu@entry=2) at kernel/time/tick-common.c:76
#9 0xffffffff810f1011 in tick_handle_periodic (dev=0xffff88007fd0d100) at kernel/time/tick-common.c:88
#10 0xffffffff81053895 in local_apic_timer_interrupt () at arch/x86/kernel/apic/apic.c:925
#11 0xffffffff8174dd2d in smp_apic_timer_interrupt (regs=<optimized out>) at arch/x86/kernel/apic/apic.c:949
#12 <signal handler called>
clock_event
1. 初始时 系统初始化时钟setup_pit()或者setup_hpet()时,会判断此时CPU0的
time_tick_device尚未初始化,由此,其会设置模式未TICKDEV_MODE_PEERIODIC,由
此模式,会将tick_handle_periodic函数赋值刚给该设备(i8253_clockevent), 而
clockevent_i8253_init()完成后,会将global_clock_event设置为&i8253_clockevent,由
此,irq0 中断到达后,会最终调用tick_periodic(),然后update_wall_time()
每个cpu 都有lapic timer setup_APIC_timer()时会注册, 当lapic timer被启用后,位于
CPU0 上之前的clock event device(global_clock_evt)被shutdown, cpu0上clock event
device 变为lapic_clockevent
tick_check_nohz_this_cpu()
tick_nohz_update_jiffies(now)
当tick_stop时被调用
tick_nohz_restart_sched_tick()
tick_sched_do_timer()
tick_nohz_handler()
当exit_idle()时被调用
进入idle taks后,会根据需要stop tick
如此,当apic timer interrupt产生后,最终进入tich_check_nohz_this_cpu()进行处理,更
新wall_time()
Created With
MindMaster