cpu虚拟化
基础名词
- 指令环
为限制应用程序直接调用系统资源,划分出4个环,内核使用ring0,驱动使用ring1-2,应用使用ring3。
2. 特权指令
cpu分为特权指令和普通指令,操作系统在ring0上执行特权指令完成对系统资源管控。
- 敏感指令
由于虚拟机内核运行于物理机之上,其特权指令运行于环3,当特权指令运行于环3会引起cpu异常(trap),vmm(vitual machine monitor)便通过捕获该异常进行代为向物理机申请内核执行,但对于一部分敏感指令(17条),Guest OS的执行权限不够,本该交给VMM来执行,然而它们在非ring0下可以依然照样执行,只是和ring0下执行的语义会有不同,或者直接静默失败了,由此vmm不能捕获这部分命令并执行。我们便成会引起cpu异常这部分指令为敏感指令。
如此来说,一个物理机是否可虚拟化,应保证其敏感指令应是特权指令的子集。
软件辅助完全虚拟化
软件模拟通过优先级压缩和二进制代码翻译技术完成虚拟化。
优先级压缩技术(Ring Compression):vmm运行于ring0,guest os运行于ring1。用户级别的指令直接送到cpu执行,特权指令需通过陷入模拟(Trap-and-emulation)引发trap让vmm代其执行,而对于那17条敏感指令则需要通过二进制翻译系统解决。
二进制代码翻译技术(Binary Translation):是位于应用程序和硬件之间的一个软件层。通过扫描虚拟机中的操作系统的二进制代码,将不可虚拟化的指令转化为可虚拟化指令,然后这些指令与VMM合作访问资源或者触发异常,让VMM进一步处理。
硬件辅助完全虚拟化
intel vt-x和amd-v两种硬件支持虚拟化解决技术。
以intel为例,Intel-V 在 ring0~ring3 的基础上,增加了VMX模式,VMX分为root和non-root。
VMM运行于VT-x这个特权级上,我们称之为Ring-1层。 然后操作系统可以依旧停留在Ring0,这样就能够让操作系统依旧停留在之前的本身应该存在的一个层级,从而使操作系统能够执行任何指令,而当操作系统执行一些触发异常或者不触发异常的特权指令的时候,CPU自动切换到该Ring-1模式下,从而使VMM能够捕获所有的异常和执行失败的信号,进而处理。
硬件辅助的完全虚拟化中,VMM不需要费尽心思去捕获特权指令了,CPU会捕获non-root mode的Ring0上执行的特权指令,然后VM-Exit到root mode的VMM进行处理,处理完后通过VM-Entry返回。从硬件层面解决了捕获的问题。
半虚拟化
完全虚拟化和半虚拟化区别在于对那些不能引起异常的敏感指令(17条)的处理方式。完全虚拟化是千方百计的想去捕捉那17条命令,而半虚拟化则需要修改guest os的内核,当执行那17条命令时转而直接调用VMM提供的特殊API(hypercall)来进行模拟,避免捕获。其实硬件辅助和软件辅助都时基于异常捕获处理,而半虚拟化避免了捕获的开销,这也使得它效率最高,但需要修改os内核不尽人意。
内存虚拟化
物理内存空间有效,对于每个进程在使用内存时不能覆盖到其他进程的内存。由此提出虚拟内存的概念,对物理内存抽象,相当于对物理内存进行虚拟化。这样每个进程都可以看到自己使用了一块连续且大小为整个主机的内存,采用页面将虚拟地址映射到物理地址,当进程申请内存时也不用担心和别的进程冲突,底层有机制处理这些冲突。
Guest虚拟内存地址(GVA)
|
Guest物理地址(GPA)
| Guest
------------------
| HV
Host虚拟地址(HVA)
|
Host物理地址(HPA)
guest os内存虚拟化要进行多级地址翻译,效率低下。且在虚拟地址到逻辑地址的页表缓冲(TLB:Translation Lookaside Buffer)命中也降低。
TLB
如果MMU每次地址转换都去片外内存上的页表上查找对应的页表条目,那么转换速度会大大降低,于是就有了TLB。
可以理解为MMU内部专用的存放页表的cache,保存着最近使用的页表条目,或者全部页表。当MMU接收到虚拟地址后首先在TLB中查找,如果找到该VA对应的转换条目则可以直接转换,当找不到时在去外部页表查找,并置换进TLB。由于TLB访问速度快,通过TLB缓存的页表可以为MMU的地址转换加速。
GVA -> GPA -> HVA -> HPA
软件模拟
由vmm来维护GPA->HVA的转换,然后再经过一次HVA->HPA,效率低下。
影子页表
影子页表由vmm维护,实际上就是一个Guest的页表到宿主机页表的映射。在初次GVA->HPA的转换时候,影子页表没有建立,此时Guest产生缺页中断,和传统的转换过程一样,经过两次转换(VA->PA),然后影子页表记录GVA->GPA->HVA->HPA。这样产生GVA->GPA的直接关系,保存到影子页表中,也提高tlb命中。
但当guest进程过多,每个进程需要维护一个页表,通过软件层面解决,效率并不是最好,所以itel和amd提供了硬件方式。
EPT/NPT
intel的是EPT(Extended Page Table),引入了EPT和EPT base pointer,EPT中存储着PA到MA的映射,而EPT base pointer负责指向EPT页表。当进行地址转换时,根据CR3找到VA-PA的页表进行VA到PA的转换,然后根据EPT base pointer找到EPT,进行PA到MA的转换。
amd的是NPT(Nested Page Table),此时Guest OS和Host都有自己的CR3。当进行地址转换时,根据gCR3找到VA-PA的页表进行VA到PA的转换,然后根据nCR3找到PA-MA的页表,进行PA到MA的转换。
这两种技术原理类似,在硬件层面上实现客户机虚拟地址到宿主机物理地址之间的转换。称为Virtualation MMU。当有了这种MMU虚拟化技术后,对于虚拟机进程来说还是同样把GVA通过内部MMU转换为GPA,并不需要改变什么,保留了完全虚拟化的好处。但是同时会自动把GVA通过Virtualation MMU技术转换为真正的物理地址(HPA)。很明显减少了由GPA到HPA的过程,提升虚拟机性能。而且,能够内存空间地址得标签化(类似于vlan tag),使得虚拟机切换后,tlb缓存不会失效。
IO虚拟化
全虚拟化
vmm必须从设备硬件的最底层开始模拟一个io设备以及驱动。
guest os应用访问io设备-->
guest kernel调用虚拟驱动-->
虚拟驱动将数据转发给虚拟网卡-->
虚拟网卡转发给Hypervisor模拟的该io设备进程-->
模拟的io进程将数据放入物理机io stack-->
等待物理机io设备将数据发出。
半虚拟化
引入前端IO驱动和后端IO驱动,guest获得前端驱动,host获得后端驱动,两者之间采取c/s方式建立连接,以事件和回调机制实现通信。此时guest就需要知道自己处于虚拟环境下,需要对guest内核进行修改。
guest os应用访问io设备直接数据转发给前端驱动(因为知道自己处于虚拟化环境)-->
Hypervisor后端驱动获得数据将数据放入物理机io stack-->
等待物理机io设备将数据发出。
透传技术
透传技术即当我们有多个物理设备时,不通过模拟直接分配给每个虚拟机使用,这种效率使用上几乎等于物理设备。
有一些I/O设备是具备DMA(Direct Memory Access)功能的。由于DMA是直接在设备和物理内存之间传输数据,必须使用实际的物理地址(也就是HPA),但DMA本身是为了减轻CPU的处理负担而存在的,其传输过程并不经过CPU。对于一个支持DMA传输的设备,当它拿着GPA去发起DMA操作时,由于没有真实的物理内存地址,传输势必会失败。
那如何实现对进行DMA传输的设备的GPA->HPA转换呢?再来一个类似于EPT/NPT的MMU?没错,这种专门转换I/O地址的MMU在x86的阵营里就是IOMMU。而像Intel的VT-d就是用来处理这些问题的,以及处理各主机中断。
IOMMU(Input/Output Memory Management Unit)是一个内存管理单元(Memory Management Unit),它的作用是连接DMA-capable I/O总线(Direct Memory Access-capable I/O Bus)和主存(main memory)。传统的内存管理单元会把CPU访问的虚拟地址转化成实际的物理地址。而IOMMU则是把设备(device)访问的虚拟地址转化成物理地址
IOMMU的一个重要用途是在虚拟化技术(virtualization):虚拟机上运行的操作系统(guest OS)通常不知道它所访问的host-physical内存地址。如果要进行DMA操作,就有可能破坏内存,因为实际的硬件(hardware)不知道guest-physical和host-physical内存地址之间的映射关系。IOMMU根据guest-physical和host-physical内存地址之间的转换表(translation table),re-mapping硬件访问的地址,就可以解决这个问题。