tsc_deadline和tsc_adjust虚拟化场景的影响

tsc_deadline和tsc_adjust虚拟化场景影响

1. tsc_deadline及其影响

Linux上tsc_deadline机制说明

tsc_deadline是tsc机制的一种,通过cat /proc/cpuinfo可以获取当前cpu是否支持tsc特性。对于local APIC timer机制,有3中timer机制,分别是:

  1. one-shot模式,这种模式下,通过编程赋初始值到当前计数寄存器,该寄存器接下来会随着时间推移自减,当到达0的时候,会产生一个时钟中断,直到下次重新编程,该值一直处于0。即单次触发。
  2. periodic模式,同ones-shot模式类似,也是基于计数器自减,但不同的是,当时钟中断产生后,会重新开始计数器自减,即周期性产生时钟中断。
  3. tsc_deadline模式,tsc-deadline模式下,当本地cpu的tsc大于等于其IA32_TSC_DEADLINE MSR的值时触发lapic timer中断。

对于操作系统,其初始启动时都使用的PERIODIC模式,当可用作HRES的时钟源注册后,更优的时钟源被选择会触发tick_device的工作模式切换,即从PERIODIC模式切换为ONESHOT模式。对于lapic tick device,其初始化的setup_APIC_timer()函数中,会判定CPU是否支持tsc_deadline,若支持,则会将其set_next_event函数指针赋值为lapic_next_deadline,即通过tsc_deadline机制产生时钟中断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
static int lapic_next_deadline(unsigned long delta,
struct clock_event_device *evt)
{
u64 tsc;

tsc = rdtsc();
wrmsrl(MSR_IA32_TSC_DEADLINE, tsc + (((u64) delta) * TSC_DIVISOR));
return 0;
}

/*
* Setup the local APIC timer for this CPU. Copy the initialized values
* of the boot CPU and register the clock event in the framework.
*/
static void setup_APIC_timer(void)
{
struct clock_event_device *levt = &__get_cpu_var(lapic_events);

if (this_cpu_has(X86_FEATURE_ARAT)) {
lapic_clockevent.features &= ~CLOCK_EVT_FEAT_C3STOP;
/* Make LAPIC timer preferrable over percpu HPET */
lapic_clockevent.rating = 150;
}

memcpy(levt, &lapic_clockevent, sizeof(*levt));
levt->cpumask = cpumask_of(smp_processor_id());

if (this_cpu_has(X86_FEATURE_TSC_DEADLINE_TIMER)) {
levt->name = "lapic-deadline";
levt->features &= ~(CLOCK_EVT_FEAT_PERIODIC |
CLOCK_EVT_FEAT_DUMMY);
levt->set_next_event = lapic_next_deadline;
clockevents_config_and_register(levt,
tsc_khz * (1000 / TSC_DIVISOR),
0xF, ~0UL);
} else
clockevents_register_device(levt);
}

2. QEMU-KVM虚拟化场景下tsc_deadline及VMX_PREEMPTION_TIMER的原理分析

由上述可知,对于QEMU-KVM虚拟化场景,oneshot模式的lapic timer设定机制有2种:

  1. TSC_DEADLINE:即guest os的lapic驱动在设置下一时钟中断,通过wrmsr(MSR_IA32_TSC_DEADLINE,),触发CPU从guest模式退出到VMM,kvm模块截获EXIT REASON后,通过kvm_set_lapic_tscdeadline_msr()函数设置下次lapic时钟中断。
  2. 无TSC_DEADLINE特性,此时guest kernel通过apic_write(APIC_TMICT, delata)写入下次触发间隔,触发VM_EXIT,kvm模块调用kvm_set_msr_common()–> kvm_x2apic_msr_write()–>kvm_lapic_set_reg()-> set_target_expiration()+restart_apic_timer(apic)设置下次时钟中断。

####关于VMX_PREEMPTION_TIMER机制
VMX_PREEMPTION_TIMER机制主要影响lapic timer触发机制:当使能VMX_PREEPTION_TIMER机制时,当timer时刻点到达时,CPU会直接从guest模式EXIT,其EXIT REASON为EXIT_REASON_PREEMPTION_TIMER,如此,通过handle_preemption_timer()对vcpu->arch.apic.laic_timer.pending加1,后续可通过该pending值大于0判定此时需要注入lapic timer中断,通过kvm_inject_pending_timer_irqs()进行注入。
当不使能VMX_PREEPTION_TIMER机制时,host上基于hrtimer机制设置guest vcpu lapic timer,即当hrtimer到达后,触发器callback apic_timer_fn,调用apic_timer_expired(apic) pend lapic中断。但为vcpu 注入中断机制一致。不同的是,此时host需进入中断处理流程。

3. 影响

从上述可知,使用tsc_deadline和不使用tsc_deadline机制是类似的,guest设置apic timer时都需EXIT到VMM进行处理,理论上对虚拟机性能影响差异不大。若使用VMX_PREEMPTION_TIMER机制,会导致EXIT_REASON_PREEMPTION_TIMER,但应会减小host上apic timer中断,即最终应减小guest上的irq_exit,而host apic timer需host内核进入中断处理,因此整体开销应会大于使用VMX_PREEMPTION_TIMER机制的情况。

2. tsc_adjust原理分析及影响

1. tsc_adjust机制

通过cat /proc/cpuinfo可获取cpu是否支持tsc_adjust特性,tsc_adjust主要用于CPU tsc软件同步。软件可以通过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
  3. 若WRMSR从IA32_TSC_ADJUST MSR加(减)值X, 处理器中TSC也会加(减)相应值

    2. Linux内核的使用

    对于tsc_adjust机制,Linux内核的使用主要体现在两方面:
  4. CPU初始化时同步tsc
  5. cpu_enter_idle()时恢复MSR_IA32_TSC_ADJUST 值,因为某些场景下该值可能丢失

3. qemu-kvm虚拟化场景下的影响分析

对于虚拟化场景,由于其tsc本身就是基于host cpu获取的,因此可以理解tsc_adjust在虚拟化场景下意义不大。而开启tsc_adjust特性后,cpu_enter_idle()时的rdmsr(MSR_IA32_TSC_ADJUST)会导致CPU退出到guest模式,引起VM_EXIT。因此最好是disable这一特性。