KVM地址翻译流程及EPT页表的建立过程

原文: KVM地址翻译流程及EPT页表的建立过程


引:本文多处使用以下术语,声明如下:

GVAGuest Virtual Address客户机进程的线性地址
GPAGuest Physical Address客户机的物理地址
HVAHost Virtual Address宿主机进程的线性地址
HPAHost Physical Address宿主机的物理地址

KVM机制下,客户系统运行在CPU的非根模式,透明的完成地址翻译,即对客户机而言,一条客户机虚拟地址经MMU翻译为客户机物理地址而返回。但实际过程则稍微复杂,因为每一条客户机物理地址也都是真实存在于物理内存上的,而虚拟机所在的地址空间并不是真实的内存物理地址空间,所以客户域下的GPA需要再经过某种地址翻译机制完成到HPA的转化,才能够取得真实物理内存单元中的内容。

KVM提供了两种地址翻译的机制,基于软件模拟的影子页表机制,以及基于硬件辅助的扩展页表机制(IntelEPTAMDNPT)。详细的机制介绍参见Reference,这里重介绍开启硬件支持的EPT/NPT地址翻译情况。
 

地址翻译的流程:

与影子页表的构成略有不同(影子页表项存储的是GVA->HPA的映射),基于硬件辅助的地址翻译采用二维的地址翻译结构(two-dimensional),如以下两图所示。客户系统维护自身的客户页表,即完成GVA->GPA的映射,而具体的每一条客户页表项、客户页目录项都是真实存储在物理内存中的,所以需要完成GPA->HPA的映射,定位到宿主物理地址空间,以获得物理内存单元中的值。EPT/NPT页表就负责维护GPA->HPA的映射,并且EPT/NPT页表是由处理器的MMU直接读取的,可以高效的实现地址翻译。总结之,所谓“二维”的地址翻译结构,即客户系统维护自己的页表,透明地进行地址翻译;VMM负责将客户机请求的GPA映射到宿主机的物理地址,到真实的内存单元中取值。

 

一条完整的地址翻译流程为,客户系统加载客户进程的gCR3,处于非根模式的CPUMMU查询TLB,没有所请求的GPAHPA的映射,在cache中查询EPT/NPT,若cache中未缓存,逐层向下层存储查询,最终获得gCR3所映射的物理地址单元内容,作为下一级客户页表的索引基址,根据GVA获得偏移,获得一条地址用于索引下一级页表,该地址为GPA,再由VCPUMMU查询EPT/NPT,如此往复,最终获得客户机请求的客户页内容。假设客户机有m级页表,宿主机EPT/NPTn级,在TLBmiss的最坏情况下,会产生m*n次内存访问,完成一次客户机的地址翻译。

 

虽然影子页表与EPT/NPT的机制差距甚大,一个用于建立GVA->HPA的影子页表,另一个用于建立GPA->EPT的硬件寻址页表,但是在KVM层建立页表的过程却十分相似,所以在KVM中共用了建立页表的这部分代码。至于究竟建立的是什么页表,init_kvm_mmu()会根据EPT支持选项是否开启,选择使用哪种方式建立页表。

 

EPT页表的建立过程:

与宿主机页表建立过程相似,EPT页表结构也是通过对缺页异常的处理完成的。Guset处在非根模式下运行,加载新的客户进程时,将VMCS客户域CR3的值加载到gCR3寄存器(保存的是GPA),非根模式下的CPU根据EPT表寻址该GPA->HPA的映射,EPT页表的基址在VMCS域的EPTP保存。初始情况下客户页表、EPT页表均为空,映射未建立,发生EPT_VIOLATION,切换到根模式下,由KVM负责建立该GPA到宿主内存地址HPA的映射,此时映射已建立,取得该内存单元中的值(一条GPA),返回给客户机,切换到非根模式继续运行。VCPUmmu查询客户页表,发现为空,客户机产生缺页,不发生VM_Exit,由客户系统的缺页处理函数捕获该异常,创建客户页表,根据GVA的偏移产生一条新的GPA,客户机寻址该GPA,产生缺页异常,此时该GPA对应的EPT页表项不存在,发生EPT_VIOLATION,切换到根模式下,由KVM负责建立该GPA->HPA映射,再切换回非根模式,如此往复,直到非根模式下GVA最后的偏移建立最后一级客户页表,获得GPA,缺页异常退出到根模式建立最后一级EPT页表项,完成整个EPT页表的建立。

 

KVM对客户机缺页异常的处理,此处以Intel EPT为例,处理流程如图:

 

客户机运行过程中,首先获得gCR3的客户页帧号(右移PAGE_SIZE),根据其所在memslot区域获得其对应的HVA,再由HVA转化为HPA,得到宿主页帧号。若GPA->HPA的映射不存在,将会触发VM-ExitKVM负责捕捉该异常,并交由KVM的缺页中断机制进行相应的缺页处理。kvm_vmx_exit_handlers函数数组中,保存着全部VM-Exit的处理函数,它由kvm_x86_opsvmx_handle_exit负责调用。缺页异常的中断号为EXIT_REASON_EPT_VIOLATION,对应由handle_ept_violation函数进行处理。

tdp_page_faultEPT的缺页处理函数,负责完成GPA->HPA转化。而传给tdp_page_faultGPA是通过vmcs_read64函数(VMREAD指令)获得的。
gpa = vmcs_read64(GUEST_PHYSICAL_ADDRESS);

