kvm_mmu_get_page函数解析

请先阅读博文 《 __direct_map 函数解析之影子页表的构建


感谢Intel OTC的 wufeng、OenHan、chenhe、ruanshua、社区的@heart2011给予的帮助和支持 微笑


一、kvm_mmu_page 结构

在《KVM的vMMU相关数据结构及其影子页表关系分析》中,我已经讲解了kvm_mmu_page结构的含义和租用,以及同影子页表的关系,我们这里就不再赘述了,这里讲一讲上次没有提到的几个成员


spt 成员指向影子页表页,页中被分为多个spte。影子页表用的页面成为shadow page,一个页面中分为多个表项,每个表项成为spte,注意不论哪个级别,表项都成为spte。这个影子页表页的page描述符中的private指向了管理该页表页的kvm_mmu_page成员。
一个页表页,会被会被多个上级页表项引用到,那么所有引用该页表页的上级页表项称为parent_spte,存放在kvm_mmu_page的parent_ptes链表中,KVM给出了相关的辅助函数,以便方向查找的时候使用

static void mmu_page_add_parent_pte(struct kvm_vcpu *vcpu,
				    struct kvm_mmu_page *sp, u64 *parent_pte)
{
	if (!parent_pte)
		return;

	pte_list_add(vcpu, parent_pte, &sp->parent_ptes);
}

mmu_valid_gen:为有效版本号,开始时候,同vcpu->kvm->arch.mmu_valid_gen的版本号相同。当发现这两个版本号不相同的时候,说明页表页时无效的,可以快速碾压失效该页表页所管理的GUEST物理地址空间



当分配完一个kvm_mmu_page结构之后,会用其管理GUEST物理地址空间的起始GFN为key计算一个hash值,并上到哈希表arch.mmu_page_hash[]上,以便可以快速的根据gfn找到管理该物理地址空间的页表页;KVM相关的函数如下

	hlist_add_head(&sp->hash_link,
		&vcpu->kvm->arch.mmu_page_hash[kvm_page_table_hashfn(gfn)]);


同时只要是激活的页表页,也会加入arch.active_mmu_page链表中,以便在后面快速的释放内存


二、gfn参数的意义和其辅助函数

gfn在《 __direct_map 函数解析之影子页表的构建》一文中已经提到讲过了,它是该页表页所管理GUEST物理地址空间的起始gfn,而不是发生缺页地址所在页面的gfn。缺页gfn是被管理的GEUST物理地址空间中的一个值


KVM中利用起始gfn在哈希表arch.mmu_page_hash[]上,快速的查找管理该物理地址空间的页表页是否已经被分配了,代码如下

#define for_each_gfn_sp(_kvm, _sp, _gfn)				\
	hlist_for_each_entry(_sp,					\
	  &(_kvm)->arch.mmu_page_hash[kvm_page_table_hashfn(_gfn)], hash_link) \
		if ((_sp)->gfn != (_gfn)) {} else

	for_each_gfn_sp(vcpu->kvm, sp, gfn) {
......
		mmu_page_add_parent_pte(vcpu, sp, parent_pte);
......
		return sp;
	}

.细心的同学可能会问到,一个缺页gfn,既在level3页表页的管理空间内,也在level2页表页的管理空间内,那么gfn应该是哪个页表页的起始gfn呢?答案很简单,是level2的起始gfn。因为在__direct_map函数中,是从level4逐级往下处理的,当处理level3页表页的spte的时候,gfn为level3页表页管理GUEST物理地址空间的起始gfn,单发现已经有了指向level2页表页的基地址了,就不会调用kvm_mmu_get_page了;下一次循环的时候,gfn就被变更为level2页表页管理GUEST物理地址空间的起始gfn了,而spte中没有指向level1页表页基地址的指针,才会调用kvm_mmu_get_page,这时候传入的gfn是level2页表页管理GUEST物理地址空间的起始gfn。


三、 kvm_mmu_get_page 函数处理逻辑


