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()