本博文为原创,遵循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映射到宿主机的物理地址,到真实的内存单元中取值。

一条完整的地址翻译流程为,处于非根模式的CPU加载客户进程的gCR3,由于gCR3是一条GPA,CPU需要通过查询EPT/NPT页表来实gCR3 GPA->HPA的转换。CPU MMU首先查询硬件的TLB,如果没有GPA到HPA的映射,在cache中查询EPT/NPT,若cache中未缓存,逐层向下层存储查询,最终获得gCR3所映射的物理地址单元内容,作为下一级客户页表的索引基址。如果还没有,CPU抛出EPT Violation,由VMM截获处理。根据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的值加载到CR3寄存器(保存的是GPA),非根模式下的CPU根据EPT页表寻址该GPA->HPA的映射,EPT页表的基址在VMCS执行域的EPTP字段中保存。初始情况下客户进程页表与该进程的父进程共享一套页表结构(COW机制),Guest CR3指向的客户物理页面为空页面,客户页表缺页,KVM采用不处理客户页表缺页的机制,虚拟机不退出,由客户机的缺页异常处理函数负责分配一个客户物理页面,将该页面物理地址回填,建立客户页表结构;完成该映射的过程需要将GPA翻译到HPA,此时该进程相应的EPT页表为空,产生EPT_VIOLATION,虚拟机退出到根模式下执行,由KVM捕获该异常,建立该GPA到宿主物理地址HPA的映射,完成一套EPT页表的建立,中断返回,切换到非根模式继续运行。VCPU的mmu查询下一级客户页表,根据GVA的偏移产生一条新的GPA,客户机寻址该GPA对应页面,产生客户缺页,不发生VM_Exit,由客户系统的缺页处理函数捕获该异常,从客户物理内存中选择一个空闲页,将该客户物理地址GPA回填给客户页表,此时该GPA对应的EPT页表项不存在,发生EPT_VIOLATION,切换到根模式下,由KVM负责建立该GPA->HPA映射,再切换回非根模式,如此往复,直到非根模式下GVA最后的偏移建立最后一级客户页表,分配GPA,缺页异常退出到根模式建立最后一套EPT页表。至此,一条GVA对应在真实物理内存单元中的内容,便可通过这一套二维页表结构获得。

虚拟机每一次运行,执行vcpu_enter_guest这个函数,载入ept页表

static int vcpu_enter_guest(struct kvm_vcpu *vcpu)
{... ...r = kvm_mmu_reload(vcpu);... ...
}

调用 kvm_mmu_load 函数,设置客户CR3

int kvm_mmu_load(struct kvm_vcpu *vcpu)
{int r;r = mmu_topup_memory_caches(vcpu);if (r)goto out;r = mmu_alloc_roots(vcpu);spin_lock(&vcpu->kvm->mmu_lock);mmu_sync_roots(vcpu);spin_unlock(&vcpu->kvm->mmu_lock);if (r)goto out;/* set_cr3() should ensure TLB has been flushed */kvm_x86_ops->set_cr3(vcpu, vcpu->arch.mmu.root_hpa);
out:return r;
}

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:

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

2.贴一篇好文章,配合视频讲解,方便理解http://dl.acm.org/citation.cfm?id=1346286&picked=formats

