目前的全虚拟化内存主要有两种解决方案:

1 完全使用软件实现的shadow page table 方案,已经在Xen/KVM中实现,性能比较令人满意
2 使用硬件辅助的全虚拟化方案如 AMD处理器支持的Nesting Page,在Xen中已经得到支持并成为(Hardware Assist Paging,简称HAP),在浏览相关源代码树时可以看到相关的文件,根据Xen summit的slide, HAP性能通常比SPT(shadow page table)高10%

下面我简单说一下影子页表的原理:
Xen/KVM 为客户操作系统提供一个虚拟的物理地址空间,称之为客户物理地址空间。运行在Xen domain中的客户操作系统(Guest Operating System,GOS)认为自己在物理机器上运行并使用该客户物理地址空间中的客户物理地址(Guest Physical Address, GPA)进行寻址。
GOS的页表(Guest Page talbe,GPT)维护的是线性地址(Linear Address,LA)到GPA的映射,但这也页表并不能给硬件使用,因为硬件需要使用从LA到机器物理地址(Host Physical Address,HPA)的映射。
Xen/KVM将GPA到HPA的映射记录在P2M表中(Physical to Machine Mapping, P2M),并为GPT的每个页表维护一个与其对应的硬件使用的页表,其中保存LA->HPA的映射,我们称之为SPT。

映射示意:
GPT:  LA->GPA  (由GOS维护)
SPT:  LA->HPA (由Xen/KVM维护,由GPT派生(同步)得到)
P2M: GPA->HPA

SPT正常工作的一个关键问题是SPT的映射项如何得到,专业上称作如何使GPT与SPT同步:
以Xen为例:
1) 早期的Xen shadow代码(成为shadowI)采用的是Lazy同步的方式,也就是说:
当客户操作系统修改了GPT,如果是权限下降,比如W->R,则当客户操作系统作出修改后会立即使用INVLPG,使TLB无效,这时Xen捕获INVLPG指令并根据其线性地址对SPT/GPT进行同步
当客户操作系统修改了GPT,如果是权限上升,比如R->W,则但客户操作系统访问该页面时会引发#PF,这时Xen捕获#PF,根据线性地址同步SPT/GPT
2) 现在的Xen shadow代码(shadowII),采用的Eager同步方式:
Xen监控对客户页表的操作(所有客户页所在页面对应的SPT项只读),当客户操作系统试图修改GPT时会捕获#PF,模拟该指令的执行并且同步SPT/GPT,
相比较shaodwI,减少了一次Trap

Xen实现SPT的关键入口是:
sh_page_fault()
具体情况很复杂,参考我上传的slide(2007格式的ppt,因为论坛附件上限的原因)
大致的流程:

sh_page_fault()
1 检查是否由客户操作系统本身引发的#PF,如果是则向客户操作系统注入#PF, 并返回
2 遍历GPT,将每级GPT记录在某结构中以便之后查询
3 根据之前记录的每级GPT, 同步或者创建相应的SPT
4 判断是否客户操作系统要对GPT进行修改,如果不是就返回
5 模拟客户操作系统对GPT的修改,并且同步相应的SPT
6 返回

另一个入口点是对INVLPG指令的处理,
sh_invlpg()
不再详细说明

http://bbs.chinaunix.net/attachment.php?aid=NDQxMzkwfDZjMjJiYzE2fDEzMTI3OTMwMzh8YzdiODg3UzlBZmRuRnNTZE92VE4vMzFRd0w3VkpWZTY0eFd2SldRZ0F0RHZtcmc%3D

 

Xen 内存虚拟化实现--辛晓慧

 

KVM的基本原理
shaoh.li@gmail.com

 

节前看了看kvm的实现,下面是一点心得体会。
KVM的基本结构
-----------------
| guest OS |
-------------- |-----------------|
| 进程 | | qemu |
-------------- ------------------
-----------------------------------------------------
|Linux kernel [KVM module] |
-----------------------------------------------------