gfn_to_pfn函数分析:
GPAHPA的转化分两步完成,分别通过gfn_to_hvahva_to_pfn两个函数完成。
-gfn_to_hva首先确定gpa对应的gfn映射到哪一个kvm_memory_slot,通过kvm_memory_slot做一个地址映射(实际就是做一个线性的地址偏移,偏移大小为(gfn - slot->base_gfn) * PAGE_SIZE),这样就得到了由gfnhva的映射,实际获得的是GPA的客户物理页号到宿主虚拟地址的映射。
-hva_to_pfn利用获得的gfnhva的映射,完成宿主机上的虚拟地址(该虚拟地址为gfn对应的虚拟地址)到物理地址的转换,进而获得宿主机物理页框号pfn。此转换可能涉及宿主机物理页缺页,需要请求分配该页。

__direct_map函数分析:
建立EPT页表结构的函数为__direct_mapKVM用结构体kvm_mmu_page表示一个EPT页表项。__direct_map负责将GPA逐层添加到EPT页表中,若找到最终levelEPT页表项,调用mmu_set_spteGPA添加进去,若为各级中间level的页表项,调用__set_spte将下一级物理地址添加进去。详细函数分析:
1.for_each_shadow_entry宏:遍历每一级EPT页表

#define for_each_shadow_entry(_vcpu, _addr, _walker)    \

for (shadow_walk_init(&(_walker), _vcpu, _addr);\

    shadow_walk_okay(&(_walker));\

    shadow_walk_next(&(_walker)))


先介说明一下结构体struct kvm_shadow_walk_iterator

struct kvm_shadow_walk_iterator {

u64 addr; //寻找的GuestOS的物理页帧,即(u64)gfn << PAGE_SHIFT

hpa_t shadow_addr;//当前EPT页表项的物理基地址

int level; //当前所处的页表级别

u64 *sptep; //指向下一级EPT页表的指针

unsigned index;//当前页表的索引

};


shadow_walk_init负责初始化struct kvm_shadow_walk_iterator结构,

static void shadow_walk_init(struct kvm_shadow_walk_iterator *iterator,

    struct kvm_vcpu *vcpu, u64 addr)

{

//把要索引的地址赋给addr

iterator->addr = addr;

 

//初始化时,要查找的页表基址就是当前VCPU的根页表目录的物理地址

iterator->shadow_addr = vcpu->arch.mmu.root_hpa;

 

//说明EPT页表是几级页表

iterator->level = vcpu->arch.mmu.shadow_root_level;

 

if (iterator->level == PT32E_ROOT_LEVEL) {

iterator->shadow_addr

= vcpu->arch.mmu.pae_root[(addr >> 30) & 3];

iterator->shadow_addr &= PT64_BASE_ADDR_MASK;

--iterator->level;

if (!iterator->shadow_addr)

iterator->level = 0;

}

}


shadow_walk_okay查询当前页表,获得下一级EPT页表的基地址,或最终的物理内存单元地址,

static bool shadow_walk_okay(struct kvm_shadow_walk_iterator *iterator)

{

//若页表级数小于1,直接退出

if (iterator->level < PT_PAGE_TABLE_LEVEL)

return false;

 

//最后一级页表

if (iterator->level == PT_PAGE_TABLE_LEVEL)

if (is_large_pte(*iterator->sptep))

return false;

 

//获得在当前页表的索引

iterator->index = SHADOW_PT_INDEX(iterator->addr, iterator->level);

 

//取得下一级EPT页表的基地址,或最终的物理内存单元地址

iterator->sptep= ((u64 *)__va(iterator->shadow_addr)) + iterator->index;

return true;

}


shadow_walk_next索引下一级EPT页表

static void shadow_walk_next(struct kvm_shadow_walk_iterator *iterator)

{

iterator->shadow_addr = *iterator->sptep & PT64_BASE_ADDR_MASK;

--iterator->level;

}


2. mmu_set_spte,设置当前请求levelEPT页表项,该level值由tdp_page_fault中的level = mapping_level(vcpu, gfn)计算而得,若已经存在该EPT页表项,即is_rmap_spte(*sptep)为真,说明要更新页表结构,覆盖掉原来的页表项内容。

if (is_rmap_spte(*sptep)) {

/*

* If we overwrite a PTE page pointer with a 2MB PMD, unlink

* the parent of the now unreachable PTE.

*/

if (level > PT_PAGE_TABLE_LEVEL &&

   !is_large_pte(*sptep)) {

struct kvm_mmu_page *child;

u64 pte = *sptep;

 

 

child = page_header(pte & PT64_BASE_ADDR_MASK);

mmu_page_remove_parent_pte(child, sptep);

__set_spte(sptep, shadow_trap_nonpresent_pte);

kvm_flush_remote_tlbs(vcpu->kvm);

} else if (pfn != spte_to_pfn(*sptep)) {

pgprintk("hfn old %lx new %lx\n",

spte_to_pfn(*sptep), pfn);

drop_spte(vcpu->kvm, sptep, shadow_trap_nonpresent_pte);

kvm_flush_remote_tlbs(vcpu->kvm);

} else

was_rmapped = 1;

}

 

//设置页表项后,刷新TLB

if (set_spte(vcpu, sptep, pte_access, user_fault, write_fault,

     dirty, level, gfn, pfn, speculative, true,

     reset_host_protection)) {

if (write_fault)

*ptwrite = 1;

kvm_mmu_flush_tlb(vcpu);

}


若对应页表项不存在,即*iterator.sptep == shadow_trap_nonpresent_pte,则创建该levelEPT页表项,执行如下: 
3. kvm_mmu_get_page,分配一个EPT页表页,即kvm_mmu_page结构 
4. __set_spte,设置页表项 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值