深入解析虚拟化(三)——XEN和类虚拟化

欢迎来到深入解析虚拟化的第三章。我们之前已经看过VMWare是如何使用二进制翻译实现完全虚拟化的。在本章中,我们会探讨另一种称为类虚拟化的虚拟化技术。一个利用类虚拟化技术的主要的供应商是XEN。

和VMWare二进制翻译VMM一样,我要指出,我们在本章中所讨论的是在引进虚拟化的硬件支持(VT-x和AMD-v))[2006]前所为虚拟x86架构特殊设计的。Xen当前销售的VMM和原始设计的明显不同。尽管如此,你将学到的知识会拓展你对虚拟化和底层概念的理解。

Xen哲学

Xen的第一个发布版可追溯到2003年。Xen人员注意到使用BT(VMWare的解决方案)的完全虚拟化具有很好的好处,它不需要更改客户操作系统代码就可运行虚拟机,因此完全虚拟化在兼容性和可移植性上有重要意义,然而,由于使用了影子页表,因此在性能具有负面的影响,并且VMM过于复杂。

 

对于第一个原因,Xen创造了一个新的x86 虚拟机监视器,它允许多个商用操作系统以安全和资源管理的方式共享传统硬件,但不牺牲性能或功能。这是通过一种称为 类虚拟化(paravirtualization) 的方法实现的。

 

类虚拟化的主要思想是通过对客户操作系统的小的改变来换取性能上和VMM简单性的大幅提升。尽管它需要修改客户操作系统,然而,值得关注的点是,它不需要改变应用程序二进制接口(application binary interface,ABI),因此客户机ring 3层的应用不需要修改。当你有如Linux或BSD这样操作系统的源代码时,类虚拟化是可行的,但是很难支持仅以二进制形式发布的闭源操作系统,例如Windows。在这篇论文中:Xen 和虚拟化的艺术(Xen and The Art Of Virtualization),它们提到有人正在努力移植Windows XP来支持类虚拟化,但我不知道它们是否实现了,如果你有任何想法,请告诉我。

 

用于本次课程的资料,你需要下载xen源代码。我们选择2号主版本,因为在该版本后,它们添加了硬件辅助虚拟化的支持。注意,在Xen术语中,我们保留单词 域(domain) 来指代正在运行了客户操作系统的虚拟机。Domain0 是启动时Xen管理程序启动的第一个域,会运行一个Linux 操作系统。该域是具有特权的:它可以访问硬件及管理其他域。这些其他域被称为 DomUs ,U代表用户(user)。它们是非特权的,能运行任何已经移植到Xen的操作系统。

保护VMM

为了保护VMM免受来自操作系统的错误行为(和来自另一个域),客户操作系统必须被修改来运行在更低的权限层次。所以和VMWare一样,客户内核被降权后占据ring 1 层,Xen 占据在ring 0 层,用户模式应用依然运行在ring 3 层。Xen 被映射到每个客户操作系统的顶部64M内存的地址空间中,以节省TLB刷新。虚拟机的段被VMM截断以确保它们不会与VMM自身重叠。用户模式应用使用被截断的段运行,并受到自身操作系统的限制,无法使用页保护 pet.us 来访问客户内核区域。

虚拟化CPU

在虚拟化x86时,我们要处理的问题是指令集(我们在第一章中讨论过),它是非经典虚拟化的。类虚拟化包括修改那些原先会陷入的敏感指令不再陷入。(译者注:个人理解:在如VMWare的完全虚拟化中,使用的方式为“陷入再模拟”,而在Xen中,使用的是HyperCall,将会在后文提到)。除了这点,由于所有的特权态必须由Xen处理,特权指令通过请求在Xen中验证和执行而被类虚拟化,任何客户操作系统尝试直接执行特权指令都会导致处理器没有反应,或者产生错误,这是因为只有Xen在足够的特权级执行。

 

所以,无论何时客户机需要执行一个特权操作(如安装一个新页表),客户机使用 hypercall 来跳转到Xen中;这和系统调用是类似的,只不过是发生在从ring 1 到 ring 0 。你可以把 hypercalls 看作是允许用户代码以可信代码的控制和管理的方式执行特权指令。

 

Hypercalls 在一个常规的操作系统中以类似系统调用的方式被调用;一个软中断被引发,中断的向量指向Xen中的入口点。在 32 位 的 x86 机器上,需要的指令是 int 0x82 , 在 64 位的 x86上是 syscall ;(真正的)IDT被设置,以至于这只会从ring 1 中所产生(中断)。要调用的特定 hpercall 包含在EXA中——在xen/include/public/xen.h 可以找到讲这些值与符号hypercall名称相映射的列表。

 

