有些msr寄存器在用户态才有可能访问,内核态不会访问,那么我们vmexit的时候是不需要切换到host值的,而且vmcs里面没有保存相应的寄存器值,只在vcpu需要返回到用户态的时候才把这些msr的值保存到shared_msrs里面,同时切换到host之前保存的值。avi大神的patch解释了这种可以不用在内核态切换的寄存器的优化思路:https://lore.kernel.org/patchwork/cover/170941/
VMCS本身只对很少的一些MSR寄存器进行切换,所以原来大部分MSR的切换是依赖软件进行的,软件切换的开销很大,上述patch则是对此的优化。
这些寄存器一定不会在内核态被访问吗?KVM模块能保证自己不去访问,但是如何保证在内核开抢占的情况下,其他内核代码不会访问这些寄存器呢?AMD的svm里面比较诚实的提出了这种担心,侧面佐证了这确实是个隐患,需要开发者心中有数:
svm_vcpu_load: /* This assumes that the kernel never uses MSR_TSC_AUX */ if (static_cpu_has(X86_FEATURE_RDTSCP)) wrmsrl(MSR_TSC_AUX, svm->tsc_aux);
shared_msrs里面host值来自于vcpu_enter_guest调用kvm_x86_ops->prepare_guest_switch(vcpu)的时候保存的值。
vcpu返回用户态切换的过程是调用kvm_on_user_return函数,而这个函数也就是在kvm_x86_ops->prepare_guest_switch(vcpu)的时候调用kvm_set_shared_msr配置的,后者调用了内核函数:user_return_notifier_register(&smsr->urn); 这个函数是内核提供的返回用户态时刻的通知链接口,kvm用它来给vcpu返回挂了个钩子,如此实现了vcpu ioctl系统调用返回qemu时将shared_msrs切换回host之前保存的值。
kvm_on_user_return就是注册到内核的vcpu返回用户态时执行的逻辑,在切换shared_msrs的同时也把自己从通知链上注销了,因为一方面不需要别的进程在退出的时候也执行这个函数另一方面在vcpu enter guest的时候还会再注册的。
MSR从HOST切换到GUEST
该过程发生在vmentry时,最后调用链如下:
vcpu_enter_guest
kvm_x86_ops->prepare_guest_switch(vcpu) ==> vmx_save_host_state
kvm_set_shared_msr(vmx->guest_msrs[i].index, vmx->guest_msrs[i].data, vmx->guest_msrs[i].mask)
从host切到guest,guest的msr值从vmx->guest_msrs[i].data这里来
vmx->guest_msrs在vmx_get_msr时被读取,vmx_set_msr时被更改,vmx_get/set_msr一般在handle_rdmsr/wrmsr以及热迁移前后做save load msrs时被qemu触发调用。vmx_set_msr里面同样会调用kvm_set_shared_msr,这里其实更多是为了试一下是否可以设置成功,如果设置不成功那么要让vmx->guest_msrs里面相应的msr值保持原来的设置而不去更新,因为可能新设置的是个非法的值。实际上vmx_set_msr只需要更新guest_msrs就可以了,因为vmentry的时候还是会从guest_msrs里面读取再kvm_set_shared_msr。
MSR从GUEST切换到HOST
vmexit时并不会切share_msrs涉及到的msr,而是发生在vcpu要返回user mode之际,内核调用前面注册的通知函数,调用链如下:
prepare_exit_to_usermode
exit_to_usermode_loop
fire_user_return_notifiers
urn->on_user_return(urn) ==> kvm_on_user_return 找到对应的shared_msrs并恢复到vmentry前的host值
static void kvm_on_user_return(struct user_return_notifier *urn) { unsigned slot; struct kvm_shared_msrs *locals = container_of(urn, struct kvm_shared_msrs, urn); struct kvm_shared_msr_values *values; unsigned long flags; /* * Disabling irqs at this point since the following code could be * interrupted and executed through kvm_arch_hardware_disable() */ local_irq_save(flags); if (locals->registered) { locals->registered = false; user_return_notifier_unregister(urn); } local_irq_restore(flags); for (slot = 0; slot < shared_msrs_global.nr; ++slot) { values = &locals->values[slot]; if (values->host != values->curr) { wrmsrl(shared_msrs_global.msrs[slot], values->host); values->curr = values->host; } } }