KVM 源码解读

550阅读 0评论2013-11-24 droplist
分类:LINUX

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 进行写操作。

上一篇:qemu-kvm 中断虚拟化
下一篇:kvm磁盘优化