在 Guest系统中IO访问可能分三种:
1,guest 内部执行PMIO指令,guest 会触发VM_EXIT,退出状态是KVM_EXIT_IO;
2,guest内部执行对MMIO空间(如virtio设备的feature协商空间、NOTIFY、设备特有配置空间等)的访问,guest 会触发VM_EXIT,退出状态将是KVM_EXIT_MMIO;
3,guest 内部对ram物理内存的访问,会通过EPT硬件(x86)转化为HPA,进而完成对物理内存的读写(但如果是第一次访问由于EPT页表不存在,会发生EPT_VIOLATION,触发VM_EXIT进而建立EPT表项);
MMIO是将设备I/O映射到 guest 地址空间内,它的实现需要利用EPT机制,guest物理内存(GPA)的虚拟化也是通过EPT机制来完成的。那么VM_EXIT后,KVM需要区分是对MMIO地址的访问还是对ram物理地址的访问,即KVM怎么识别MMIO的pagefault与ram 物理内存的pagefault?是通过EPT页表项的页表项的reserved bits区分。
EPT pagefault:
static int (*kvm_vmx_exit_handlers[])(struct kvm_vcpu *vcpu) = {
[EXIT_REASON_EPT_VIOLATION] = handle_ept_violation, //EPT_VIOLATION表示的是对应的物理页不存在,类似“page not present” pagefault;
[EXIT_REASON_EPT_MISCONFIG] = handle_ept_misconfig, //EPT_MISCONFIG表示EPT页表中有reserved域被设置(KVM就是通过设置EPT页表中的reserved
//bits 来表示该页表代表MMO regions),类似“reserved bit set” pagefault;
intel 手册:
An EPT misconfiguration occurs if the entry is present and a reserved bit is set.
EPT misconfigurations result when an EPT paging-structure entry is configured with settings reserved for future functionality.
EPT页表的reserved bits怎么设置的?
对于ram 普通内存,QEMU/KVMTOOL 会调用 ioctl(kvm->vm_fd, KVM_SET_USER_MEMORY_REGION, &mem) 对其进行注册(即将ram内存的HVA和GPA的关系注册到KVM,并分配SLOT), 而对于 MMIO 内存空间,不会调KVM_SET_USER_MEMORY_REGION 注册。
当 guest 第一次访问MMIO地址时发现它对应的GPA在EPT页表中不存在,将导致EPT_VIOLATION触发VM_EXIT而退回到KVM,KVM检查GPA时发现它的PFN不存在(因为QEMU/KVMTOOL不会调用KVM_SET_USER_MEMORY_REGION对MMIO地址空间进行注册),认为这是个MMIO地址,KVM会设置PFN为KVM_PFN_NOSLOT(eg, try_async_pf()),然后set_mmio_spte函数调用is_noslot_pfn(pfn)判断PFN是NOSLOT,就调用mark_mmio_spte()来标志它的spte是一个MMIO region。后面再次访问这个MMIO空间的GPA地址时,发现GPA对应的EPT页表的reserved bits被置位,就会产生ept misconfiguration异常(EPT_MISCONFIG),进而调用handle_ept_misconfig -> handle_mmio_page_fault -> x86_emulate_instruction 来处理所有的MMIO操作了。
try_async_pf() :
/* Don't expose private memslots to L2. */
if (is_guest_mode(vcpu) && !kvm_is_visible_memslot(slot)) {
*pfn = KVM_PFN_NOSLOT;
*writable = false;
return false;
}
/* noslot pfn indicates that the gfn is not in slot. */
static inline bool is_noslot_pfn(kvm_pfn_t pfn)
{
return pfn == KVM_PFN_NOSLOT;
}
static bool set_mmio_spte(struct kvm_vcpu *vcpu, u64 *sptep, gfn_t gfn,
kvm_pfn_t pfn, unsigned int access)
{
if (unlikely(is_noslot_pfn(pfn))) {
mark_mmio_spte(vcpu, sptep, gfn, access); //MMIO空间是KVM通过设置spte的保留位来标志的
return true;
}
return false;
}
mark_mmio_spte() -> make_mmio_spte(vcpu, gfn):
u64 gpa = gfn << PAGE_SHIFT;
mask |= shadow_mmio_value | access;
mask |= gpa | shadow_nonpresent_or_rsvd_mask;
mark_mmio_spte() -> mmu_spte_set(sptep, mask):
__set_spte(sptep, mask); //真正设置MMIO EPT页表项
static void ept_set_mmio_spte_mask(void)
{
/*
* EPT Misconfigurations can be generated if the value of bits 2:0
* of an EPT paging-structure entry is 110b (write/execute).
*/
kvm_mmu_set_mmio_spte_mask(VMX_EPT_MISCONFIG_WX_VALUE, 0); // 设置shadow_mmio_value = 110b | SPTE_MMIO_MASK
// shadow_mmio_value(mask)就是要向 MMIO 的 EPT页表项中要写入的值,
// shadow_mmio_value = 110b | SPTE_MMIO_MASK = 110b | (3ULL << 52);
// 110b表示该页可读可写但是还未分配或者不存在,这显然是一个错误的EPT页表项;
// 3ULL << 52 设置reserved bits 用于标识为MMIO spte.
// 如果判断EPT页表项spte时(is_mmio_spte(spte)):spte&shadow_mmio_mask == shadow_mmio_mask,就会触发ept_msconfig;
}
reference
MMIO内存模拟原理_享乐主的博客-CSDN博客
MMIO Emulation
MMIO Emulation · kernelgo
|