KVM MMU EPT内存管理

本文深入探讨了KVM虚拟化环境中的内存管理,特别是EPT(扩展页表)和MMU(内存管理单元)的工作原理。讨论了GVA、GPA、GFN、HVA、HPA、HFN等地址概念,以及它们在虚拟机和宿主机之间的转换。文章还详细介绍了KVM中的memslots、pte_list_desc和spt页结构,并讲解了MMU页面的角色、权限和状态管理,包括直接映射、反向映射表以及页面回收和更新机制。
转载请注明:【转载自博客xelatex KVM】,并附本文链接。谢谢。
 
【注】文章中采用的版本:
Linux-3.11,https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.11.tar.gz
qemu-kvm,git clone http://git.kernel.org/pub/scm/virt/kvm/qemu-kvm.git, 
          git checkout 4d9367b76f71c6d938cf8201392abe4bfb1136cb
 
先说几个英文缩写:
  • GVA - Guest Virtual Address,虚拟机的虚拟地址
  • GPA - Guest Physical Address,虚拟机的虚拟地址
  • GFN - Guest Frame Number,虚拟机的页框号
  • HVA - Host Virtual Address,宿主机虚拟地址,也就是对应Qemu中申请的地址
  • HPA - Host Physical Address,宿主机物理地址
  • HFN - Host Frame Number,宿主机的页框号
KVM中目前一般采用硬件虚拟化来对内存的MMU进行虚拟化,以提高访存的效率。在比较老的虚拟化技术中,一般采用SPT(Shadow Page Table)来实现MMU虚拟化,所以在KVM代码中有很多地方依然采用类似的命名方式。本文主要针对Intel体系架构Intel VT-x中EPT在KVM中的应用进行说明。
 
一、内存槽(slot)的注册和管理
 
上文中我提到KVM只提供机制,不提供策略。为了实现对内存区域的管理,采用了kvm_memory_slot结构来对应Qemu中的AddressSpace。Qemu将虚拟机的线性地址(物理地址)在KVM中注册为多个内存槽,如BIOS、MMIO、GPU、RAW。

Qemu的内存模型请参考官方文档 https://github.com/qemu/qemu/blob/master/docs/memory.txt
 
KVM中memory slot的数据结构:

<include/linux/kvm_host.h>
struct kvm_memory_slot {
    gfn_t base_gfn; // 该slot对应虚拟机页框的起点
    unsigned long npages; // 该slot中有多少个页
    unsigned long *dirty_bitmap; // 脏页的bitmap
    struct kvm_arch_memory_slot arch; // 体系结构相关的结构
    unsigned long userspace_addr; // 对应HVA的地址
    u32 flags; // slot的flag
    short id; // slot识别id
};

<arch/x86/include/asm/kvm_host.h>
struct kvm_arch_memory_slot {
    unsigned long *rmap[KVM_NR_PAGE_SIZES]; // 反向映射结构(reverse map)
    struct kvm_lpage_info *lpage_info[KVM_NR_PAGE_SIZES - 1]; // Large page结构(如2MB、1GB大小页面)
}; 

KVM数据结构中与内存槽相关的结构,注意KVM对每个虚拟机都会建立和维护一个struct kvm结构。

<include/linux/kvm_host.h>
struct kvm {
	spinlock_t mmu_lock; // MMU最大的锁
	struct mutex slots_lock; // 内存槽操作锁
	struct mm_struct *mm; /* userspace tied to this vm,指向虚拟机内部的页存储结构 */
	struct kvm_memslots *memslots; // 存储该KVM所有的memslot
...
};

struct kvm_memslots {
	u64 generation;
	struct kvm_memory_slot memslots[KVM_MEM_SLOTS_NUM];
	/* The mapping table from slot id to the index in memslots[]. */
	short id_to_index[KVM_MEM_SLOTS_NUM];
};

kvm->memslots结构在创建虚拟机时被创建,代码见:

<virt/kvm/kvm_main.c>
static struct kvm *kvm_create_vm(unsigned long type)
{
...
    kvm->memslots = kzalloc(sizeof(struct kvm_memslots), GFP_KERNEL);
    if (!kvm->memslots)
        goto out_err_nosrcu;
    kvm_init_memslots_id(kvm);
...
}

内存槽的注册入口在 kvm_vm_ioctl函数中 case KVM_SET_USER_MEMORY_REGION部分,最终调用函数 __kvm_set_memory_region在KVM中建立与Qemu相对应的内存槽结构。 __kvm_set_memory_region函数主要做了如下几件事情:
  1. 数据检查
  2. 调用id_to_memslot来获得kvm->memslots中对应的memslot指针
  3. 设置memslot的base_gfn、npages等域
  4. 处理和已经存在的memslots的重叠
  5. 调用install_new_memslots装载新的memslot
虚拟机线性地址被分成若干个memslot,每个memslot是不能重叠的,也就是说每一段内存区间都必须有独立的作用。一般来说Qemu会对RAM、IO memory、High memory等分别注册若干个memslot


二、KVM MMU创建和初始化流程

2.1 KVM MMU的创建

KVM在vcpu创建时创建和初始化MMU,所以说KVM的MMU是每个VCPU独有的(但是有一些是共享的内容,后面会说到)。创建VCPU的代码起点是函数 kvm_vm_ioctl_create_vcpu

