Xen和虚拟化技术(二)
2005-08-02 02:13:54 来源:WEB开发网核心提示:2. XEN:方法和概述在传统的VMM中,虚拟硬件的功能是与底层机器上的真实硬件完全相同的,Xen和虚拟化技术(二),这种“完全虚拟化”(full virtualization)的方法最显而易见的好处在于操作系统可以不经任何修改就直接在虚拟硬件上运行,但是它也有很多缺点,在数据包和数据流两个粒度上监视每个domain的
2. XEN:方法和概述
在传统的VMM中,虚拟硬件的功能是与底层机器上的真实硬件完全相同的。这种“完全虚拟化”(full virtualization)的方法最显而易见的好处在于操作系统可以不经任何修改就直接在虚拟硬件上运行,但是它也有很多缺点。特别是针对那些当前被广泛应用的IA32(或者称作x86)架构,这种方法带来的缺陷更是不容忽视。
x86架构的设计从来就不支持完全的虚拟化。如果要正确实现x86架构虚拟化,VMM就必须能够对某几条特定的“超级指令(supervisor instruction)”进行操作。但是,如果在没有足够特权的情况下执行这些超级指令会导致“沉默的失败(//fail silently:如果特权级不够,那么会直接导致执行失败,不会产生其它响应)”,而并非产生一个便于我们使用的陷阱(trap)。
另外,将x86架构中的MMU进行有效的虚拟化也是一件很困难的事情。这些问题是可以被解决的,但是在解决的同时必须要付出操作复杂度增加和系统性能降低的代价。VMware ESX Server[10]需要动态地重写那些被VMM操控的机器码部分,在其中有可能需要VMM干涉的地方插入陷阱操作(//在什么地方插入陷阱操作,是在程序运行起来后才知道的,所以需要动态地重写相关代码)。因为务必要对所有那些不能够引起陷阱的特权指令进行捕捉和操作,所以这种转换(//动态重写代码)要被应用于整个guest OS的内核(导致了相关的转换,执行和缓存等开销)。ESX Server实现中采用的技术是建立系统结构(system structure)(比如页表)的影子版本,通过为每一次“更新”操作设立陷阱来解决虚拟页表和物理页表的一致性问题(//具体细节还是要看ESX Server的说明)。但是在处理“更新密集”型的操作(如创建新的应用进程)的时候,该方法会带来高昂的开销。
除了x86架构非常复杂的原因,还有一些其它方面的争论反对“完全虚拟化”。其中值得一提的是,被操控的操作系统在一些情况下需要接触到真实的资源。例如,提供真实时间和虚拟时间以允许guest OS能够更好地支持“时间敏感”型的任务,还可以正确地操作TCP超时和RTT估算;给出真实的机器地址以允许guest OS能够利用超级页(superpage)或者页染色(page coloring)等方法改进性能。
我们提出的虚拟机抽象能够避免完全虚拟化带来的种种缺陷。这种虚拟机抽象和底层硬件相似却并不完全相同,因此被称之为“准虚拟化”(//paravirtualization:或者翻译为半虚拟化?后面译文沿用准虚拟化)方法。这种方法虽然需要对guest OS进行一些改动,但是它能够改善性能。还有特别重要的一点需要说明:准虚拟化方法不会对应用二进制接口(ABI)进行修改,因此用户也就不用修改那些在guest OS上执行的应用程序。
我们进行的关于准虚拟化方法的讨论要遵循以下一些规则:
1.最基本的是要支持那些不经改动的应用二进制文件的执行,即用户不用对应用程序做针对Xen的转换。因此我们必须虚拟化现有的标准ABI所需的全部体系结构特征。
2.很重要的一点是要支持完整的多应用操作系统。这就需要将在单个guest OS实例中的复杂的服务器配置虚拟化(//例如,如果guest OS上配置了ftp服务,那么虚拟硬件就要打开相应端口)。
3.准虚拟化务必要有很高的性能。另外针对那些不协作(//uncooperative:这里的不协作是指硬件架构不支持共享,所以才需要资源隔离)的机器架构,如x86架构,准虚拟化还需要能够提供很强的资源隔离能力。
4.在协作(cooperative)的机器架构上,准虚拟化方法要能够完全地隐藏资源虚拟化带来的影响,减少guest OS在正确性和性能上面临的风险。
请注意,我们在这里提出的准虚拟化的x86抽象的方法是与最近在Denali项目中提出的方法有很大差异的。Denali是为了支持数以千计的运行着网络服务的虚拟机而设计的。这些网络服务中绝大部分是小规模的,不流行(//应用的不流行也就说明了运行该应用的环境比较少,所以只要针对这些相应的特定环境作专门的虚拟化即可)的应用。与之相反的是,Xen的设计最终要支持近100个运行着业界标准应用和服务的虚拟机。由于设计目标的极大差异,我们不妨将Denali的设计选择和我们自己的设计规则做一个有益的讨论。
首先,Denali不需要关注现有的ABI,因此他们的VM接口忽略掉了相关的架构特征。例如,Denali并不完全支持x86的分段机制,但是这一点却是在NetBSD,Linux和Windows XP等操作系统的ABI中都有提出并且被广泛使用。例如,线程库中经常会使用分段机制来寻址线程局部数据。
其次,Denali的实现没有解决在单个guest OS中支持多个应用(application multiplexing)的问题,也没有解决多地址空间的问题。应用被显式地链接到Ilwaco guest OS实例上,这么做在某种意义上类似于之前在Exokernel中的libOS[23]。因此每个虚拟机只能操控一个单用户单应用的没有保护措施的所谓的“操作系统”。在Xen中,与之相反,每个虚拟机上能够操控一个真正的操作系统。这个操作系统上能够安全地执行数以千计个不经改动的用户级进程。虽然Denali号称开发了一个虚拟MMU原型能够对其在该领域有所帮助,但是我们没有看到公开的技术细节和评估报告。
再次,在Denali体系结构中,是由VMM执行全部的内存与磁盘间的页面调度的。这可能是与虚拟层缺乏存储管理支持有关。由VMM完成页面调度是与我们的性能隔离目标相违背的:那些“有恶意”的虚拟机可能会故意产生抖动行为,导致其它虚拟机的CPU时间和磁盘带宽被不公平地剥夺(//VMM监控很多VM,各个VM上再跑操作系统,所以如果很多事情都放在VMM中做必然会影响到各个VM;所以要把一些事情放在上面的操作系统做来达到隔离性)。在Xen中,我们希望每个guest OS在其自己分配到的内存空间和磁盘区域内执行它自己的页面调度(此前已经有self-paging的方法被提出)。
最后,Denali为机器的全部资源虚拟了“名字空间”。这样的话,如果一个VM不能够“叫出”另一个VM下辖的资源的名字,那么该VM就不能够访问这些资源(例如,Denali中的VM并不知道硬件地址,它们只看得到Denali创建的虚拟地址)。与此相对,我们认为hypervisor中的安全访问控制已经足以确保安全性;此外,就像之前讨论过的,当前在guest OS是否应该能够直接看到物理资源这一点上存在着很热烈的关于正确性和性能的争论。
在后续的章节里,我们将描述Xen提出的虚拟机抽象,然后将讨论如何将一个guest OS作必要的改动以适应Xen。在这篇文章里我们定义了一些术语要提醒大家注意。例如,术语guest OS是指Xen能够操控的操作系统之一;术语domain是指一个运行中的虚拟机,在其上有一个guest OS在执行。program和process之间的区别和传统系统中的区别类似。我们称Xen本身为hypervisor,因为它运行的特权级要比它所操控的guest OS中的supervisor code运行的特权级更高。
2.1 虚拟机接口
一个准虚拟化的x86接口主要包括了系统中的三个大的方面:存储管理,CPU和设备I/O。在下面,我们将依次介绍各个机器子系统的情况,并讨论在我们的准虚拟化架构中是如何体现的。虽然在我们的实现中,有相当一部分,如存储管理,是专门针对x86的,但是实际上还有很多方面(比如我们虚拟的CPU和I/O设备)都是可以很容易地应用于其它机器架构上的。更进一步地说,在与RISC架构在实现上有差异的很多地方,x86往往表现出的是该方面最坏情况时的情形。例如,对硬件页表进行有效的虚拟化就比虚拟化一个软件管理的TLB困难很多。
存储
管理分段不能够使用具有完全特权级的段描述符,不能够与线性地址空间的最顶部交迭(//因为最顶部是Xen)。
分页guest
OS直接对硬件页表做读访问,但是更新(//就是写)是分批进行的而且要经过hypervisor确认。一个domain可以被分配在不连续的页面上。
CPU保护guest OS必须运行在低于Xen的特权级上。
异常guest OS必须将异常句柄的描述符表在Xen中记录。除了页面错误外,其它句柄和真实的x86架构相同。
系统调用guest OS为系统调用提供一个“快速”的句柄。允许应用直接调用它所在的guest OS,而不必间接地通过Xen完成每次调用。
中断硬件中断被一个轻量级的事件系统替换。
时间每个guest OS具有一个定时器接口,可以得到“真实的”和“虚拟的”时间。
设备I/O网络,磁盘,……虚拟设备访问起来很简单。数据传递使用的是异步I/O环。由一个事件机制替换硬件中断来发布通告。
2.1.1存储管理
虚拟化存储毫无疑问是准虚拟化一个体系结构中最困难的部分,它包括hypervisor所需的机制和移植各个guest
OS所需的改动。如果在架构中提供了由软件管理的TLB的话,那么这个任务会变得轻松些,它们可以以比较简单的方式被有效地虚拟化[13]。带标记的TLB是另外一个在大部分RISC架构(这些RISC架构主要用于构建服务器,比如Alpha,MIPS和SPARC)中支持的有用特征。其中,每个TLB项都有和地址空间标识符相关的标记,这使得hypervisor和各个guest OS能够有效地在被隔离开的地址空间内共存。这时在执行转移(//transferring execution:在进程执行间切换的时候,执行的指令序列从一个进程转移到另一个进程,称为执行转移)的时候,是不需要刷新(flush)整个TLB(//只对具有和自己的地址空间标识符相吻合的TLB项进行操作)。
不幸的是,x86架构并没有由软件管理的TLB;取而代之的是在发生TLB失效的时候,处理器会自动通过遍历硬件页表结构来处理。因此为了获得最好的可能达到的性能,当前地址空间内所有的有效页传输)都要在硬件可访问的页表中给出(//最好情况理应如此,但实际如何做得到呢?)。此外,因为TLB是没有标记的,所以地址空间的切换需要整个TLB的刷新。在这些限制下,我们作出了两个决定:(i)由guest OS负责分配和管理硬件页表,这么做最小化了Xen对页表操作的影响,确保了安全性和隔离性;(ii)Xen处于每个地址空间的最顶部的64MB空间内,因此避免了在进入和离开hypervisor时进行TLB刷新操作(//这个要看源代码才能最后搞清楚)。
每当guest OS需要一个新的页表,例如创建了一个新进程,它就在自己保留的内存空间内分配和初始化一个页面,并且将其在Xen中记录。此时操作系统必须放弃对页表存储空间直接写的权限:所有后续的更新操作都必须由Xen进行确认。这就在很多方面限制了更新操作,包括只允许操作系统它自己所属的页进行映射操作,不允许对页表进行可写的映射操作。guest OS可以成批地进行更新操作以减少每次更新都要进入hypervisor带来的代价(//因为每次更新都要hypervisor确认)。每个地址空间顶部的64MB区域是保留给Xen的,是不能够被guest OS访问或者重新进行映射的。因为任何通常的x86架构中的ABI都不会使用到这个区域中的地址,所以这个约束不会破坏到应用程序的兼容性。
分段机制也是以类似的方式,通过对硬件段描述符表的更新确认来进行虚拟化(//这样做就达到虚拟化的目的了么?哦,应该是Xen在确认后接着由Xen执行,嗯)。对于x86架构上段描述符的限制只有:(i)它们的特权级别必须比Xen要低;(ii)它们不能够对地址空间中Xen的保留部分进行访问。
2.1.2CPU
虚拟化CPU对guest OS提出了几个要求。因为hypervisor插在操作系统的下层违背了惯常的关于操作系统在整个系统中特权最高的假设。为了保护hypervisor不会受到操作系统不正确行为的影响(即domain不受另一个domain的影响),guest OS就必须被改造为能够运行在较低的特权级上。
很多处理器体系结构只是提供了两个特权级。在这些情况下,guest OS和应用程序共享较低的特权级。同时,guest OS运行在单独的地址空间中以保护自己不会受到应用程序执行的影响。guest OS通过hypervisor设定虚拟的特权级和改变当前的地址空间来间接地和应用之间进行控制传递。另外,如果处理器的TLB支持地址空间标记,那么也就可以避免TLB刷新带来的高昂代价。
在x86架构上有效地实现特权级的虚拟化是可能的,因为x86架构在硬件上支持四个不同的特权级。x86架构的特权级往往用圈(ring)来表示,从ring 0(最高特权)到ring 3(最低特权)。操作系统的代码运行在ring 0这个特权级上,因为再没有其它的ring能够执行那些特权指令。ring 3通常用于执行应用代码。就我们所知,自OS/2起到现在的各个知名的x86 架构上的操作系统都还没有利用ring 1和ring 2这两个特权级的。那么,任何遵循这个通常的安排的操作系统(//没有利用ring 1和ring 2)就都可以移植到Xen上来。
这个移植过程只需要做一些改动使操作系统改为运行在ring 1特权级上。这就防止了guest OS会直接执行特权指令,也保证了操作系统与运行在ring 3上的应用程序之间相隔离的安全性。
特权指令需要被Xen确认和执行以达到准虚拟化的目的,这主要应用于诸如安置新的页表,或者在处理器idle时放弃之(而不是去hlt它)等操作。因为只有Xen有足够高的特权级来执行这些指令,所以任何guest OS试图直接运行特权指令都会失败,后果要么是“沉默”要么是产生错误。
异常,包括内存错误和软件陷阱,都可以在x86架构的基础上直接进行虚拟化。有一个表,内容为对每类异常进行描述的句柄。表中所列的异常都是在Xen中有记录的,以用作确认。表中给出的句柄都是与真正的x86硬件中相同的;之所以这一点是可能做到的,主要是因为在我们的准虚拟化架构中,异常堆栈框架是没有被修改的。唯一的一个改动是在页面错误句柄上。因为该句柄的操作需要从特权处理器寄存器(CR2)中读出出错的地址;但是这是不可能的(//因为特权级别不够了),我们就将它(//页面错误句柄?CR2的值?)写入扩展的堆栈框架中(后来发现,在移植XP的时候,将这个值写入一个预先商定的共享存储位置上要比修改堆栈框架简单一些)。当系统在ring 0以外执行时有异常发生,Xen的句柄就会在guest OS堆栈中创建一个异常堆栈框架的拷贝,并且会将控制交给相应的已经记录过的异常句柄。
典型的,只有两类异常会经常发生而影响到系统的性能:系统调用(一般都是通过软件异常实现)和页面错误。我们让每个guest OS都记录一个“快速的”异常操作句柄来改进系统调用的性能。这个异常操作句柄可以直接由处理器使用,而不必非要间接地经过ring 0;这个句柄在放置进硬件异常列表中之前就是经过确认的(//所以不必经过Xen)。不幸的是,我们不可能使用同样的技术来处理页面错误句柄,因为只有那些运行在ring 0的代码才能够从寄存器CR2中读出错误的地址;因此,页面错误必须要经过Xen才能提交,Xen保存该寄存器的值供来自ring 1的访问使用。
当Xen发现异常产生时,它会对异常句柄进行确认以确保安全性。这只需要检查句柄的代码段中是否含有指明要在ring 0中执行的操作。既然没有guest OS能够创建这样一个段,那么只需要将专门的段选择符和少量的保留在Xen中的静态值作比较即可。除了这点以外,任何其它的句柄问题都会在异常传播(exception propagation)(//一个异常导致了另一个异常的产生)的过程中被修正。例如,如果句柄缺少相应代码段或者句柄没有分配到内存页,那么在Xen为将控制返回给句柄而执行iret指令的时候就会有一个相应的错误产生。Xen通过检查出错的程序计数器值来检测这些“双错误(//double faults:之前已经出错了,现在到了iret已经是第二个错误了;第二个错误是由第一个错误传播而来)”:如果地址是处于异常虚拟化的代码中(//说明异常处理没有完成,iret没成功),那么guest OS就要被终止。
对于直接的系统调用句柄来说,这种“懒惰(//第一个错误发生的时候,没有被检查到;直到Xen执行了iret之后才报错)”的检查也是安全的:当CPU试图直接跳至guest OS句柄的时候,会发生访问错误(//之前的过程都一样,只是直接的系统调用是不经过Xen的)。在这种情况下,产生错误的地址将处于Xen之外(因为Xen不会去执行guest OS系统调用),因此错误就以上文讲过的一般方式进行虚拟化即可。如果由于错误的传播导致了进一步的“双错误”,那么guest OS会像上文谈及的一样被终止。
2.1.3设备I/O
在完全虚拟化环境下需要仿真现有的硬件设备,而Xen不同于此。Xen给出了一套清楚、简单的设备抽象。这就使得我们能够设计一个接口以有效地满足我们对保护性和隔离性的需求。为了做到这一点,I/O和各个domain之间的数据传递都是要经过Xen的,可以使用的方法有共享内存,异步缓冲区描述符环等。这些方法能够在Xen有效地执行确认检查(例如,检查缓冲区是否包括在了domain的存储空间内)的同时,为在系统中的竖直方向上传递缓冲区信息提供了一个高性能的通信机制。和硬件中断类似,Xen支持一个轻量级的事件递交机制用于为一个domain传送异步通告(notification)。这些通告是在对未决事件类型的位图进行更新的时候产生的,也可以通过调用一个guest OS专有的事件句柄产生。这些调用的返回可以由guest OS来决定是否进行“拖延”处理。例如,这么做(//拖延处理)可以避免在频繁唤醒通告时带来的额外开销。
2.2移植OS到Xen的代价
当前我们的NetBSD移植还处于非常初级的阶段,因此我们就没有将其结果在这里报告。虽然XP的移植要更进一步,但也还处于移植过程中;当前移植的XP能够执行RAM上的用户空间的应用,但是缺乏虚拟的I/O驱动。基于这个原因,表中就没有给出和XP的虚拟设备驱动相关的数据。无论怎样,和Linux一样,我们可以想见这些驱动应该是小的和简单的,因为这要得益于Xen提供的理想的硬件抽象。
衡量代价的标准是与原先的x86代码相比修改或增加的那些必要的注释以及遵从一定格式的代码的行数(不包括设备驱动)。对Windows XP中体系结构无关(architecture independent)的代码所作的改动达到了一个惊人的数字,这是因为Windows XP使用了多种多样的结构和联合来访问页表项(PTE)。每次对页表的访问都不得不被单独地进行修改(//因为每次访问都可能用到不同的结构),当然这个过程是可以采用一些脚本来自动完成的。与此相反的,Linux需要的改动就少了很多,这是因为Linux的存储系统是使用预处理程序中的宏来访问PTE的— 这些宏定义为增加准虚拟化所需的转换和hypervisor调用提供了便利的位置(//就在这些位置上加即可)。
在这两个操作系统中,体系结构特有(architecture specific)的部分用于将x86代码向我们的准虚拟架构的移植。这包括重写那些使用了特权指令的程序,删除大量的低层的系统初始化代码。另外,Window XP需要有更多的改变,这主要是因为之前遗留下来的16位仿真代码的存在以及需要一个略有不同引导加载(boot-loading)机制。注意,XP中的x86特有的代码要比Linux多很多,因此可以预见到在做移植的时候也就需要做更多的工作。
2.3控制和管理
贯穿于整个Xen的设计与实现过程中,有一个目标就是尽可能地将策略从机制当中剥离出来。虽然hypervisor必须要被包含在数据通路(data-path aspects)上。例如,在domain之间调度CPU,在发送之前过滤网络数据包,或者在读数据块的时候进行执行访问控制(//必然的,因为Xen位于guest OS和底层硬件之间,guest OS又是彼此隔离的,所以数据传递是一定都要经由Xen的)。但是在更高层次的问题上,例如CPU如何被共享或者各个domain能够发送哪种数据包,这时就不需要将Xen包括在内了,甚至都不用考虑它(//hypervisor是实现机制,而如何共享CPU和如何进行任务分工都是策略问题)。
最终得到的架构是hypervisor只是提供那些最基本的控制操作。这些操作经由一个可访问的接口从经过授权的domain传来;而那些复杂的策略决策,比如许可控制(//不知道这个许可控制是否和第1部分里提到的是一回事儿),都最好由运行在guest OS上的管理软件执行,而并非在有特权的hypervisor代码中(//Xen只是提供机制,不负责策略)。
整个系统架构中有一个domain是在引导(boot)时创建的。这个domain被允许使用控制接口。这个初始的domain,术语称为Domain 0,它负责操控应用级的管理软件。控制接口具有创建和终止其它domain的能力,还能控制它们相关的调度参数、物理存储分配以及它们对给定的物理磁盘和网络设备的访问。
除了处理器和存储资源,控制接口还支持虚拟网络接口(VIF)和块设备(VBD:虚拟块设备)的创建和删除。这些虚拟I/O设备具有一些和访问控制相关的信息。这些信息决定了哪个domain能够访问它们,以及访问时有哪些约束(例如,一个只读的VBD可以被创建,一个VIF可以过滤IP包以防止源地址欺骗)。
这个控制接口,结合对系统当前状态进行的剖析统计,其结果能够被输出到一套运行在Domain 0上的应用级管理软件上。该管理软件作为管理工具的补充,能够对整个服务器进行方便地管理:例如,能够创建和破坏domain,设定网络过滤器和路由规则,在数据包和数据流两个粒度上监视每个domain的网络活动,创建和删除虚拟网络接口和虚拟块设备。我们期待开发出高级的工具来进一步将管理策略的应用程序自动化(//这里的管理策略和前面讲的“机制与策略分开”中的策略不是一回事儿吧?)。
在传统的VMM中,虚拟硬件的功能是与底层机器上的真实硬件完全相同的。这种“完全虚拟化”(full virtualization)的方法最显而易见的好处在于操作系统可以不经任何修改就直接在虚拟硬件上运行,但是它也有很多缺点。特别是针对那些当前被广泛应用的IA32(或者称作x86)架构,这种方法带来的缺陷更是不容忽视。
x86架构的设计从来就不支持完全的虚拟化。如果要正确实现x86架构虚拟化,VMM就必须能够对某几条特定的“超级指令(supervisor instruction)”进行操作。但是,如果在没有足够特权的情况下执行这些超级指令会导致“沉默的失败(//fail silently:如果特权级不够,那么会直接导致执行失败,不会产生其它响应)”,而并非产生一个便于我们使用的陷阱(trap)。
另外,将x86架构中的MMU进行有效的虚拟化也是一件很困难的事情。这些问题是可以被解决的,但是在解决的同时必须要付出操作复杂度增加和系统性能降低的代价。VMware ESX Server[10]需要动态地重写那些被VMM操控的机器码部分,在其中有可能需要VMM干涉的地方插入陷阱操作(//在什么地方插入陷阱操作,是在程序运行起来后才知道的,所以需要动态地重写相关代码)。因为务必要对所有那些不能够引起陷阱的特权指令进行捕捉和操作,所以这种转换(//动态重写代码)要被应用于整个guest OS的内核(导致了相关的转换,执行和缓存等开销)。ESX Server实现中采用的技术是建立系统结构(system structure)(比如页表)的影子版本,通过为每一次“更新”操作设立陷阱来解决虚拟页表和物理页表的一致性问题(//具体细节还是要看ESX Server的说明)。但是在处理“更新密集”型的操作(如创建新的应用进程)的时候,该方法会带来高昂的开销。
除了x86架构非常复杂的原因,还有一些其它方面的争论反对“完全虚拟化”。其中值得一提的是,被操控的操作系统在一些情况下需要接触到真实的资源。例如,提供真实时间和虚拟时间以允许guest OS能够更好地支持“时间敏感”型的任务,还可以正确地操作TCP超时和RTT估算;给出真实的机器地址以允许guest OS能够利用超级页(superpage)或者页染色(page coloring)等方法改进性能。
我们提出的虚拟机抽象能够避免完全虚拟化带来的种种缺陷。这种虚拟机抽象和底层硬件相似却并不完全相同,因此被称之为“准虚拟化”(//paravirtualization:或者翻译为半虚拟化?后面译文沿用准虚拟化)方法。这种方法虽然需要对guest OS进行一些改动,但是它能够改善性能。还有特别重要的一点需要说明:准虚拟化方法不会对应用二进制接口(ABI)进行修改,因此用户也就不用修改那些在guest OS上执行的应用程序。
我们进行的关于准虚拟化方法的讨论要遵循以下一些规则:
1.最基本的是要支持那些不经改动的应用二进制文件的执行,即用户不用对应用程序做针对Xen的转换。因此我们必须虚拟化现有的标准ABI所需的全部体系结构特征。
2.很重要的一点是要支持完整的多应用操作系统。这就需要将在单个guest OS实例中的复杂的服务器配置虚拟化(//例如,如果guest OS上配置了ftp服务,那么虚拟硬件就要打开相应端口)。
3.准虚拟化务必要有很高的性能。另外针对那些不协作(//uncooperative:这里的不协作是指硬件架构不支持共享,所以才需要资源隔离)的机器架构,如x86架构,准虚拟化还需要能够提供很强的资源隔离能力。
4.在协作(cooperative)的机器架构上,准虚拟化方法要能够完全地隐藏资源虚拟化带来的影响,减少guest OS在正确性和性能上面临的风险。
请注意,我们在这里提出的准虚拟化的x86抽象的方法是与最近在Denali项目中提出的方法有很大差异的。Denali是为了支持数以千计的运行着网络服务的虚拟机而设计的。这些网络服务中绝大部分是小规模的,不流行(//应用的不流行也就说明了运行该应用的环境比较少,所以只要针对这些相应的特定环境作专门的虚拟化即可)的应用。与之相反的是,Xen的设计最终要支持近100个运行着业界标准应用和服务的虚拟机。由于设计目标的极大差异,我们不妨将Denali的设计选择和我们自己的设计规则做一个有益的讨论。
首先,Denali不需要关注现有的ABI,因此他们的VM接口忽略掉了相关的架构特征。例如,Denali并不完全支持x86的分段机制,但是这一点却是在NetBSD,Linux和Windows XP等操作系统的ABI中都有提出并且被广泛使用。例如,线程库中经常会使用分段机制来寻址线程局部数据。
其次,Denali的实现没有解决在单个guest OS中支持多个应用(application multiplexing)的问题,也没有解决多地址空间的问题。应用被显式地链接到Ilwaco guest OS实例上,这么做在某种意义上类似于之前在Exokernel中的libOS[23]。因此每个虚拟机只能操控一个单用户单应用的没有保护措施的所谓的“操作系统”。在Xen中,与之相反,每个虚拟机上能够操控一个真正的操作系统。这个操作系统上能够安全地执行数以千计个不经改动的用户级进程。虽然Denali号称开发了一个虚拟MMU原型能够对其在该领域有所帮助,但是我们没有看到公开的技术细节和评估报告。
再次,在Denali体系结构中,是由VMM执行全部的内存与磁盘间的页面调度的。这可能是与虚拟层缺乏存储管理支持有关。由VMM完成页面调度是与我们的性能隔离目标相违背的:那些“有恶意”的虚拟机可能会故意产生抖动行为,导致其它虚拟机的CPU时间和磁盘带宽被不公平地剥夺(//VMM监控很多VM,各个VM上再跑操作系统,所以如果很多事情都放在VMM中做必然会影响到各个VM;所以要把一些事情放在上面的操作系统做来达到隔离性)。在Xen中,我们希望每个guest OS在其自己分配到的内存空间和磁盘区域内执行它自己的页面调度(此前已经有self-paging的方法被提出)。
最后,Denali为机器的全部资源虚拟了“名字空间”。这样的话,如果一个VM不能够“叫出”另一个VM下辖的资源的名字,那么该VM就不能够访问这些资源(例如,Denali中的VM并不知道硬件地址,它们只看得到Denali创建的虚拟地址)。与此相对,我们认为hypervisor中的安全访问控制已经足以确保安全性;此外,就像之前讨论过的,当前在guest OS是否应该能够直接看到物理资源这一点上存在着很热烈的关于正确性和性能的争论。
在后续的章节里,我们将描述Xen提出的虚拟机抽象,然后将讨论如何将一个guest OS作必要的改动以适应Xen。在这篇文章里我们定义了一些术语要提醒大家注意。例如,术语guest OS是指Xen能够操控的操作系统之一;术语domain是指一个运行中的虚拟机,在其上有一个guest OS在执行。program和process之间的区别和传统系统中的区别类似。我们称Xen本身为hypervisor,因为它运行的特权级要比它所操控的guest OS中的supervisor code运行的特权级更高。
2.1 虚拟机接口
一个准虚拟化的x86接口主要包括了系统中的三个大的方面:存储管理,CPU和设备I/O。在下面,我们将依次介绍各个机器子系统的情况,并讨论在我们的准虚拟化架构中是如何体现的。虽然在我们的实现中,有相当一部分,如存储管理,是专门针对x86的,但是实际上还有很多方面(比如我们虚拟的CPU和I/O设备)都是可以很容易地应用于其它机器架构上的。更进一步地说,在与RISC架构在实现上有差异的很多地方,x86往往表现出的是该方面最坏情况时的情形。例如,对硬件页表进行有效的虚拟化就比虚拟化一个软件管理的TLB困难很多。
存储
管理分段不能够使用具有完全特权级的段描述符,不能够与线性地址空间的最顶部交迭(//因为最顶部是Xen)。
分页guest
OS直接对硬件页表做读访问,但是更新(//就是写)是分批进行的而且要经过hypervisor确认。一个domain可以被分配在不连续的页面上。
CPU保护guest OS必须运行在低于Xen的特权级上。
异常guest OS必须将异常句柄的描述符表在Xen中记录。除了页面错误外,其它句柄和真实的x86架构相同。
系统调用guest OS为系统调用提供一个“快速”的句柄。允许应用直接调用它所在的guest OS,而不必间接地通过Xen完成每次调用。
中断硬件中断被一个轻量级的事件系统替换。
时间每个guest OS具有一个定时器接口,可以得到“真实的”和“虚拟的”时间。
设备I/O网络,磁盘,……虚拟设备访问起来很简单。数据传递使用的是异步I/O环。由一个事件机制替换硬件中断来发布通告。
2.1.1存储管理
虚拟化存储毫无疑问是准虚拟化一个体系结构中最困难的部分,它包括hypervisor所需的机制和移植各个guest
OS所需的改动。如果在架构中提供了由软件管理的TLB的话,那么这个任务会变得轻松些,它们可以以比较简单的方式被有效地虚拟化[13]。带标记的TLB是另外一个在大部分RISC架构(这些RISC架构主要用于构建服务器,比如Alpha,MIPS和SPARC)中支持的有用特征。其中,每个TLB项都有和地址空间标识符相关的标记,这使得hypervisor和各个guest OS能够有效地在被隔离开的地址空间内共存。这时在执行转移(//transferring execution:在进程执行间切换的时候,执行的指令序列从一个进程转移到另一个进程,称为执行转移)的时候,是不需要刷新(flush)整个TLB(//只对具有和自己的地址空间标识符相吻合的TLB项进行操作)。
不幸的是,x86架构并没有由软件管理的TLB;取而代之的是在发生TLB失效的时候,处理器会自动通过遍历硬件页表结构来处理。因此为了获得最好的可能达到的性能,当前地址空间内所有的有效页传输)都要在硬件可访问的页表中给出(//最好情况理应如此,但实际如何做得到呢?)。此外,因为TLB是没有标记的,所以地址空间的切换需要整个TLB的刷新。在这些限制下,我们作出了两个决定:(i)由guest OS负责分配和管理硬件页表,这么做最小化了Xen对页表操作的影响,确保了安全性和隔离性;(ii)Xen处于每个地址空间的最顶部的64MB空间内,因此避免了在进入和离开hypervisor时进行TLB刷新操作(//这个要看源代码才能最后搞清楚)。
每当guest OS需要一个新的页表,例如创建了一个新进程,它就在自己保留的内存空间内分配和初始化一个页面,并且将其在Xen中记录。此时操作系统必须放弃对页表存储空间直接写的权限:所有后续的更新操作都必须由Xen进行确认。这就在很多方面限制了更新操作,包括只允许操作系统它自己所属的页进行映射操作,不允许对页表进行可写的映射操作。guest OS可以成批地进行更新操作以减少每次更新都要进入hypervisor带来的代价(//因为每次更新都要hypervisor确认)。每个地址空间顶部的64MB区域是保留给Xen的,是不能够被guest OS访问或者重新进行映射的。因为任何通常的x86架构中的ABI都不会使用到这个区域中的地址,所以这个约束不会破坏到应用程序的兼容性。
分段机制也是以类似的方式,通过对硬件段描述符表的更新确认来进行虚拟化(//这样做就达到虚拟化的目的了么?哦,应该是Xen在确认后接着由Xen执行,嗯)。对于x86架构上段描述符的限制只有:(i)它们的特权级别必须比Xen要低;(ii)它们不能够对地址空间中Xen的保留部分进行访问。
2.1.2CPU
虚拟化CPU对guest OS提出了几个要求。因为hypervisor插在操作系统的下层违背了惯常的关于操作系统在整个系统中特权最高的假设。为了保护hypervisor不会受到操作系统不正确行为的影响(即domain不受另一个domain的影响),guest OS就必须被改造为能够运行在较低的特权级上。
很多处理器体系结构只是提供了两个特权级。在这些情况下,guest OS和应用程序共享较低的特权级。同时,guest OS运行在单独的地址空间中以保护自己不会受到应用程序执行的影响。guest OS通过hypervisor设定虚拟的特权级和改变当前的地址空间来间接地和应用之间进行控制传递。另外,如果处理器的TLB支持地址空间标记,那么也就可以避免TLB刷新带来的高昂代价。
在x86架构上有效地实现特权级的虚拟化是可能的,因为x86架构在硬件上支持四个不同的特权级。x86架构的特权级往往用圈(ring)来表示,从ring 0(最高特权)到ring 3(最低特权)。操作系统的代码运行在ring 0这个特权级上,因为再没有其它的ring能够执行那些特权指令。ring 3通常用于执行应用代码。就我们所知,自OS/2起到现在的各个知名的x86 架构上的操作系统都还没有利用ring 1和ring 2这两个特权级的。那么,任何遵循这个通常的安排的操作系统(//没有利用ring 1和ring 2)就都可以移植到Xen上来。
这个移植过程只需要做一些改动使操作系统改为运行在ring 1特权级上。这就防止了guest OS会直接执行特权指令,也保证了操作系统与运行在ring 3上的应用程序之间相隔离的安全性。
特权指令需要被Xen确认和执行以达到准虚拟化的目的,这主要应用于诸如安置新的页表,或者在处理器idle时放弃之(而不是去hlt它)等操作。因为只有Xen有足够高的特权级来执行这些指令,所以任何guest OS试图直接运行特权指令都会失败,后果要么是“沉默”要么是产生错误。
异常,包括内存错误和软件陷阱,都可以在x86架构的基础上直接进行虚拟化。有一个表,内容为对每类异常进行描述的句柄。表中所列的异常都是在Xen中有记录的,以用作确认。表中给出的句柄都是与真正的x86硬件中相同的;之所以这一点是可能做到的,主要是因为在我们的准虚拟化架构中,异常堆栈框架是没有被修改的。唯一的一个改动是在页面错误句柄上。因为该句柄的操作需要从特权处理器寄存器(CR2)中读出出错的地址;但是这是不可能的(//因为特权级别不够了),我们就将它(//页面错误句柄?CR2的值?)写入扩展的堆栈框架中(后来发现,在移植XP的时候,将这个值写入一个预先商定的共享存储位置上要比修改堆栈框架简单一些)。当系统在ring 0以外执行时有异常发生,Xen的句柄就会在guest OS堆栈中创建一个异常堆栈框架的拷贝,并且会将控制交给相应的已经记录过的异常句柄。
典型的,只有两类异常会经常发生而影响到系统的性能:系统调用(一般都是通过软件异常实现)和页面错误。我们让每个guest OS都记录一个“快速的”异常操作句柄来改进系统调用的性能。这个异常操作句柄可以直接由处理器使用,而不必非要间接地经过ring 0;这个句柄在放置进硬件异常列表中之前就是经过确认的(//所以不必经过Xen)。不幸的是,我们不可能使用同样的技术来处理页面错误句柄,因为只有那些运行在ring 0的代码才能够从寄存器CR2中读出错误的地址;因此,页面错误必须要经过Xen才能提交,Xen保存该寄存器的值供来自ring 1的访问使用。
当Xen发现异常产生时,它会对异常句柄进行确认以确保安全性。这只需要检查句柄的代码段中是否含有指明要在ring 0中执行的操作。既然没有guest OS能够创建这样一个段,那么只需要将专门的段选择符和少量的保留在Xen中的静态值作比较即可。除了这点以外,任何其它的句柄问题都会在异常传播(exception propagation)(//一个异常导致了另一个异常的产生)的过程中被修正。例如,如果句柄缺少相应代码段或者句柄没有分配到内存页,那么在Xen为将控制返回给句柄而执行iret指令的时候就会有一个相应的错误产生。Xen通过检查出错的程序计数器值来检测这些“双错误(//double faults:之前已经出错了,现在到了iret已经是第二个错误了;第二个错误是由第一个错误传播而来)”:如果地址是处于异常虚拟化的代码中(//说明异常处理没有完成,iret没成功),那么guest OS就要被终止。
对于直接的系统调用句柄来说,这种“懒惰(//第一个错误发生的时候,没有被检查到;直到Xen执行了iret之后才报错)”的检查也是安全的:当CPU试图直接跳至guest OS句柄的时候,会发生访问错误(//之前的过程都一样,只是直接的系统调用是不经过Xen的)。在这种情况下,产生错误的地址将处于Xen之外(因为Xen不会去执行guest OS系统调用),因此错误就以上文讲过的一般方式进行虚拟化即可。如果由于错误的传播导致了进一步的“双错误”,那么guest OS会像上文谈及的一样被终止。
2.1.3设备I/O
在完全虚拟化环境下需要仿真现有的硬件设备,而Xen不同于此。Xen给出了一套清楚、简单的设备抽象。这就使得我们能够设计一个接口以有效地满足我们对保护性和隔离性的需求。为了做到这一点,I/O和各个domain之间的数据传递都是要经过Xen的,可以使用的方法有共享内存,异步缓冲区描述符环等。这些方法能够在Xen有效地执行确认检查(例如,检查缓冲区是否包括在了domain的存储空间内)的同时,为在系统中的竖直方向上传递缓冲区信息提供了一个高性能的通信机制。和硬件中断类似,Xen支持一个轻量级的事件递交机制用于为一个domain传送异步通告(notification)。这些通告是在对未决事件类型的位图进行更新的时候产生的,也可以通过调用一个guest OS专有的事件句柄产生。这些调用的返回可以由guest OS来决定是否进行“拖延”处理。例如,这么做(//拖延处理)可以避免在频繁唤醒通告时带来的额外开销。
2.2移植OS到Xen的代价
当前我们的NetBSD移植还处于非常初级的阶段,因此我们就没有将其结果在这里报告。虽然XP的移植要更进一步,但也还处于移植过程中;当前移植的XP能够执行RAM上的用户空间的应用,但是缺乏虚拟的I/O驱动。基于这个原因,表中就没有给出和XP的虚拟设备驱动相关的数据。无论怎样,和Linux一样,我们可以想见这些驱动应该是小的和简单的,因为这要得益于Xen提供的理想的硬件抽象。
衡量代价的标准是与原先的x86代码相比修改或增加的那些必要的注释以及遵从一定格式的代码的行数(不包括设备驱动)。对Windows XP中体系结构无关(architecture independent)的代码所作的改动达到了一个惊人的数字,这是因为Windows XP使用了多种多样的结构和联合来访问页表项(PTE)。每次对页表的访问都不得不被单独地进行修改(//因为每次访问都可能用到不同的结构),当然这个过程是可以采用一些脚本来自动完成的。与此相反的,Linux需要的改动就少了很多,这是因为Linux的存储系统是使用预处理程序中的宏来访问PTE的— 这些宏定义为增加准虚拟化所需的转换和hypervisor调用提供了便利的位置(//就在这些位置上加即可)。
在这两个操作系统中,体系结构特有(architecture specific)的部分用于将x86代码向我们的准虚拟架构的移植。这包括重写那些使用了特权指令的程序,删除大量的低层的系统初始化代码。另外,Window XP需要有更多的改变,这主要是因为之前遗留下来的16位仿真代码的存在以及需要一个略有不同引导加载(boot-loading)机制。注意,XP中的x86特有的代码要比Linux多很多,因此可以预见到在做移植的时候也就需要做更多的工作。
2.3控制和管理
贯穿于整个Xen的设计与实现过程中,有一个目标就是尽可能地将策略从机制当中剥离出来。虽然hypervisor必须要被包含在数据通路(data-path aspects)上。例如,在domain之间调度CPU,在发送之前过滤网络数据包,或者在读数据块的时候进行执行访问控制(//必然的,因为Xen位于guest OS和底层硬件之间,guest OS又是彼此隔离的,所以数据传递是一定都要经由Xen的)。但是在更高层次的问题上,例如CPU如何被共享或者各个domain能够发送哪种数据包,这时就不需要将Xen包括在内了,甚至都不用考虑它(//hypervisor是实现机制,而如何共享CPU和如何进行任务分工都是策略问题)。
最终得到的架构是hypervisor只是提供那些最基本的控制操作。这些操作经由一个可访问的接口从经过授权的domain传来;而那些复杂的策略决策,比如许可控制(//不知道这个许可控制是否和第1部分里提到的是一回事儿),都最好由运行在guest OS上的管理软件执行,而并非在有特权的hypervisor代码中(//Xen只是提供机制,不负责策略)。
整个系统架构中有一个domain是在引导(boot)时创建的。这个domain被允许使用控制接口。这个初始的domain,术语称为Domain 0,它负责操控应用级的管理软件。控制接口具有创建和终止其它domain的能力,还能控制它们相关的调度参数、物理存储分配以及它们对给定的物理磁盘和网络设备的访问。
除了处理器和存储资源,控制接口还支持虚拟网络接口(VIF)和块设备(VBD:虚拟块设备)的创建和删除。这些虚拟I/O设备具有一些和访问控制相关的信息。这些信息决定了哪个domain能够访问它们,以及访问时有哪些约束(例如,一个只读的VBD可以被创建,一个VIF可以过滤IP包以防止源地址欺骗)。
这个控制接口,结合对系统当前状态进行的剖析统计,其结果能够被输出到一套运行在Domain 0上的应用级管理软件上。该管理软件作为管理工具的补充,能够对整个服务器进行方便地管理:例如,能够创建和破坏domain,设定网络过滤器和路由规则,在数据包和数据流两个粒度上监视每个domain的网络活动,创建和删除虚拟网络接口和虚拟块设备。我们期待开发出高级的工具来进一步将管理策略的应用程序自动化(//这里的管理策略和前面讲的“机制与策略分开”中的策略不是一回事儿吧?)。