qemu-kvm虚拟化时钟机制
LanceLiu
2019-10-16
qemu-kvm timer 相关设备实现机制 tsc实现机制 rtc实现机制 lapic timer实现机制 acpi_pm实现机制 kvm-clock实现机制 VMCS 存在VMCS OFFSET static void kvm_vcpu_write_tsc_offset(struct kvm_vcpu *vcpu, u64 offset)用于写offset kvm_vm_ioctl_create_vcpu() kvm_arch_vcpu_postcreate() kvm_write_tsc(vcpu, &msr); offset = 0 - rdtsc() Sub Topic 迁移为何引起tsc落后? kvm_arch_init()中kvm_timer_init()会设置vcp->arch.virtual_tsc_khz 读取 guest_read_tsc() kvm_scale_tsc(vcpu, host_tsc) + tsc_offset 启动时为何出现时钟超前或落后? 测试5次 1214、585、87、1232、1322、-43、586、-172、170 suspend为何引起kvm-clock时钟落后 虚拟机启动时读取时间? 配置了kvm-clock 未配置kvm-clock 调用kvm_get_wallclock读取时间 mach_get_cmos_time configure_rtc() base utc 标准时间 localtime 使用host时间 offset 指定从何时开始 driftfix slew 快速定位 lost_tick_policy ---> LOST_TICK_SLEW 启动 coalesced_timer 读取 初始化 会设置base rtc, 虚拟机通过cmos相关函数接口读取时得到时间精度为妙级 见RTC实现机制 初始化 相关问题 如何从kvm内核态进入用户态? 同其他应用程序一样 getboottime()为何调用set_normalized_timespec(ts, -boottime.tv_sec, - boottime.tv_nsec); kvm_write_wall_clock() wc = boot - ns_to_timespec(kvm_clock_offset) kvm_clock_offset = -ktime_get_boot_ns() = ktime_get_boottime() 该函数由启动时kvm_arch_init_vm()函数确定 diable kvm-clock,使用rtc机制 kvm_clock机制 kvm_write_wall_clock使用的是getboottime()没有算上monotonic中nsec部分 修改后: 464, 430, 598、401 基础部分 write_msr流程 wrmsr index --> ecx data --> edx << 32 + eax 在start_kernel()中的setup_arc() 调用kvmclock_init() 1. 为hv_clock分配一个PAGE页(4KB) 2. kvm_register_clock() 该步骤通过native_write_msr_safe()的方式通知VMM 将&hvclock[cpu].pvti传递给VMM vm_stop resume的影响 qemu-kvm在初始化时的kvmclock_init()会注册vm changer handler kvm_clock_vm_state_change() do_vm_stop() vm_start()中分别会调用vm_state_notify(),调用 vm_change_state_head链中的callback函数,最终调用kvmclock_vm_state_change() kvmclock_vm_state_change() kvmclock_current_nsec从guest中system_time_msr获取time 包含tsc_timestamp env->tsc kvm-clock guest time更新 vcpu->hv_clock [tsc_timestamp, system_time, ...] vcpu->last_guest_tsc vcpu_enter_guest() check 到 KVM_REQ_CLOCK_UPDATE 调用 kvm_guest_time_update() host kvm模块 kvm_arch_init_vm()中kvm->arch.kvmclock_offset = -ktime_get_boot_ns(); vcpu->hv_clock.tsc_timestamp = tsc_timestamp; vcpu->hv_clock.system_time = kernel_ns + v->kvm->arch.kvmclock_offset; vcpu->last_guest_tsc = tsc_timestamp; 迁移时对CPU状态的处理机制 process_incoming_migration_co() 中qemu_loadvm_state(QEMUFile *f)会调用 cpu_synchronize_all_post_init()函数,该函数会调用kvm_arch_put_registers(cpu, KVM_PUT_FULL_STATE);恢复CPU状态,其中包括CPU的tsc offset 源端cpu_pre_save()负责保存虚拟CPU状态 目的端cpu_post_load()负责加载虚拟CPU状态 见kvm-clock实现机制 cpu stop 相关 do_vm_stop() pause_all_cpus() 1. diable vm_clock Sub Topic vcpu thread持续处于qemu_kvm_wait_io_evnet()的 while(cpu_thread_is_idle(env))中 改进 bc20403598702fac96b5a732bdb184ccbe1fcb48 vs 6053a86fe7bd3d5b07b49dae6c05f2cd0d44e687 diff --git a/hw/i386/kvm/clock.c b/hw/i386/kvm/clock.c + * This reduces kvmclock difference on migration from 5s diff --git a/include/hw/i386/pc.h b/include/hw/i386/pc.h diff --git a/target/i386/kvm.c b/target/i386/kvm.c diff --git a/target/i386/kvm_i386.h b/target/i386/kvm_i386.h Machine compat_props用于何处? 限制及问题 rhel7.2 版本 内核 3.10.0-327 无KVM_CLOCK_TSC_STABLE 社区改进 2. 从pre_save_kvmclock()到真正的resume时间可能过长 self 改进 虚拟机cpu_presave()时同步最新tsc resume使用最新tsc更新kvm clock 测试结果 (1) 将迁移给wall time落后降低到10ms级别以内 tsc_deadline tsc_adjust MSR_IA32_TSC_DEADLINE lapic_next_deadline() wrmsr(MSR_IA32_TSC_DEADLINE, tsc + ((u64)delta) * TSC_DIVISOR) 默认: lapic_next_event() apic_write(APIC_TMICT, delta) HV模式 SW模式 当vmx支持preemption timer及 使能preemption timer时,使用hv模式 基于host hrtimer机制实现,基于guest 当前tsc和tscdeadline,设置hrtimer超时时间,当 hrtimer到达后,触发器callback apic_timer_fn,调用apic_timer_expired(apic) pend lapic 中断 set vmx->hv_deadline_tsc = tsc + delta_tsc; vmcs_set_bits(PIN_BASED_VM_EXEC_CONTROL, PIN_BASED_VMX_PREEMPTION_TIMER); vmx_vcpu_run() 中vmx_arm_hv_timer(cpu), vmcs_write32(VMX_PREEMPTION_TIMER_VALUE, delta_tsc) 计算delta_tsc,然后写入VMCS的VMX_PREEMPTION_TIMER_VALUE 当时间戳到达后,CPU退出guest模式,reason为 EXIT_REASON_PREEMPTION_TIMER,如此,调用handle_preemption_timer(),向 vcpu->arc.apic->lapic_timer.pending写入1,最终通过kvm_inject_apic_timer_irqs()注入 到vcpu中 kvm_set_msr_common() kvm_x2apic_msr_write() kvm_lapic_set_reg() set_target_expiration() restart_apic_timer(apic); 通过kvm_lapic_get_regs()获取apic->lapic_timer.period 再设置lapic_timer.target_expiration,后基于此设置hrtimer超时时间 tsc_adjust基本概念(Intel X86手册) 软件可以通过WRMSR指令写入IA32_TIME_STAMP_COUNTER_MSR修改 TSC值。由于这个写操作只会应用到逻辑CPU,软件寻求在多个逻辑处理器上同步TSC 值必须在每个逻辑处理器中执行这一操作。对软件来说这相比于让所有TSC在给定时间 有相同值更难。 通过使用64位的IA32_TSC_ADJUST MSR简化TSC修正同步。类似 IA32_TIME_STAMP_COUNTER_MSR, 每个逻辑处理器都各自维护 IA32_TSC_ADJUST 工作机制 1. RESET后IA32_TSC_ADJUST为0 2. 如果WRMSR 从TSC加(减)值X,逻辑处理器也会从IA32_TSC_ADJUST MSR中加或 减X Linux内核的使用 qemu-kvm虚拟化场景的影响 1. CPU初始化 2. cpu_enter_idle() 1. tsc_adjust在虚拟化场景下其实意义不大 2. 开启tsc_adjust特性,rdmsrl(IA32_TSC_ADJUST_MSR)会导致vmexit 3.若WRMSR从IA32_TSC_ADJUST MSR加(减)值X, 处理器中TSC也会加(减)相应值 void arch_cpu_idle_enter(void) { tsc_verify_tsc_adjust(); local_touch_nmi(); enter_idle(); } rdmsrl(MSR_IA32_TSC_ADJUST, curval); if (adj->adjusted == curval) return; /* Restore the original value */ wrmsrl(MSR_IA32_TSC_ADJUST, adj->adjusted); VMX_PREEMPTION_TIMER Sub Topic HOST kvm_set_lapic_tscdeadline_msr() start_apic_timer()