kvm timer虚拟化

2022-04-28 17:49:02 浏览数 (1)

在这篇中遗留了几个问题,先尝试回答一下,不一定准确,代码太多,看不过来,全靠猜测,代码的历史很长,都是智慧的结晶,一时半会消化不了很正常。

  • 有全局hpet和局部local apic timer,cpu会用哪个呢?

clock_event_device有rating,local apic timer是150,hpet是50,tick_check_preferred选择rating高的。

  • hpet中断哪个cpu处理?其它cpu收不到这个中断怎么tick呢?

不好说,可以用MSI和interrupt remapping,可以配置亲和。一个cpu收到得通知其它cpu,估计就是tick broadcast干的事情,可能得用ipi中断。

  • 软件定时器是全局的还是局部的?

lapic timer是per cpu,软件timer靠硬件驱动,感觉per cpu的好。

lapic timer

intel在lapic硬件单元实现的硬件定时器,提供了四个寄存器the divide configuration register, the initialcount and current-count registers, and the LVT timer register和三种模式,Periodic mode很省事,不需要频繁写寄存器,但不符合linux的需求,NO_HZ_IDLE和NO_HZ_FULL都会动态调整下次tick的时间,One-shot和TSC-Deadline有点像,One-shot 通过MMIO给 initial-count register写一个相对时间,比如10ms那就是10ms后来个中断,TSC-Deadline通过给IA32_TSC_DEADLINE MSR写一个tsc的绝对时间,cpu的tsc值到了这个绝对值就来个中断,感觉比One-shot好控制,cpu HZ高点,10ms干的活多,cpu HZ低点10ms干的活少,TSC-Deadline设置一个值 ,HZ高点,那么tsc涨得快,HZ低点tsc涨得慢,两次中断之间cpu干的活是固定的,所以最终linux选择了TSC-Deadline mode。

linux要正常运转,不能没有timer中断,就像人不能没有心跳,NO_HZ_IDLE和NO_HZ_FULL也只是把timer中断的周期拉长了一点。

kvm timer

host有自己的lapic timer,硬件实现,guest也有自己的lapic timer,kvm模拟。一个pcup上要运行很多个vcpu,每个vcpu都有自己的lapic timer,kvm要模拟很多个lapic timer,kvm用软件定时器hrtimer来模拟lapic timer,guest写tscdeadline msr,kvm把这个tsc值转换成一个软件定时器的值,启动软件定时器,硬件定时器驱动软件定时器,软件定时器超时后,假如硬件timer中断正好把vcpu exiting出来,那么设置timer interrupt pending,重新enter时把timer中断注入,如果vcpu运行在其它pcpu上,需要把vcpu kick出来,所以最好把timer绑定的物理cpu和vcpu所运行的物理cpu始终一致,如果vcpu运行的物理cpu变化了,migrate timer到新的物理cpu,这样中断来了vcpu自动exit,不再需要kick一次。

preemption timer是intel vmx技术增加的一种硬件timer,和tsc相关,在VMCS中设置一个值 ,vm entry,时间到了,preemption timer就会触发vcpu exiting出来。vcpu写tscdeadline msr exiting出来,kvm把这个值写到VMCS中,enter non-root,时间到了exiting出来,设置pending,然后重新进入把中断注入。

代码语言:javascript复制
kvm_set_lapic_tscdeadline_msr
  -->start_apic_timer
      -->restart_apic_timer
          {
            if (!start_hv_timer(apic))
		start_sw_timer(apic);
           }

这儿hv_timer就是preemption timer,sw_timer是软件hrtimer,有preemption timer就用hv_timer,没有就用sw_timer。hv_timer的问题就是可能时间没到,vcpu由于其它原因exit出来,那么就需要kvm_lapic_switch_to_sw_timer,再次enter时kvm_lapic_switch_to_hv_timer。

腾讯优化方案

惠伟:kvm timer导致exit过多的解决办法

接着这儿细分析

  • fastpath

写tscdeadline msr或者preemption timer导致exit出来后用vmx_exit_handlers_fastpath处理,for不break,那么kvm就不用做更多工作,能够快速重新进入non-root。

代码语言:javascript复制
	for (;;) {
		exit_fastpath = static_call(kvm_x86_run)(vcpu);
		if (likely(exit_fastpath != EXIT_FASTPATH_REENTER_GUEST))
			break;

        if (unlikely(kvm_vcpu_exit_request(vcpu))) {
			exit_fastpath = EXIT_FASTPATH_EXIT_HANDLED;
			break;
		}

		if (vcpu->arch.apicv_active)
			static_call(kvm_x86_sync_pir_to_irr)(vcpu);
    }

https://github.com/torvalds/linux/commit/ae95f566b3d22ade75c67827f1171594dacc9a03

https://github.com/torvalds/linux/commit/26efe2fd92e50822674acce1dbc4f2ac6fc1788f

  • post-interrupt

隔离出物理cpu,不再用preemption timer,把hrtimer运行在隔离出的物理cpu,hrtimer超时后隔离出的物理cpu用post-interrup方式把timer中断注入vcpu,这样vcpu不用exiting出来。

https://github.com/torvalds/linux/commit/4d151bf3b89e71490e69defc811579b2bde617e2

https://github.com/torvalds/linux/commit/0c5f81dad46c90792e6c3c4797131323c9e96dcd

  • not-running

vcpu写了tscdeadline msr exiting出来进入fastpath处理流程,然后被中断打断或者被其它task抢占,那么等fastpath有机会执行时时间已经到了,此时就不现需要设置hrtimer,直接执行apic_timer_expired,但如果设置了hrtimer,hrtimer的超时函数apic_timer_fn也调用apic_timer_expired,参数from_timer_fn表示是否来自超时函数,超时函数在另一个cpu执行,用post-interrupt没问题,但不设置hrtimer直接超时的,没必要用post-interrupt,此时用了post-interrupt,自己给自己发一个ipi中断,浪费cpu资源,反正再次enter时就可以把中断注入。

代码语言:javascript复制
static void apic_timer_expired(struct kvm_lapic *apic, bool from_timer_fn)
{
	if (!from_timer_fn && vcpu->arch.apicv_active) {
		WARN_ON(kvm_get_running_vcpu() != vcpu);
		kvm_apic_inject_pending_timer_irqs(apic);
		return;
	}
	if (kvm_use_posted_timer_interrupt(apic->vcpu)) {
		kvm_apic_inject_pending_timer_irqs(apic);
		return;
	}
}

https://github.com/torvalds/linux/commit/379a3c8ee44440d5afa505230ed8cb5b0d0e314b

腾讯真是精益求精,佩服佩服,这些问题都能发现,这得有多高超的观察力或者多精细的测试手段,不敢想象。

总结

虚拟化环境中tsc和timer的计算好复杂,还没有完全掌握,希望自己能坚持写下去,达到完全搞清楚。

0 人点赞