在做KVM模块热升级的过程中碰到了这个坑,通读代码时本来以为msr在vcpu load和vcpu exit中进行切换,便忽略了kvm_shared_msr_cpu_online,没想到它能直接重置了host,连投胎的过程都没有,直接没办法debug,还是要多亏这个问题在某种情况下不必现,chengwei才更快找到原因,顺便看了一下kvm_shared_msrs机制,理清楚了问题的触发逻辑,记录如下。

代码版本:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git v5.1

1. kvm shared msr的作用

guest在发生VM-exit时会切换保存guest的寄存器值,加载host寄存器值,因为host侧可能会使用对应的寄存器的值。当再次进入VM即发生vcpu_load时,保存host寄存器值,加载guest的寄存器值。来回的save/load就是成本,而某些msr的值在某种情况是不会使用的,那边就无需进行save/load,这些msr如下:

这些msr只在userspace才会被linux OS使用,kernel模式下并不会被读取,具体msr作用见如上注释。那么当VM发生VM-exit时,此时无需load host msr值,只需要在VM退出到QEMU时再load host msr,因为很多VM-exit是hypervisor直接处理的,无需退出到QEMU,那么此处就有了一些优化。

2. kvm shared msr在KVM模块加载中的处理

kvm shared msr有两个变量,shared_msrs_global和shared_msrs,对应代码如下:

shared_msrs在kvm_arch_init下初始化:

shared_msrs_global在hardware_setup下初始化,就是将vmx_msr_index的msr index填充到shared_msrs_global中:

kvm_arch_init和hardware_setup都是在KVM模块加载过程中执行的。

3. kvm shared msr在VM创建中的处理

VM创建过程中执行kvm_arch_hardware_enable,继而kvm_shared_msr_cpu_online,shared_msr_update函数负责存储host的msr值

4. kvm shared msr在VM运行中的切换

前面提到host msr值已经保存在smsr->values[slot].host下,那么进入guest前则会执行vmx_prepare_switch_to_guest,通过kvm_set_shared_msr完成加载guest msr的动作。

另外因为guest可以也会设置guest msr而发生VM-exit,这一步则有vmx_set_msr来完成,此处可知,guest msr保存在vmx->guest_msrs中。

再说kvm_set_shared_msr,就是设置物理MSR值,同时将值保存在smsr->values[slot].curr。

5. user_return_notifier的作用

kvm_set_shared_msr末尾设置了smsr->urn.on_user_return为kvm_on_user_return,user_return_notifier_register将其注册到return_notifier_list,顾名思义,就是返回用户态时的通知链。

在do_syscall_64和do_fast_syscall_32都会处理到prepare_exit_to_usermode,在exit_to_usermode_loop中会执行fire_user_return_notifiers,将链表上的函数执行一遍。

实际上此时使用user_return_notifier_register只有kvm_set_shared_msr,看一下回调函数kvm_on_user_return,就干了两件事,将smsr->values[slot].host写入到msr中,和取消kvm_on_user_return的注册。

6. 问题出现的场景

当KVM模块A上的VM的某个VCPU运行在非用户态时,KVM模块B加载,因为kvm_shared_msr_cpu_online是通过kvm_arch_hardware_enable,hardware_enable_nolock在hardware_enable_all下的on_each_cpu函数上执行,即kvm_shared_msr_cpu_online被执行前,KVM模块A上的VCPU并没有发生从kernel->userspace,那么此时KVM模块B上kvm_shared_msrs.values[X].host存储的则是KVM模块A上VM的guest msr值,当KVM模块B上的VM切换到QEMU时,此刻host msr则被置为KVM模块A上VM的guest msr,于是就崩了。


kvm_shared_msrs机制与分析来自于OenHan

链接为:http://oenhan.com/kvm_shared_msrs

发表评论