在版本 2 中,Xen支持23种hypercall。hypercall的向量编号被放在 eax 中,参数放在剩下的通用寄存器中。例如,如果客户机需要使一个页面无效,它会发出 HYPERVISOR_mmu_update hypercall,eax 会被置为 1 。HYPERVISOR_mmu_update() 接受一组(ptr,val)对。对于本例:

  • prt[1:0] 指定适当的 MMU_* 命令,在本例中是:MMU_EXTENDED_COMMAND 。
  • val[7:0] 指定适当的 MMUEXTENDEDCOMMAND 子命令:在本例中是:MMU_EXTENDED_COMMAND
  • ptr[:2] 指定要从TLB刷新的线性地址。

异常,包括内存错误和软件陷入,在x86上直接被虚拟化了。提供了一个 虚拟IDT(virutal IDT) ,一个域可以通过 HYPERVISORsettraptable hypercall 向 Xen提交陷入处理程序表。大多数陷阱处理程序和原生x86的处理程序是相同的,因为在Xen 的类虚拟化架构中,异常栈帧是没有被修改的,尽管页错误处理程序是有些不同的。
这是提交给管理程序的虚拟IDR的定义,由元组(中断向量,权限环,处理程序的CS:EIP)组成。

 

页错误处理程序不同的原因是处理程序会正常的从CR2读取导致错误的地址,这需要ring 0 权限;由于这是不可能的,Xen将它写入扩展栈帧。当在ring 0 外执行时发生了异常,Xen的处理程序在客户机操作系统栈上创建异常栈帧的副本,并将控制权返回给相应的已注册的处理程序。

 

通常,只有两种异常频繁的发生足以影响系统性能:系统调用(这通常是由软件异常引发)和页错误。Xen通过允许每个域注册一个快速异常处理程序来改善系统调用的性能,快速异常处理程序直接被处理器访问,而不用通过ring 0 间接寻址 ;在将此处理程序安装到硬件异常表中之前,将验证此处理程序。

 

位于 linux-2.6.9-xen-sparse/arch/xen/i386/kernel/entry.S 的文件包含了系统调用和故障低级处理程序。例如系统调用处理程序:

 

通过将中断映射到事件来虚拟化中断。事件是一种通过 __HYPERVISOR_set_callbacks hypercall提供的回调以异步方式从Xen到域通讯的方式。客户操作系统可以将这些事件映射到其标准中断调度机制。Xen负责决定会处理每个物理中断源的目标域。

虚拟化内存

我们之前看过一种是VMWare使用的 影子页表(shadow page tables) 的虚拟化内存的技术。当使用影子页表时,操作系统保留一组自己的页表,这与和硬件共享的页表不同。管理程序捕获页表更新并负责验证它们,将更改传递给硬件后返回。该技术会引发许多 管理器内部页错误(hypervisor-incuded page faults) (隐藏的页错误),因为它要确保影子页表和客户页表是同步的,并且由于 世界切换(world switchs) 或 VM 退出(VM Exits)期间消耗的周期,这种操作在性能方面很不划算。

 

在类虚拟化世界,解决方案是不同的。客户操作系统不允许为Xen和操作系统保留不同的页表,而是允许对实际页表进行只读访问。必须通过hypervisor(通过hypercall)更新页表,而不是直接写内存,以防止客户操作系统做了不可接受的更改。也就是说,每次客户操作系统请求一个新的页表,可能由于正在创建新进程,它会从自身的保留的内存中分配并初始化页,并将其注册到 Xen 。此时,操作系统必须放弃直接写页表内存的权限:所有后续的更新必须由Xen验证。客户操作系统可以批量请求更新以缓解进入管理程序的开销。

虚拟化设备

显然,虚拟机本身不能信任处理设备,否则,例如每个客户操作系统可以认为它们拥有整块硬盘分区,并且可能存在比实际磁盘分区更多的虚拟机。为了防止这种行为,管理程序需要干预所有的设备访问来阻止任何恶意活动。有许多方法来虚拟化设备,在最高级别,虚拟化设备的选择和虚拟化CPU的选择并行。我们可以使用 完全虚拟化/模拟(full virtualization/emulation)或使用 类虚拟化 。

 

在 完全虚拟化/模拟 中,非特权客户机有一种错觉,即它正在和一个与底层物理设备相同的专用设备进行交互。这通常使用一个旧的、支持好的硬件设备和在软件中模拟来达到。这样做的优势是客户机不需要任何特殊的驱动,因为你可以认为任何操作系统已经支持这些旧的设备。坏处是它很难安全和正确的实现这样的模拟。统计数据已经证明了许多漏洞存在与设备模拟中(如Qemu),更重要的是它很慢,并且可能不支持设备的高级功能。如今,因为性能和易用性,设备大多被类虚拟化。尽管如此,仍然存在一些场景(恶意软件沙箱),可以发现 硬件辅助虚拟化(HVM)和 Xen 或 KVM 中的设备模拟(Qemu)一起使用,来避免在VM中运行代码:在客户虚拟机中运行的驱动程序越少,会留下越少的痕迹 :)

 

