本文介绍KVM中percpu变量shared_msrs,以及其相关的一些的数据结构。
其最终是为了在VM entry和VM exit的时候切换host和guest之间几个重要的MSR的值。
数据结构1: vmx_msr_index[]数组
vmx_msr_index[]数组定义需要percpu变量中需要维护哪些MSR,这些MSR在host和guest中是独立的值。
1. 定义
1 | /* |
数据结构2: kvm_shared_msrs,__percpu变量
1. 定义
1 |
|
2. 创建
在x86.c中的kvm_arch_init()函数中,为每个CPU分配了一个percpu变量shared_msrs,为kvm_shared_msrs结构体1
2
3
4
5int kvm_arch_init(void *opaque){
...
shared_msrs = alloc_percpu(struct kvm_shared_msrs);
...
}
3. 初始化
数据结构3– shared_msrs_global
1. 定义
在x86.c中,定义了一个结构体struct kvm_shared_msrs_global和其一个static实例shared_msrs_global;1
2
3
4
5
6
7
struct kvm_shared_msrs_global {
int nr;
u32 msrs[KVM_NR_SHARED_MSRS];
}
static struct kvm_shared_msrs_global __read_mostly shared_msrs_global;
2. 初始化
在hardware_setup()函数对数据结构1 -- vmx_msr_index[]数组每一个成员调用kvm_define_shared_msr()函数对shared_msrs_global静态变量进行初始化,具体地:
- 将vmx_msr_index[]数组中每一个成员MSR存入shared_msrs_global.msrs[]数组中,
- 使用shared_msrs_global.nr记录shared_msrs_global.msrs[]数组的大小,即为vmx_msr_index[]数组的大小。
总结
shared_msrs_global为一个静态变量,其中所包含的MSR成员(shared_msrs_global.msrs[])与数据结构1 -- vmx_msr_index[]数组是一模一样的,并且顺序是一一对应的,只是shared_msrs_global中多了一个成员nr表示该数组的大小。
数据结构4 – vmx->guest_msrs
1. 定义
vmx->guest_msrs为strcut vcpu_vmx结构体中定义的一个struct shared_msr_entry的指针。其实质为struct shared_msr_entry的数组。1
2
3
4
5
6
7
8
9
10
11
12
13
14struct shared_msr_entry {
unsigned index;
u64 data;
u64 mask;
}
struct vcpu_vmx {
...
struct shared_msr_entry * guest_msrs;
int nmsrs;
int save_nmsrs;
bool guest_msrs_dirty;
...
}
2. 创建
在vmx_create_vcpu()函数中,vmx->guest_msrs = kmalloc(PAGE_SIZE, GFP_KERNRL)为其分配了一个4KB的page页。
3. 初始化
在vmx.c中的vmx_vcpu_setup()函数中,初始化vmx->guest_msrs[]数组,1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19static void vmx_vcpu_setup(struct vcpu_vmx *vmx)
{
...
for (i = 0; i < ARRAY_SIZE(vmx_msr_index); ++i) {
u32 index = vmx_msr_index[i];
u32 data_low, data_high;
int j = vmx->nmsrs;
if (rdmsr_safe(index, &data_low, &data_high) < 0)
continue;
if (wrmsr_safe(index, data_low, data_high) < 0)
continue;
vmx->guest_msrs[j].index = i;
vmx->guest_msrs[j].data = 0;
vmx->guest_msrs[j].mask = -1ull;
++vmx->nmsrs;
}
...
}
初始时,vmx->nmsrs=0,然后根据数据结构1 -- vmx_msr_index[]数组定义的不同的MSR,来填充vmx->guest_msrs[]结构:
具体地,使用rdmsr_safe()和wrmsr_safe()验证在实际的物理平台上可以读取对应的MSR,然后将其加入vmx->guest_msrs[]数组。
注意
存入vmx->guest_msrs[]中每一个元素的index不是MSR的index,而是该MSR在vmx_msr_index[]数组中的序号;data的初始值为0;mask的初始值为全1;
最后使用vmx->nmsrs记录vmx->guest_msrs[]数组的大小。
4. 操作/使用
find_msr_entry()
1 | struct shared_msr_entry *find_msr_entry(struct vcpu_vmx *vmx, u32 msr) |
该函数查询指定的msr是否在vmx->guest_msrs[]数组中有表示,如果有则返回对应的元素的指针,如果没有则返回NULL;
其中参数2:u32 msr,为要查找的MSR的 index编号值。
返回值: NULL,vmx->guest_msrs[]没有对应的MSR;!NULL, vmx->guest_msrs[]中MSR对应的元素的指针。
setup_msrs()
1 | /* |
该函数setup vmx->guest_msrs[]数组,当更新了vmx->guest_msrs[]数组中元素的data,mask之后,会调用该函数来重新setup,
其会按照MSR_STAR, MSR_LSTAR, MSR_SYSCALL_MASK, MSR_EFER, MSR_TSC_AUX的顺序进行处理,
注意 该顺序和初始化时使用的vmx_msr_index[]数组中顺序不一样
具体地,每一步处理首先会判断是否支持该MSR,然后判断vmx->guest_msrs[]数组中是否包含该MSR,如果包含则将MSR从vmx->guest_msrs[]中向前移,最后处理完后,vmx->guest_msrs[]数组中的MSR如果被支持,肯定是和前面的顺序是一致的。
并且,vmx->save_nmsrs保存该setup_msrs()函数处理的其中的几个MSR,即setup的有效的MSR的个数,以及将vmx->guest_msrs_dirty=true;表示调用过setup_msrs()函数。
最后调用vmx_update_msr_bitmap(),其检查msr_bitmap_mode是否改变,如果改变则更新vmx->vmcs01.msr_bitmap。
vmx_prepare_switch_to_guest()
1 | void vmx_prepare_switch_to_guest(struct kvm_vcpu * vcpu) |
在该函数中的for循环中,根据上一个函数setup_msrs()设置的vmx->guest_msrs_dirty以及vmx->save_nmsrs来依次调用kvm_set_shared_msr()函数。
kvm_set_shared_msr()
1 | int kvm_set_shared_msr(unsigned slot, u64 value, u64 mask) |
具体地kvm_set_shared_msr()函数取出当前cpu的percpu变量shared_msrs。
并将shared_msrs->values[slot].curr值更新为新的值,并且把该值写到物理的MSR寄存器中。
并检查该cpu的shared_msrs是否registered,如果没有,则注册。
注意,其中注册的kvm_on_uesr_return()函数:
- 如果该cpu的shared_msrs->registered,则设置其为false,并则user_return_notifier_unregister()该struct user_return_notifier.
- 对shared_msrs中所有values[],判断其host是不是==curr,如果不是的,那么将host的值写入物理的MSR寄存器,并更新curr的值为host。