本博文为原创,遵循CC3.0协议,转载请注明出处:http://blog.csdn.net/lux_veritas/article/details/9284635

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

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

GVA,Guest Virtual Address客户机进程的线性地址
GPA,Guest Physical Address客户机的物理地址
HVA,Host Virtual Address宿主机进程的线性地址
HPA,Host Physical Address宿主机的物理地址

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

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

地址翻译的流程:

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

一条完整的地址翻译流程为,客户系统加载客户进程的gCR3,处于非根模式的CPU的MMU查询TLB,没有所请求的GPA到HPA的映射,在cache中查询EPT/NPT,若cache中未缓存,逐层向下层存储查询,最终获得gCR3所映射的物理地址单元内容,作为下一级客户页表的索引基址,根据GVA获得偏移,获得一条地址用于索引下一级页表,该地址为GPA,再由VCPU的MMU查询EPT/NPT,如此往复,最终获得客户机请求的客户页内容。假设客户机有m级页表,宿主机EPT/NPT有n级,在TLB均miss的最坏情况下,会产生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),返回给客户机,切换到非根模式继续运行。VCPU的mmu查询客户页表,发现为空,客户机产生缺页,不发生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-Exit,KVM负责捕捉该异常,并交由KVM的缺页中断机制进行相应的缺页处理。kvm_vmx_exit_handlers函数数组中,保存着全部VM-Exit的处理函数,它由kvm_x86_ops的vmx_handle_exit负责调用。缺页异常的中断号为EXIT_REASON_EPT_VIOLATION,对应由handle_ept_violation函数进行处理。

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

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

__direct_map函数分析:
建立EPT页表结构的函数为__direct_map,KVM用结构体kvm_mmu_page表示一个EPT页表项。__direct_map负责将GPA逐层添加到EPT页表中,若找到最终level的EPT页表项,调用mmu_set_spte将GPA添加进去,若为各级中间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,设置当前请求level的EPT页表项,该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,则创建该level的EPT页表项,执行如下:

3.
kvm_mmu_get_page,分配一个EPT页表页,即kvm_mmu_page结构

4.
__set_spte,设置页表项

References:

http://blog.stgolabs.net/2012/03/kvm-virtual-x86-mmu-setup.html

KVM地址翻译流程及EPT页表的建立过程的更多相关文章

  1. KVM硬件辅助虚拟化之 EPT in Nested Virtualization

    在嵌套虚拟环境(Nested Virtualization)下,执行在hypervisor上的Virtual Machine仍能够作为hypervisor去执行其他的Virutal Machine,而 ...

  2. 《CSAPP》地址翻译

    本节所使用的符号: 地址翻译 地址翻译是一个N元素的虚拟地址空间(VAS)中的元素和一个M元素的物理地址空间(PAS)中元素之间的映射. 映射实现: MMU利用页表来实现这种映射.CPU中的一个控制寄 ...

  3. 【硬核】MMU是如何完成地址翻译的

    目录 1. 什么是虚拟内存? 2. 虚拟内存的作用 3. 虚拟内存与物理内存 3.1 CPU存取数据 3.2 物理地址常用术语 3.3 虚拟地址常用术语 3.4 页表常用术语 3.5 页命中/缺页 4 ...

  4. onvif获取摄像头的流媒体地址完整流程

    linux设备上的Onvif 实现6:获取摄像头的流媒体地址完整流程 整体流程: Probe: 发现网络摄像头,获取webserver地址 http://192.168.15.240/onvif/de ...

  5. Linux内存管理学习2 —— head.S中的段页表的建立

    作者 彭东林 pengdonglin137@163.com 平台 TQ2440 Qemu+vexpress-ca9 Linux-4.10.17 正文 继续分析head.S: 此时r2存放的是设备树镜像 ...

  6. 临时2级页表的初始化过程 head_32.S 相关代码解释

    page_pde_offset = (__PAGE_OFFSET >> 20); /* __PAGE_OFFSET是0xc0000000,page_pde_offset = 3072 = ...

  7. Linux内存管理 (2)页表的映射过程

    专题:Linux内存管理专题 关键词:swapper_pd_dir.ARM PGD/PTE.Linux PGD/PTE.pgd_offset_k. Linux下的页表映射分为两种,一是Linux自身的 ...

  8. java 解决Hash(散列)冲突的四种方法--开放定址法(线性探测,二次探测,伪随机探测)、链地址法、再哈希、建立公共溢出区

    java 解决Hash(散列)冲突的四种方法--开放定址法(线性探测,二次探测,伪随机探测).链地址法.再哈希.建立公共溢出区 标签: hashmaphashmap冲突解决冲突的方法冲突 2016-0 ...

  9. 《Programming WPF》翻译 第8章 5.创建动画过程

    原文:<Programming WPF>翻译 第8章 5.创建动画过程 所有在这章使用xaml举例说明的技术,都可以在代码中使用,正如你希望的.可是,代码可以使用动画在某种程度上不可能在x ...

随机推荐

  1. 关于js小数计算的问题

    在js浮点运算中 var a=0.2-0.1; var b=0.3-0.2; console.log(a==b); 答案是什么呢,很多人可能认为是true,包括我在内,但是当我写出来运行了一下,我被答 ...

  2. UIImage拉伸显示

    下面张图片,是设计来做按钮背景的:  button.png,尺寸为:24x60 现在我们把它用作为按钮背景,按钮尺寸是150x50,以下是没有经过技术性拉伸处理的情况: // 得到view的尺寸   ...

  3. No Hibernate Session bound to thread, and configuration does not allow creat

    No Hibernate Session bound to thread, and configuration does not allow creat 今天遇到这么一个错误,在网上差了很多都没有能解 ...

  4. Bootstrap 轮播(Carousel)插件

    Bootstrap 轮播(Carousel)插件是一种灵活的响应式的向站点添加滑块的方式.除此之外,内容也是足够灵活的,可以是图像.内嵌框架.视频或者其他您想要放置的任何类型的内容. 如果您想要单独引 ...

  5. Android基础学习

    1.specify :指定 2.Nested:嵌套 3.启动模拟器时出现错误信息"Please ensure that adb is correctly located at:XXXXX&q ...

  6. Eclipse右键New菜单项的自定义设置

    大家都知道一般在Eclipse中新建一个项目或者文件都是通过菜单项的File-New来创建,然而有些右键出来的选项可能从来都不会使用到,而有些可能会常用到但是右键中又没有,这个时候就可以自定义New中 ...

  7. 不同框架实现的WebService的服务端获取HttpServletRequest的方法

    一. 基于xfire实现的WebService HttpServletRequest request = XFireServletController.getRequest(); 二. 基于axis实 ...

  8. VC模拟发送数据包-百度关键词查找

    VC模拟发送数据包-百度关键词查找 逗比汪星人2009-09-06上传   VC模拟发送数据包-百度关键词abcdef查找 详情 http://blog.csdn.net/wangningyu htt ...

  9. php获取html checkbox的值。

    一个小错误,搞了好久: <label><input class="short" type="checkbox" id="is_onl ...

  10. java常量使用比较好的方法

    1.首先建立一个工具类 public class AppConst { private static Map<String,String> map=new HashMap<Strin ...