KVM地址翻译流程及EPT页表的建立过程相关推荐

  1. 临时内核页表的建立过程

    Motivation:当内核被解压到线性地址0x100000后,为了继续启动内核,即启动内核的第一个swapper进程,内核需要建立一张临时页表供其使用. 当内核从16位的实模式进入保护模式(通过在汇 ...

  2. Linux虚拟内存与线性地址翻译

    1. 虚拟内存 对于操作系统的使用者而言,内存就像是一个一排排按照从0到n被编好数字的收纳柜,每个柜子可以存放8个bit,也就是一个字节,我们将需要存放的信息切成若干个字节,放到连续的柜子中.以后我们 ...

  3. 端到端的地址翻译(虚拟地址是怎样取到相应高速缓存的数据的?)

    [0]写在前面-为什么需要虚拟存储器? 0.1)定义:虚拟存储器其实就是借用了磁盘地址空间,还记得当初我们安装CentOS,划分的swap 文件系统吗? 0.2)VM简化了链接和加载.代码和数据共享, ...

  4. QEMU中TCG翻译流程

    声明:本文使用的qemu源码版本为qemu-3.1.0-rc0 前言:qemu中采用事件驱动架构和并行架构相结合的方式来工作的.qemu中的线程主要有Vcpu线程,main_loop线程.I/O线程和 ...

  5. 深入理解地址翻译 CSAPP

    地址翻译, 用自己的话说一遍, 然后自己在draw.io画图理解. 术语 页表就是一个 页表条目(Page Table Entry  PTE)的数组,每一项(每个PTE)是[有效位,物理地址]. 我们 ...

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

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

  7. 深入了解MMU是如何完成地址翻译的?

    虚拟内存是现代操作系统中最伟大的发明之一.它为每个进程提供了一个一致的.私有的地址空间,让每个进程产生了一种自己在独享主存的错觉. 为了讲清楚MMU是如何一步一步完成地址翻译,取出数据的,本篇文章在前 ...

  8. 如何判断cpu是否支持二级地址转换SLAT(EPT)

    Windows 8 Consumer Preview 于2月正式发布,随后  Windows Server 8 Beta  也公布了下载.整体对比,Windows  8 在硬件方面的要求并不高,其最低 ...

  9. tlb cache操作系统地址翻译

    记录王道操作系统P211地址翻译题目 题目:1.有一个tlb与一个data cache 2.存储器以字节为编址单位 3.虚拟地址14位 4.物理地址12位 5.页面大小64B 6.tlb为四路组相联, ...

  10. 263-虚拟内存的地址翻译

    虚拟内存的地址翻译 地址翻译 形式上来说,地址翻译是一个N元素的虚拟地址空间(VAS)中的元素和一个M元素的物理地址空间(PAS)中元素之间的映射, MAP:VAS->PAS U ∅ 这里 下图 ...

最新文章

  1. 这只「蚂蚁」能拍照!普林斯顿造出微米级相机,个头缩小到50万分之一
  2. 【收藏】推荐系列:2008年第08期 总10期
  3. 独家 | 数据科学家应该避免的5种统计陷阱(附链接)
  4. Run-Time Check Failure #0,The value of ESP was not properly saved 错误解决
  5. showModalDialog sesission丢失
  6. 1.5.2 在IIS上配置ASP.NET(转)
  7. codeigniter:去掉 URL 中的 index.php
  8. 经典SQL回顾之晋级篇
  9. 百度超级链Xuper关于金额相关问题
  10. 20180517 迭代器
  11. 从零开始学Pytorch(零)之安装Pytorch
  12. autocad哪个版本最好用_分享家用游戏用Win10哪个版本最好最稳定(个人见解篇)...
  13. 力扣-547 省份数量
  14. BCB:内存泄漏检查工具CodeGuard
  15. matlab把句子分割成单词_将英语句子单个单词分割存储
  16. LaTex 之 各类括号总结
  17. #2微信小程序错误:音乐API调用音乐URL时出现错误
  18. SpringCloud 基本使用
  19. 【JS】购物车附带源码与素材图片压缩包
  20. Discuz! X2.5 插件盘点:自动邀请码注册

热门文章

  1. ios 加速计效果实现
  2. 如果能站在巨人的肩膀上
  3. 以太坊源码阅读【Transaction(交易模块)】
  4. 人工智能导论测试题——第1章绪论
  5. 微信公众号自动回复及多客服功能实现
  6. java实现pdf旋转_Java实现PDF文本旋转倾斜的方法
  7. 微信公众平台开发(九) 数据库操作
  8. VMware Workstation 15 Player 共享文件夹制作
  9. 从零开始学习CANoe 系列文章目录汇总
  10. 中职计算机应用基础表格制作说课稿,表格制作的说课稿