KVM 可以做成一个module,目前它不支持para-virtualization. KVM要求CPU支持虚拟技术,建议找个core 2 duo试试。加载kvm和kvm-intel模块后系统建立一个新的字符设备/dev/kvm(如果你的udev版本比较老,可能需要手动建立这个文件。注:最新的kernel2.6.21-rc*为kvm建立了一个新的虚拟文件系统,但下面讲的基本的原理没变)。这个设备提供ioctl和mmap操作。ioctl操作可以达到对虚拟cpu的控制,对io的访问则是由软件来模拟。目前kvm使用qemu来做io的模拟。qemu本身有软件的cpu模拟,但kvm没有使用,因此qemu的代码中有很多东西对kvm是多余的,将来很有可能会被剥离掉。一个虚拟机由qemu来加载,大致上qemu的执行流程如下:
1. KVM ioctl KVM_SET_MEMORY_REGION (kvm_dev_ioctl_set_memory_region), 设置guest内存。KVM模块将分配所有的内存。这些内存不能被swap out.
2. ioctl KVM_CREATE_VCPU (kvm_dev_ioctl_create_vcpu)。分配新的vcpu,目前kvm不支持smp guest。对于intel vt,主要就是初始化vmcs结构(vmx_vcpu_setup)。建议先看看intel vt的文档。
3. mmap kvm设备。qemu将bios image等拷贝到mmaped的内存。mmap的虚拟内存起始地址就是guest的物理地址0。
4. while (true) {
ioctl KVM_RUN 。
}用vt的术语说就是vm_entry,让cpu进入unpreviledged模式,此时cpu开始运行guest的代码。在运行过程中如果特定的情况发生(vmcs的vmexit control对这样的情况有设置),kvm run将退出(vt的术语叫vmexit),此时intel-kvm模块将读取vmcs的域决定退出的原因。具体kvm怎么处理vmexit可以看看kvm_handle_exit。有些vmexit可以由kvm模块自己处理比如page fault、cpu控制寄存器修改(比如修改cr0将cpu切换到保护模式)等,有些需要qemu来处理,比如设备io。如果需要qemu来处理,ioctl KVM_RUN将退到qemu进程当中,这个ioctl同时会返回一些退出信息比如是否io访问,io访问的端口号等给qemu。qemu根据这些信息进行指令模拟。如果kvm模块和qemu都不能处理某个vmexit,guest os会终止。这个循环一直执行直到guest关机。

qemu本身是一个应用程序,作为一个进程执行,和其他进程没有区别。在qemu运行时,cpu总是处于previledged ring3模式。只有当qemu调用ioctl KVM_RUN时cpu才会切换到unpriviledged模式,即guest运行的模式。

没有时间写详细的代码分析,下面是一些要点(主要是我看代码时碰到的比较困难的地方)
1.vmx设置了一些vmexit的条件,但奇怪的是invdpg指令不在其中。
2. intel vt realmode靠vm86模式。因此即使guest在实模式下系统也需要shadow pagetable。vm86没法实现实模式下4G内存访问。

MMU的处理有点复杂。
1. guest os有自己的page table,但这个page table使用的是guest的虚拟物理地址。cpu本身要使用真实的物理地址。kvm使用shadow pagetable来处理这个问题。guest看到的pagetable是其自身的pagetable,但cpu真正用来做虚拟地址到物理地址映射的是shadow pagetable。如果guest不在x64模式,shadow page table总是使用PAE模式而不管guest的pagetable的级别(在vm86模式下也是如此)。shadow page table总是使用4k的page。也就是说即使guest page table使用2M或4M的page,shadow page table都使用4K的page来模拟。
2. How to track guest pte dirty: 如果guest写某个page,此page对应的pte应该设置dirty。cpu的mmu只会设置shadow page table的pte dirty。为了让guest的pagetable有正确的dirty标志,kvm使shadow pte只读。产生page fault后使此pte可写,同时更新guest page table的dirty标志。
3. How to detect an address is memio: 前面说了qemu将做KVM_SET_MEMORY_REGION设置内存region。如果某个地址不在qemu设置的内存region当中,KVM即认为此内存地址是memio。
4. struct kvm_pte_chain -- guest的shadow page table数目是有限的。有时候需要释放掉一些shadow page table。对于所有使用的shadow page table,这个数据结构记录此page table的parent pte。其作用是当我们想释放掉一个shadow page table时可以找到所有指向它的pte,从而将这些pte设为空。如果只有一个pte指向此page table page,kvm有个优化,直接用此page的parent_pte域记录。否则,此page的parent_ptes链表记录所有的parent ptes。
5. struct kvm_rmap_desc -- 其目的是track guest page table的更改。对于guest中的任何page,此数据结构记录哪些shadow pte指向这个page。有可能有多个shadow pte指向这个page。如果此page被用作guest page table,这个page在指向它的shadow pte中被写保护。如果guest写此page,也就是达到更改guest page table时,一个page fault将产生。kvm将删除此page对应的shadow page table page(kvm有个优化,这里只说最简单的情况)。kvm然后模拟这个内存写,从而修改这个page的内容。如果guest要将此page作为page table访问内存,kvm将重建此page对应的shadow page table。这回答了为什么kvm不需要invlpg指令产生vm exit。

指令模拟,X86_emulate_ops
->*_std(read_std), 用来从guest memory中读取指令
->*_emulated(write_emulated). 从上面的kvm_rmap_desc我们知道不能直接读写guest page table。->write_emulated首先使此page对应的shadow page table为空,然后对guest page 进行写操作

 

 

动态迁移:http://www.ibm.com/developerworks/cn/linux/l-cn-mgrtvm2/index.html

管理超量使用的 KVM 主机资源:http://www.ibm.com/developerworks/cn/linux/l-overcommit-kvm-resources/index.html 

应用程序虚拟化的过去与未来:http://www.ibm.com/developerworks/cn/linux/l-virtual-machine-architectures/index.html

嵌入式系统的虚拟化:http://www.ibm.com/developerworks/cn/linux/l-virtual-machine-architectures/index.html