<virt/kvm/kvm_main.c>
static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, u32 id)
{
...
    vcpu = kvm_arch_vcpu_create(kvm, id);
...
    r = kvm_arch_vcpu_setup(vcpu);
...
}

该函数中首先调用 kvm_arch_vcpu_create创建vcpu,然后调用 kvm_arch_vcpu_setup初始化vcpu。在x86架构中,kvm_arch_vcpu_create最终调用vmx_create_vcpu函数进行VCPU的创建工作。MMU的创建在 vmx_create_vcpu => kvm_vcpu_init => kvm_arch_vcpu_init => kvm_mm
<think>好的,我现在需要回答用户关于KVMkvm_mmu_page_fault函数修复页表映射逻辑的位置的问题。首先,我得回忆一下KVM内存管理机制,尤其是处理缺页中断的部分。KVM作为Linux内核的模块,负责虚拟机的内存管理MMU部分尤为重要。 用户提到的kvm_mmu_page_fault函数应该是处理虚拟机缺页异常的核心函数。我记得当Guest虚拟机发生缺页时,会陷入到Host的KVM中进行处理,这个函数就是入口点。修复页表映射的逻辑应该在这个函数里或者它调用的其他函数中。 接下来,我需要考虑这个函数的结构。通常,这类函数会先获取导致缺页的地址(gpa),然后检查原因,比如是否由权限问题或页面不存在引起。接着,可能会调用下层函数来修复页表,比如填充页表项或者调整权限。 然后,我需要具体分析代码流程。kvm_mmu_page_fault可能会调用像tdp_page_fault这样的函数,尤其是在使用EPT的情况下。TDP(Two-Dimensional Page Table)处理的是第二层地址转换,即Guest物理地址到Host物理地址的映射。在tdp_page_fault中,可能会尝试分配物理页面,建立或更新EPT页表项。 另外,像__direct_map这样的函数可能负责直接映射,将gpa映射到hva,然后插入到EPT中。还有可能调用kvm_map_gfn来处理gfn到hva的映射,确保对应的页表项正确。 权限检查部分也很重要,比如检查是否可写或可执行,这可能会影响页表项的设置。如果是因为权限不足导致的缺页,可能需要调整页表项的权限位,或者进行写时复制(Copy-On-Write)的操作。 用户可能需要知道具体的代码位置,比如在Linux内核源码中的路径。KVM的相关代码通常在arch/x86/kvm/mmu/目录下,特别是mmu.c和tdp_mmu.c文件中。例如,tdp_page_fault函数可能在tdp_mmu.c里,而__direct_map在mmu.c中。 需要确认这些函数之间的调用关系,确保逻辑正确。比如,kvm_mmu_page_fault调用tdp_page_fault,后者再调用__direct_map,最终通过kvm_set_pte_atomic等函数设置页表项。 最后,我应该将这些步骤整理成一个清晰的流程,指出关键函数和它们在源码中的位置,帮助用户定位和理解修复页表映射的具体逻辑。</think>KVM中处理缺页异常并修复页表映射的核心逻辑主要分布在以下位置: 1. **入口函数** `kvm_mmu_page_fault()` 函数是缺页异常的入口点,位于Linux内核源码的: ``` arch/x86/kvm/mmu/mmu.c ``` 该函数会先收集错误码(error_code)、客户物理地址(gpa)等信息,判断缺页类型(写保护/不存在等)。 2. **EPT缺页处理(Intel环境)** 对于使用EPT的虚拟机,实际处理逻辑在: ``` arch/x86/kvm/vmx/vmx.c -> handle_ept_violation() ``` 最终会调用到: ``` arch/x86/kvm/mmu/tdp_mmu.c -> kvm_tdp_mmu_map() ``` 3. **直接映射核心函数** 页表项创建/修复的核心逻辑在: ```c __direct_map() // arch/x86/kvm/mmu/mmu.c ``` 该函数负责: - 计算页表层级(4级/5级分页) - 遍历客户物理地址到主机物理地址的映射 - 调用`tdp_mmu_map_handle_target_level()`填充EPT页表项 4. **页表项操作** 原子级页表项设置通过: ```c kvm_set_pte_atomic() // arch/x86/kvm/mmu/spte.h ``` 该函数使用`cmpxchg64`保证原子性操作。 5. **权限校验** 权限检查逻辑分散在: ```c FNAME(fetch)(struct kvm_vcpu *vcpu, ...) // arch/x86/kvm/mmu/paging_tmpl.h ``` 这里会验证NX位、用户/超级用户权限等。 **典型修复流程**: 1. 通过`vcpu->arch.walk_mmu->gva_to_gpa()`转换客户虚拟地址→客户物理地址 2. 调用`kvm_map_gfn()`获取主机物理地址(hpa) 3. 在`__direct_map()`中逐级填充EPT页表项 4. 通过`kvm_set_pte_atomic()`原子写入最终页表项 **关键调试手段**: - 开启`CONFIG_KVM_PROVE_MMU`可验证MMU状态 - `trace_kvm_mmu_spte_requested`跟踪页表项请求 - `kvm_mmu_destroy()`可dump当前MMU状态 建议结合QEMU的`info mem`和`info tlb`命令观察客户机页表状态变化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值