在 类虚拟化 中,理念是提供简单的设备接口给每个客户机。在这种情况下,客户机会意识到设备已经被修改来使它更容易虚拟化,并需要遵守新的接口(协议)。不出所料,Xen设备虚拟化的主要模型也是类虚拟化。

 

Xen公开了一组干净简单的设备抽象。特权域(Domain0或者特权级驱动程序域)管理实际设备,然后给所有的客户机导出一类通用设备,这类设备隐藏特定物理设备的所有细节或复杂性。例如,Xen提供一个抽象块设备而不是 SCSI 设备或 IDE 设备。它只支持两种操作:读写块。这以与POSIX 中 readv 和 writev 调用 的实现是紧密对应的,允许将操作分组到单个请求中(这允许Domain 0 内核中的I/O重新排序或有效的地使用控制器)。

 

非特权客户机运行称为 前端驱动程序(frontend driver)的简化驱动程序,而直接访问设备的特权域运行称为 后端驱动程序(backend driver)的驱动程序,该驱动程序了解特定物理设备的底层细节。这种分工对新兴的操作系统特别有用。对于一个操作系统,要进入的最大的障碍之一是需要支持大多数通用设备的驱动程序并快速实现对新设备的支持。该类虚拟化模型允许客户操作系统只为每种通用类型的设备实现一种设备驱动程序,随后依赖于特权域中的操作系统来获得实际物理设备的设备驱动程序。这使操作系统开发更容易,并且使操作系统在更广泛的硬件上更容易使用。Xen所使用的架构称为 分离驱动程序模型(split driver model)

 

图片描述

 

后端驱动程序为每个前端驱动程序提供了拥有自己的通用设备副本的错觉。事实上,它可以在多个客户域同时复用设备。它负责保护域之间数据的私有性和安全性,以及执行公平访问和性能隔离。常见的后端/前端对包括网卡的 网络后端/网络前端(netback/netfront) 驱动程序和块设备的 blkback/blkfront 驱动程序,例如磁盘。

 

现在有了一个有趣的问题,如何在前端驱动程序和后端驱动程序间共享数据?大多数主流的虚拟机管理程序以建立在 环缓冲区(ring buffers) 的 共享内存(shared memory) 来实现此通讯。这提供了通过PV驱动程序垂直传递缓冲区信息的高性能通讯机制的优势,因为你不需要在内存中移动缓冲区以及做额外的拷贝,并且它也很容易实现。所有的管理程序都使用这种模型,但是命名方式不同,例如在Hyper-V中,后端被称为 虚拟化服务提供者(Virtualization Service Provider) ,前端被称为 虚拟化服务客户端(Virtualization Service Client)。KVM 使用 virtio 机制。

 

图片描述

 

环缓冲区(ring buffer)是一种简单的数据结构,由预分配的内存区域组成,每个区域以一个描述符标记。当向环的一个方向写入时,从另一方向读取它,每一个方向都(在操作中)更新描述符。如果写方达到了“已写过(written)”块,环就是满了的,需要等待读方标记一些块为空。

 

为了让你快速了解它们是如何被使用的,你可以简要了解虚拟块设备是如何使用它们的。这个设备的接口定义在 xen/include/public/io/blkif.h 头文件中。块接口定义了 blkif_request_t 和 blkif_response_t 结构体分别用来请求和响应。共享内存环结构体以下面的方式进行了扩展:

 

![图片描述]

 

Xen的最后一个选项是能够将物理设备直接授权给非特权域。这可以看作是根本没有虚拟化。然而,如果不支持虚拟化特定设备或者如果需要尽可能高的性能,授权非特权客户机直接访问设备或许是唯一的选择。当然,这意味着其他域不能访问设备,并会导致和完全虚拟化一样的可移植性问题。我们会在之后的章节中再谈到这一点。

 

在本节中,你已经学到了Xen是如何利用类虚拟化来虚拟化CPU和内存的,除了这些,我们对设备虚拟化稍有了解。请记住,对于CPU和内存,到现在为止你所学到的技术,包括类虚拟化和二进制翻译如今已经不被使用了,它们被硬件辅助虚拟化所替代,我们讲在下一章节中讨论。最后,我们已经完成了遗留的东西,我们将转向更有趣的事情:D 我希望你从中学到了一些东西。最后,我要感谢所有参考部分白皮书背后的作者所做的出色工作。

 

参考:

原文链接:Virtualization Internals Part 3 - Xen and Paravirtualization

转自网络:https://bbs.pediy.com/thread-246829.htm

原文地址:https://www.cnblogs.com/pipci/p/12347621.html