static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu,
					     gfn_t gfn,
					     gva_t gaddr,
					     unsigned level,
					     int direct,
					     unsigned access,
					     u64 *parent_pte)
{
....
	/*设置角色,重要的是level,代表本页表页在影子页表中的级别*/
	role = vcpu->arch.mmu.base_role;
	role.level = level; 
	role.direct = direct;
	if (role.direct)
		role.cr4_pae = 0;
	role.access = access;
......
	/*
         * 超找页表页是否已经被其他上级的SPTE分配过了,如果是的话,就找到了,返回
         * 注意需要将本次的parent_spte加入到反向链表中
         */
	for_each_gfn_sp(vcpu->kvm, sp, gfn) {
......
		mmu_page_add_parent_pte(vcpu, sp, parent_pte);
......
		return sp;
	}
......
	/*没有分配页表页,则分配出来页表页及其管理结构*/
	sp = kvm_mmu_alloc_page(vcpu, parent_pte, direct);
......
	/*设置所管理GUEST物理地址空间的起始gfn和level角色*/
    	sp->gfn = gfn;
	sp->role = role;
    
	/*以所管理GUEST物理地址空间的起始gfn为key,加入到hash表arch.mmu_page_hash中*/
	hlist_add_head(&sp->hash_link,
		&vcpu->kvm->arch.mmu_page_hash[kvm_page_table_hashfn(gfn)]);
	/*初始化版本号*/	
	sp->mmu_valid_gen = vcpu->kvm->arch.mmu_valid_gen;
	/*初始化清空页表页中所有的SPTE为0*/
	init_shadow_page_table(sp);
......
	return sp;
}

有了前两节的背景知识,是不是感觉这段代码弱到爆?不解释了,自己看吧  微笑





int __kvm_set_memory_region(struct kvm *kvm, const struct kvm_userspace_memory_region *mem) { int r; gfn_t base_gfn; unsigned long npages; struct kvm_memory_slot *slot; struct kvm_memory_slot old, new; struct kvm_memslots *slots = NULL, *old_memslots; int as_id, id; enum kvm_mr_change change; r = check_memory_region_flags(mem); if (r) goto out; r = -EINVAL; as_id = mem->slot >> 16; id = (u16)mem->slot; /* General sanity checks */ if (mem->memory_size & (PAGE_SIZE - 1)) goto out; if (mem->guest_phys_addr & (PAGE_SIZE - 1)) goto out; /* We can read the guest memory with __xxx_user() later on. */ if ((id < KVM_USER_MEM_SLOTS) && ((mem->userspace_addr & (PAGE_SIZE - 1)) || (mem->userspace_addr != untagged_addr(mem->userspace_addr)) || !access_ok((void __user *)(unsigned long)mem->userspace_addr, mem->memory_size))) goto out; if (as_id >= KVM_ADDRESS_SPACE_NUM || id >= KVM_MEM_SLOTS_NUM) goto out; if (mem->guest_phys_addr + mem->memory_size < mem->guest_phys_addr) goto out; slot = id_to_memslot(__kvm_memslots(kvm, as_id), id); base_gfn = mem->guest_phys_addr >> PAGE_SHIFT; npages = mem->memory_size >> PAGE_SHIFT; if (npages > KVM_MEM_MAX_NR_PAGES) goto out; new = old = *slot; new.id = id; new.base_gfn = base_gfn; new.npages = npages; new.flags = mem->flags; if (npages) { if (!old.npages) change = KVM_MR_CREATE; else { /* Modify an existing slot. */ if ((mem->userspace_addr != old.userspace_addr) || (npages != old.npages) || ((new.flags ^ old.flags) & KVM_MEM_READONLY)) goto out; if (base_gfn != old.base_gfn) change = KVM_MR_MOVE; else if (new.flags != old.flags) change = KVM_MR_FLAGS_ONLY; else { /* Nothing to change. */ r = 0; goto out; } } } else { if (!old.npages) goto out; change = KVM_MR_DELETE; new.base_gfn = 0; new.flags = 0; }
最新发布
03-21
<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、付费专栏及课程。

余额充值