现代计算机具有足够强大的能力来利用虚拟化技术支持多个虚拟机(VM: virtual machines),并且在每个虚拟机上各自运行单独的操作系统实例。这直接导致了虚拟机技术发展的又一个春天。在本文中,我们提出了Xen,一个高性能的用于资源管理的虚拟机监视器(VMM: VM monitor)。Xen能够支持的应用比如:server consolidation,co-located hosting facilities,distributed web services,secure computing platforms[12,16]和application mobility。
成功地对一台机器进行划分,使它能够支持多个操作系统的并发执行,这个过程具有很多的挑战。首先,虚拟机必须是彼此相隔离的:如果一个虚拟机的执行会影响另一个的性能,这是不可以被接受的。这一点在操作各个虚拟机的用户相互间并不信任的情况下显得特别重要。其次,它必须支持多种多样的不同操作系统以提供给各种异构(heterogeneity)的流行应用的支持(//这里的异构指的是应用开发依托的操作系统不同,因此在实现上也就有很大差异,使得应用并不能够跨平台移植,因为Xen不需要对应用程序进行修改,那么它就必须支持各种常用的操作系统;所谓流行的应用,就是那些大家常用的、必需的应用)。第三,由虚拟化技术引入的性能开销必须要小。
Xen操控(//host:操作和控制)的是常用的操作系统,但是需要对操作系统中的某些相关部分进行一些修改。在本文中描述和评估的Xen原型系统能够支持多个我们研发的XenoLinux guest OS实例的并发执行;每个实例都给出了和非虚拟化情况下的Linux 2.4中相同的应用二进制接口。目前,我们对Windows XP到Xen的移植还没有完全完成,但是已经能够运行简单的用户空间进程。移植NetBSD的工作也在进行中。
Xen使得用户能够动态地实例化一个操作系统以执行他们需要的应用。在XenoServer项目[15,35]中,我们在ISP或者Internet exchange(//布置在这些场合中经济划算而且又具有战略意义的地方)的标准服务器硬件上配置了Xen。我们在启动一个新的虚拟机的时候需要执行许可控制(admission control),希望每个虚拟机能够以某种方式为它需要的资源付出代价。我们在其它文章中讨论过我们在这个方向上的思路和方法[21];现在这篇文章则将焦点关注于虚拟机。
现在有一些方法用于构建能够在共享的机器上操控多个应用和服务器(//server:这里提到的server应该是大规模应用的意思,比如数据库服务器)的系统。也许最简单的方法就是部署一个或多个运行着标准操作系统(如Linux或者Windows)的主机,然后允许用户们安装文件和启动进程— 应用间的保护是由传统的操作系统技术提供的(//这里提到的做法,就是类似并行机性质的,各个节点都是独立的主机,但是一个应用可以在多个节点上面并行执行)。实验显示:由于要针对各个脱节(//supposedly disjoint:逻辑上脱节,指的是应用不具有连贯性/兼容性,所以针对每个应用都要单独配置一次,如果能想办法将前后有关联的应用放在一起执行,可以大大减少相关的开销,如配置开销,通信开销等等)的应用进行复杂的配置,这些配置过程导致的交互行为会使系统管理任务迅速成为时间消耗巨大的任务。
更重要的是,这样的系统不能够充分地支持性能隔离;某个进程的调度优先级,存储要求,网络通信量和磁盘访问等等特征都会影响到其它进程的性能。如果是在资源供应充足而且用户群体是限定(比如计算网格或者PlanetLab平台实验)的情况下,这个系统还是可以接受的。但是当资源是供不应求的时候,或者用户间不相协作(//uncooperative:不相协作,比如用户间的需求有冲突,那么一定会互相影响的)的时候就不行了。一个解决这个问题的方法是改进对操作系统性能隔离的支持。这已经在resource containers,Linux/RK,QLinux和SILK中被或多或少地实现了。这些方法中存在着一个难点是难以确保所有的资源都能够正确地分配给有相应资源需求的进程。例如,缓冲区cache或者存储页面的替换算法导致的在应用间的复杂的交互行为(//比如存储页面替换的时候,某个进程的页面替换序列会干扰到其它进程的,要免除这个影响就需要有复杂的机制用于进程间的协调)。这就是存在于操作系统中的“QoS干扰问题(QoS crosstalk)”。在低层采用多路执行技术能够缓解这个问题带来的影响,这已经在Exokerne和Nemesis操作系统中得到证明。在这些操作系统中,任务间无意识的或者不受欢迎的交互行为被最小化了。
我们使用了同样的基本方法来构建Xen。Xen就是以整个操作系统的粒度复用物理资源,它能够提供在操作系统间的性能隔离。相对于进程级的资源复用,Xen要允许一定范围内的guest OS“和平”共存,而并非去指定一个特殊的应用二进制接口(//如果限定了应用二进制接口,就意味着只能支持某个特定的操作系统)。为了获得这种灵活性就必须付出一些代价 — 无论是在初始化过程(比如,boot和resume与fork和exec的比较)中,还是在资源消费上,运行一个完整的操作系统与运行一个进程相比分量都要重得多。
为了达到我们的能够支持多至100个被操控的操作系统实例的目标,我们认为这些代价是值得付出的。付出这些代价后获得的系统允许单个用户在资源受控的形式下,直接运行那些不需要修改的二进制代码或者二进制代码集合(例如,后端为PostgreSQL的Apache服务器)。更进一步的,因为用户能够动态地精确创建他们的软件所需要的执行环境,所以这个系统也就提供了在非常高的层次上的灵活性。另外,在各种服务和应用间的配置交互也是可以避免的(例如,每个Windows实例都有它们自己的寄存器文件)。
本文的余下部分是这样组织的:第2部分解释了我们的虚拟化方法和Xen的工作概况。第3部分描述了我们的设计和实现的关键特征。第4部分给出了使用业界标准的测试程序评估运行在Xen上的XenoLinux与单独的Linux,VMware Workstation和用户模式Linux(UML)的性能比较结果。最后的第5部分讨论了未来的工作并作了总结。
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的网络活动,创建和删除虚拟网络接口和虚拟块设备。我们期待开发出高级的工具来进一步将管理策略的应用程序自动化(//这里的管理策略和前面讲的“机制与策略分开”中的策略不是一回事儿吧?)。
ccid_page/>
3. DETAILED DESIGN — 细节设计
在这一部分,我们介绍构成基于Xen的服务器的各个主要子部分的设计细节。在各个设计中,我们对Xen和guest OS的功能作了清楚的说明。当前的关于guest OS的讨论主要集中于XenoLinux,这主要是因为这个guest OS是目前发展最成熟的;但是我们对于当前正在进行中的对Windows XP和NetBSD的移植工作也是很有信心的。我们相信Xen可以支持多种多样的guest OS。
3.1 控制传递:hypercalls和事件
有两种机制用于Xen和其上的domain之间进行控制的交互:使用hypercall产生从domain到Xen的同步调用;使用异步事件机制完成从Xen到domain的通告递交。
hypercall接口允许domain通过执行一个同步软陷阱陷入到hypervisor执行一个特权操作,这类似于在传统的操作系统中对系统调用的使用。举一个使用hypercall的例子:一组页表更新的请求,要经过Xen确认并且完成相应的更新操作(//更新是要由Xen确认并完成的,需要特权操作,所以这时要利用hypercall陷入到hypervisor中),在更新完成后再由Xen将控制返回给产生本次调用的domain。
从Xen到domain的通信是由一个异步事件机制提供的。这个机制取代了常用的利用设备中断的递交机制,它允许那些重要事件(如domain-termination request)采用轻量级的通告形式。和传统的Unix信号类似,这些重要事件的个数比较少,但每一个都用作针对某一特定类型事件的标记。例如,用于在网络上指出新的数据已经被接收到的事件,或者表示一个虚拟磁盘请求已经完成的事件。
那些未决的事件存放在每个domain的bitmask(//一个专门的数据结构)中。bitmask的更新要由Xen在调用一个和guest OS相应的事件调用返回句柄之前完成(//Xen针对某类事件要向上发通告,如果Xen调用了guest OS相应的事件调用返回句柄,就说明该事件完成了,下面要把控制交回给domain,所以必然要在调用事件调用返回句柄之前由Xen将bitmask更新)。调用返回句柄负责重新设置未决事件集合(//调用返回句柄仍旧是由Xen操作,更新bitmask),同时以相应的行为和通告相呼应。一个domain可以通过设置一个Xen可读的软件标记来显式地推迟对事件操作:这一点是与在真实的处理器中禁止中断的过程类似的。
3.2 数据传递:I/O环
hypervisor的存在意味着在guest OS和I/O设备之间有一个额外的保护域,所以数据的传递机制就变得至关重要。数据传递机制使得数据能够在系统中沿着竖直方向移动,同时具有尽量小的开销。
有两个主要方面构成了我们对I/O传递机制的设计:资源管理和事件通告。为了做到resource accountability,我们在接收到一个来自设备的中断后,要尽量减少在将多路数据分解(//demultiplex data:分解多路数据,数据自硬件设备传来,需要传递给各个指定的domain中的guest OS中执行,这里就存在一个多路选择的问题来确定究竟把数据传给哪个domain;一旦确定了数据是传给哪个domain的,也就说明了此时是哪个domain在使用相关设备,做到了resource accountability)到一个特定的domain中所做的工作 —
管理缓冲区带来的开销是在计算任务分配给相应的domain后产生的(//任务分给domain后,domain才开始对任务数据所处的那部分缓冲区进行管理,而并非用其它的机制对整个缓冲区统一管理,那样会增加复杂性,而且缺乏保障)。类似的,设备I/O的访存操作也是由相应的domain提供的,这么做可以防止由于共享缓冲池导致的相互间的干扰(//如果I/O能够直接访存而不经过domain管理的话,就会产生混乱,比如不清楚I/O操作存取到的数据是属于哪个domain的);I/O缓冲在数据传递过程中通过Xen内部绑定(pin)到底层页框上面的方法来获得保护。
I/O描述符环是一个循环队列,它由domain分派的描述符组成,可以从Xen内部访问到。描述符中并不直接包含有I/O数据;取而代之的是,I/O数据缓冲被guest OS在带宽外分配再间接地由I/O描述符引用(//I/O描述符环的容量是有限的,所以I/O数据要先进行分配,再做向I/O描述符环上的映射;所谓out-of-band:带宽外,就是在分配数据时可以超出I/O描述符环的限制)。每次环访问都要基于两对生产者-消费者指针:domain通过推进请求生产者指针将一个请求放置在环上;Xen处理这些请求,推进一个相关的请求消费者指针。响应被放回在环上也是类似的,只是由Xen作为生产者,guest OS作为消费者。这里是不要求请求是被按顺序被处理的:guest OS给每个请求都建立了一个唯一的相关标识符,这个标识符会在相关的响应上被复制(//描述符环只是限定了能够处理请求的规模,并不规定处理顺序,谁先被处理谁后被处理是在将数据映射到描述符环上的时候决定的)。这就允许Xen出于调度和优先级的考虑,重新排定I/O操作的顺序。
这个结构是通用的,足以支持很多不同的设备范例。例如,如果一组“请求”要为网络数据包的接收提供缓冲,那么相应的后续“响应”就是数据包已经到达缓冲区的信号。重新排序在处理磁盘请求时是很有用的(//比如两个domain请求都要读同一块数据,那么就可以把这两个请求的处理放到一起完成,提高效率),它允许它们在Xen内部被调度以提高效率;使用带宽外缓冲的描述符使得能够容易地实现零复制传输(//减少了数据拷贝,如果不用描述符的话,那么就务必要将缓冲区的数据内容在I/O环中进行复制,而现在做的只是映射操作,即零复制)。
我们将请求和响应的产生和其它的通告分开:在请求的情况下,一个domain可以在调用一个hypercall陷入Xen之前将多个项排入队列;在响应的情况下,一个domain能够通过划定一个相应的阀值来推迟通告事件的递交。这使得每个domain能够权衡延迟和吞吐量的需求,类似在ArseNIC吉比特以太网接口的flow-aware(//流优化?)的中断分派[34]。
3.3 子系统虚拟化
前文描述的控制和数据传递机制被使用在我们对各个子系统的虚拟化实现中。在下面,我们讨论如何获得对CPU,定时器,内存,网络和磁盘的虚拟化。
3.3.1CPU调度
Xen当前对domain的调度采用的是Borrowed Virtual Time(BVT)调度算法[11]。我们之所以选择这个特别的算法是因为它是工作保养型的,同时还具有特殊的机制使得它能够在接收到一个事件时可以低延迟地唤醒(或者说分派)一个domain。快速的分派(//dispatch,就是类似前面说的demultiplex data)对于减少虚拟化对那些对时间敏感的操作系统子系统造成的影响是尤其重要的(//分派越快速,事件就越能及时到达操作系统);例如,TCP要依赖于确认信息的及时递交以正确地估测在网络上往返所用的时间(//如果确认信息没有及时到达,那么时间估测就有问题了)。BVT provides low-latency dispatch by using virtual-time warping, a mechanism which temporarily violates `ideal' fair sharing to favor recently-woken domains(//不很明白BVT算法的实现)。当然,其它调度算法也能够在我们的通用调度器抽象上实现。每个domain的调度参数可以由运行在Domain 0的管理软件进行调整。
3.3.2 时间和定时器
Xen为每个guest OS提供了真实时间,虚拟时间和挂钟时间(wall-clock time)等三个概念。真实时间是以纳秒为单位给出的,是从机器引导起来开始计算的时间,它记录的是精确的处理器执行的时钟周期数,时钟周期能够被外部时钟源锁频(例如,通过NTP时间服务)。domain的虚拟时间只是在它运行的时候才会被记入:这主要供guest OS的调度器使用来确保guest OS上的应用进程能够正确地共享时间片。最后,挂钟时间是一个偏移,用于加到当前的真实时间上。挂钟时间能够被调整(//比如在各个guest OS中的时间可以不同,有的是上午九点,有的是下午三点,改的就是这个值),同时不会影响真实时间的流逝。
每一个guest OS能够对一对警钟定时器进行编程,一个用于真实时间,另一个用于虚拟时间。guest OS能够维护内部的定时器队列(//很多应用程序都有定时需求,真实的或者虚拟的,特别是网络应用;guest OS要对这些定时需求排队以确定谁的时间要求最紧迫,然后就尽量依次满足它们;只要有一个定时需求没被满足,即超时了,那么系统就产生异常)并使用Xen提供的警钟定时器来触发最早的超时。超时事件将会使用Xen事件机制来递交。
3.3.3虚拟地址转换
和其它子系统一样,Xen也努力以尽可能小的开销实现存储访问的虚拟化。正如2.1.1中讨论的,由于x86架构使用的是硬件页表,因此达到这个目标有些难度。VMware中使用的方法是为每个guest OS提供虚拟的页表,这个页表对于存储管理单元(MMU)是不可见的[10]。然后由hypervisor负责陷入对虚拟页表的访问,确认更新,再将这些改变传播到虚拟页表和MMU可见的“影子”页表(//“影子”页表在第2部分提到过)。这大大增加了这个guest
OS操作的代价,比如创建一个新的虚拟地址空间,需要对“访问过的”和“脏的”比特位上的硬件更新进行显式地传播。
之所以完全虚拟化要强迫使用影子页表,是因为它要给出连续的物理内存的假象,而Xen就不拘泥于此。实际上,Xen只需要考虑对页表的更新,来防止guest OS导致不可接受的改变。因此我们避免了和使用影子页表相关的开销和额外的复杂度 — Xen中的方法是直接地由MMU记录guest OS的页表,并且限制住guest OS只能做读访问。页表更新通过hypercall传递给Xen;更新请求在被采纳以前经过确认,这么做就确保了安全性。
为了有助于确认,我们给机器的每个页框都建立了一个相关的类型和引用数。一个页框在任何时候都会排它地具有下述的某一个类型:页目录(PD),页表(PT),局部描述符表(LDT),全局描述符表(GDT)。同时,这个页框还可能是可写(RW)的类型。一个guest OS可以为它自己所属的页框创建可读的映射,而不必理会页框的当前类型。一个页框只有在它的引用数为0的时候,才能被安全地重新分配给其它任务使用。这个机制被用于满足系统对安全性一贯的需求;例如,一个domain不能够任意地对页表的某个部分建立可写的映射,如果要这么做的话就必须要求页框同时具有PT和RW类型(//什么情况下是可读的映射?什么情况下又是可写的映射呢?)。
类型系统还要被用于跟踪那些用于页表的页框中哪些是已经被确认过的(//页框用于页表,即页内容填入页框)。这时,guest OS要指出页框是否被分配用作页表 — 这就需要在页框的类型被定为PD或者PT后(//说明页框是用于页表的),由Xen对页框中的每一项做一次性地确认,直至后来由guest OS发出释放(unpin)的请求(//由guest OS释放页框)。这在改变页表基指针的时候是很有用的,因为它不需要在每一次上下文切换时都对新页表进行确认(//因为现在只确认页框了)。注意,一个页框在被释放并且引用数为0之前是不能够被重新分配给其它任务的,这样就避免了guest OS可能会绕开引用数机制而直接利用释放请求导致的危险。
为了减少所需的hypercall调用的次数,guest OS能够在利用一个hypercall进行整批处理之前在局部将更新排入一个队列。特别是在创建新的地址空间的时候,这一点是尤其有好处的。但是无论怎样,我们必须确保更新被提交得足够早以保证准确性。幸运的是,一个guest OS在第一次使用一个新的映射前要执行一次TLB刷新:这确保了任何被缓存的改变(//这些改变目前仅存于TLB中,还没有被写入内存页表项)都是没有经过确认的。因此,在TLB刷新之前立即提交那些待处理的更新就可以满足正确性(//更新的时间,解决了什么时候进行成批更新操作以保证正确性的问题)。可是,有一些guest OS会在TLB中没有陈旧表项存在(//没有陈旧表项意味着更新都已经送达内存中?)的时候省略掉这个刷新的步骤。在这种情况下,就有可能发生第一次试图使用新的映射时会发生缺页错误(//因为没有刷新TLB)。 这时,guest OS错误句柄就必须要核查是否需要更新;如果需要的话,TLB就要被刷新(//加入所缺的内容),并且将导致刚才缺页错误的指令重新执行。
3.3.4物理内存
为各个domain进行的初始的内存分配,或者说内存保留,都是在domain被创建的时候进行的;因此,内存在domain之间是静态划分的,这就提供了强大的隔离性。最大允许的内存保留量是被规定了的:如果某个domain中的内存压力增加了,那么它就可以从Xen争取额外的内存页,直到达到规定保留量的上限。反过来,如果一个domain希望节省些资源,避免不必要的开销,它也能够通过释放内存页给Xen来减少它的内存保留量。
XenoLinux实现了一个气球驱动 [42](//没看相关文献),它能够通过在Xen和XenoLinux的页面分配器之间传递内存页来调整domain的内存使用。虽然我们能够直接修改Linux的内存管理例程,但是气球驱动能够利用现有的操作系统功能来进行调整,因此简化了Linux的移植工作。无论怎样,准虚拟化能够被用于扩展气球驱动的能力。例如,guest OS中的内存越界处理机制能够被修改为通过向Xen请求更多的内存页来自动地减轻内存压力。
大部分操作系统都假定内存空间是由大量的较大规模的连续区域组成的。由于Xen并不保证它为guest OS分配的内存区域是连续的,所以guest OS需要为它们自己创建连续的物理内存的假象,即使它们实际在底层分配到的硬件内存是稀疏的。guest OS通过简单地维护一个以实际分得的页框号为索引的数组,来完全负责从实际分得的地址(physical)到硬件地址的映射。Xen提供了一个共享的地址转换数组,这个数组用于支持从硬件地址到各个domain实际分得地址的映射,它是所有的domain都可读的 — 更新这个数组是经过Xen确认的,以确保guest OS能够拥有相应的硬件页框。
即使一个guest OS在大多数情况下选择了忽略硬件地址(//因为guest OS喜欢在连续的地址空间上运行),但是在它访问它所拥有的页表的时候(这时必须使用硬件地址)必须使用转换表。硬件地址也可以暴露给操作系统存储管理系统中的有限的某些部分以优化存储访问(//具体是哪些部分可以直接“看到”硬件地址呢?)。例如,一个guest OS可以分配特定的硬件页用于优化以实际分得地址为索引的缓存中的布局,或者使用超级页将硬件内存中的连续部分作自然对齐的映射。
3.3.5网络
Xen提供了虚拟防火墙—路由器(VFR)的抽象,每个domain都有一个或多个在逻辑上附属于VFR的网络接口(VIF)。VIF看上去有些现代的网卡:具有两个I/O缓冲区描述符环,一个用于发送一个用于接收。在每个方向(//发送或者接收)上都有一些相关的规则形式(,)— 如果模式(pattern)匹配上的话,那么相关的动作(action)就会被起用。
Domain 0负责插入和删除规则。例如在典型的情况下,可以增加规则防止源IP地址欺骗,或者增加规则确保基于目的IP地址和端口的正确的数据分派(demultiplexing)。规则也可以是和VFR上的硬件接口相关的。特别是我们增加的那些用于执行传统的防火墙功能的规则,比如防止在不安全的端口上建立连接的企图。
为了发送一个数据包,guest OS简单地将缓冲区描述符排队进发送环中。Xen复制描述符,同时为了确保安全性,还要复制数据包头并执行匹配过滤规则。数据包的有效载荷不会被复制,因为我们使用的是分散—集中式(scatter-gather)的DMA;这里要注意的是,相应的页框必须被绑定,直到数据包传送完成。为了保证公平性,Xen在数据包调度器上实现了简单的循环轮转(round-robin)算法。
为了有效地实现数据包的接收,我们需要guest OS为每一个它接收到的数据包交换一个没有使用的页框(//这个招儿挺绝的,我就不将页内容复制到我自己的页框里了,直接把这个页框纳入自己的旗下,然后再换个我没用的空页框给你,反正只要修改地址转换数组就可以了);这就避免了需要在Xen和guest OS之间复制数据包内容的麻烦,但是这么做必须要求在网络接口中接收缓冲队列中的有页对齐的区域(//因为这么做是直接以页框为单位进行的,所以一次交换就是一页的内容,所以需要提供页对齐的缓冲区域才可以进行)。当一个数据包被接收,Xen立即检查接收规则组来确定目的VIF,然后将数据包缓冲区和相应的接收环(//接收环存在于各个domain中)交换一个页框。如果这时没有页框可用的话,数据包就被丢弃。
3.3.6 磁盘
只有Domain 0能够不经检查地直接访问物理(IDE和SCSI)磁盘。所有其它的domain访问永久性存储介质的时候,都要通过虚拟块设备(VBD)抽象。这个抽象是由运行在Domain 0中的管理软件来创建和配置的。由Domain 0管理VBD使得Xen中的机制比较简单,不避使用更复杂的解决方案(比如Exokernel中使用的UDF[23])。
一个VBD是由一些和所有权以及访问控制信息相关的扩展组成的,可以通过I/O环机制来访问。一个典型的guest OS磁盘调度算法将重新排定请求的优先级并把它们排进环中,这样做是为了尽量减少响应时间,并且可以有区别地进行服务(例如,由于进行投机的向前读请求的代价很高,调度器可能会选择主动先去调度后面的对同步数据的访问请求)。另外,因为Xen对真实的磁盘规划具有更完整的认识,所以我们也要在Xen中支持重新定序(//刚刚提到的是guest OS),并且可以乱序地返回响应。因此,对于guest OS来说,VBD有一点像SCSI磁盘。
在hypervisor内部还为每个VBD维护了一个转换表;整个表中的内容都由Domain 0通过特权控制接口进行安装和管理。在接收到一个磁盘请求后,Xen检查VBD标识符和偏移,并且产生相应的扇区地址和所属物理设备。许可检查也是在这个时候进行的(//检查到底能不能进行这个磁盘操作)。零复制数据传递将以DMA的方式在磁盘和绑定到该发出请求的domain上的内存页之间进行。
Xen会使用简单的循环轮转方式来成批处理各个存在竞争关系的domain发出的请求;然后,在到达磁盘硬件之前,这些请求传给标准的电梯调度器。domain可以显式地设置重定序障碍(//不许重定序)来防止在必须维持高层次语义的时候(例如,在使用了一个预写日志(write-ahead log)的时候)进行重定序。当成批的请求中能够显现出访问的公平性的时候,低层的调度能够给我们带来很好的吞吐量。下一步工作将研究使用现有的技术和调度器提供更可预测的隔离性和支持更有差别的设备。
3.4建立新的Domain
为一个新的domain建立初始的guest OS结构,这个任务很大程度上是委托给Domain 0完成的。Domain 0使用它的特权控制接口(2.3段)访问新的domain的存储空间并告知Xen该新domain的初始寄存器状态。这个方法相对于由Xen建立整个domain来说有一些优势,包括减少了hypervisor的复杂度,改进了鲁棒性(对特权接口的访问要经过完全地检查的,使得我们能够在初始阶段捕捉到大部分的bug。
最重要的是,整个建立过程易于被扩展,可以应付新的guest OS。例如,Linux内核引导时的地址空间是要比Windows XP简单得多的。我们可以为所有的guest OS指定一个固定的初始内存规划,但是这样的话就需要针对每个guest OS编写额外的引导陷阱代码用来安置操作系统所需的其它部分。不幸的是,这类代码是非常难以实现的;为了获得简单性和鲁棒性,更好的实现方法就是使用Domain 0,它能够提供比引导程序更充裕的诊断和调试支持。
Scatter-gather DMA方式是与block DMA方式相对应的一种DMA方式。
在DMA传输数据的过程中,要求源物理地址和目标物理地址必须是连续的。但是在某些计算机体系中,如IA架构,连续的存储器地址在物理上不一定是连续的,所以DMA传输要分成多次完成。
如果在传输完一块物理上连续的数据后引起一次中断,然后再由主机进行下一块物理上连续的数据传输,那么这种方式就为block DMA方式。Scatter-gather DMA方式则不同,它使用一个链表描述物理上不连续的存储空间,然后把链表首地址告诉DMA master。DMA master在传输完一块物理连续的数据后,不用发起中断,而是根据链表来传输下一块物理上连续的数据,直到传输完毕后再发起一次中断。
很显然,scatter-gather DMA方式比block DMA方式效率高。
4.相关工作
虚拟化技术被应用在商业化和研究型操作系统上已经有近30年了。IBM VM/370[19,38]最先使用了虚拟化技术以提供对先前存留的代码的支持。VMware[10]和Connectix[8]采用了将常用的PC硬件进行虚拟化的方法,允许多个操作系统在同一台主机上运行。所有这些例子都对底层硬件(至少是底层硬件的一个子集)进行了完全虚拟化的实现,而并非是准虚拟化的方法提供给guest OS一个修改后的接口。正如我们的评估结果中给出的:完全虚拟化虽然能够更容易地支持商业市售的操作系统,但是却大大降低了性能。
VMM方法还被Disco用于将常用的操作系统高效地运行在ccNUMA机器上[7,18]。其间要对被操控的操作系统做少量的改动,以使其能够虚拟化地运行在MIPS体系结构上。另外,出于性能的考虑,还要做一些其它修改。
现在,我们知道有两个其它的系统也采用了准虚拟化的方法:IBM不久前提出的Linux的准虚拟化版本允许大量的Linux实例同时运行,将用于他们的zSeries大型机上。Denali[44]在之前已经讨论过,它是一个暂时隔离的内核,试图提供能够操控大量虚拟操作系统实例的系统能力。
除了Denali,我们还知道有两种其它的方法使用了低层虚拟化技术建立分布式系统的底层架构。vMatrix[1]是基于VMware的,它的目标是建立一个用于在不同机器间移动代码的平台。由于vMatrix是在VMware之上开发的,因此它更关注的是虚拟化技术在分布式环境中存在的高层问题。另外,在IBM提出的“托管管理(Managed Hosting)”服务中,虚拟Linux的实例可以在IBM大型机上被租用(//大型机上跑多个Linux实例,你可以租一个用,搭建你自己的系统,和其他租户共享大型机的资源)。
PlanetLab项目[33]构建了一个分布式的底层架构,它的设计目的是作为实验床用于研究和开发地理空间分布的网络服务。平台的对象是研究者,试图将单个的物理主机划分为条(sliver),提供同时的对用户的低层访问。项目当前使用的是VServers[17]和SILK[4]来管理操作系统内部的共享。
我们再和操作系统外延研究和主动网络通信研究中的一些思路作比较。当代码在Xen上面运行的时候没有必要检查其“安全性”,也没有必要去检查代码运行是否能够保证终止,因为在这些情况中唯一的受害人是那些可疑的客户。于是Xen提供了更通用的方案:这个方案不需要由一个可信的编译器为被操控的代码做数字签名(比如SPIN[5]),不需要这些代码被一个安全证明伴随(比如PCC[31]),不需要由一种特殊的语言写成(比如SafetyNet[22]或者其它基于Java的系统),也不需要依赖于特殊的中间件(比如移动代理(mobile-agent)系统)。当然,这些其它的技术能够继续在运行在Xen上的guest OS中使用,而且可能会对那些时限更短暂的任务负载有着特别的用途,因为这类任务没有机会被成批处理以减少启动一个新的domain的代价。(//这段的意思,我的感觉是在操作系统外延研究和主动网络通信研究为了保证代码安全,采用了多种多样的方法;但是在Xen中,这些方法都是不必要的,因为Xen的安全性确认策略比较简单,前文有提及;但是这些方法在Xen中也还是有它们的作用,比如对于时限短暂的任务,它等不及成批地被确认,那么它就需要用其它方法保证安全性。)
关于语言级虚拟机(//比如Java虚拟机)方法中也存在着类似的问题:一个管理资源(resource-managed)的JVM[9]肯定能够操控不可信的应用,前提是这些应用必须被编译为Java字节码并且遵循特别的系统安全模型(//不可信的代码,即使出了问题,但是由于其遵循系统安全模型,所以也不会造成危害)。这样的话,Xen就能够容易地支持语言级虚拟机,就像支持其它运行在guest
OS上的应用一样。(//这段的意思,和前面类似,仍旧是表明Xen并不提供过多的安全性检查;如果要跑语言虚拟机之类的应用,语言的安全性要由虚拟机应用本身保证,而不关系到Xen。)
5.讨论和结论
我们在上文介绍了Xen hypervisor。它能够将计算机的资源划分给各个运行着guest
OS的domain。我们的准虚拟化设计特别强调了性能和资源管理。我们还描述和评估了XeonLinux,XeonLinux是将Linux 2.4内核向Xen上做的完全移植。
5.1 Future Work — 将来的工作
我们认为Xen和XenoLinux完全能够被用于更广阔的空间,所以我们准备在不久的将来把我们的软件做成一个公开版本。当前已经有一个Beta版本正在被评估(//貌似就是那个Clarkson University做的工作);一旦评估阶段结束,我们就会在项目主页上发布1.0版。
在完成初始版本后,我们计划对Xen做一些的扩展和改进。为了增加虚拟块设备的效率,我们准备实现一个由块内容索引的共享的通用缓冲Cache(universal buffer cache)。这将为我们的设计增加受控的数据共享,同时却不牺牲隔离性。为虚拟块设备增加写复制(copy-on-write)语义,使它们能够在domain之间被安全地共享,即使是不同的文件系统也不会有问题(//写复制,保证一致性,减少内容复制开销;不过跨文件系统应该还是不很容易吧?)。
为了提供更好的physical(//物理?还是像之前提到的是“实际分得的”?应该是后者)内存性能,我们计划实现一个最后机会页缓存(LPC:last-chance page cache)。这是一个全系统范围内的空闲页链表,只有在机器内存未被分光的情况下,链表才有非零的长度。当guest OS虚拟存储系统选择舍弃一个干净(clean:数据中没有dirty data,都是与磁盘中相同的)的页时会使用到LPC;这个干净的页会被加入到空闲链表的结尾,而并非被完全抛弃。如果在该页重新被Xen分配之前发生了和该页相关的错误,那么对错误的处理是不需要磁盘访问的(我的理解是,以往的方法如果操作系统释放了内存资源的话,那么它如果再想使用刚才释放页上的资源就必须重新从磁盘上调入;而现在的last-chance,就给了操作系统一个机会,如果出现了和刚释放掉的页内容相关的错误,那么操作系统可以直接从这个LPC中调相关页,而不用访问磁盘)。
Xen的一个重要角色是作为XenoServer的基础。XenoServer的设计目标超越了单机的范畴,它要搭建的是支持一个互联网规模计算架构所必需的控制系统。对于我们的设计来说,关键在于资源的使用要被精确地计算并且由工作的发起者想办法满足资源需求 — 如果资源必须要及时兑现,我们就使用一个拥塞定价策略来处理那些超过资源提供能力的要求,使用透支的方法满足超出的需求。这就必须要有精确、及时的I/O调度,它要能够更有弹性地处理那些不友好(//恶意透支?)的工作负载。我们还计划创建虚拟块设备租借等形式,将会计学中的一些理论(//上述的租借,透支之类的概念都是属于会计学的范畴)借鉴进我们的块存储架构中。
为了能够为XenoServer的管理和经营提供更好的支持,我们正在加入对日志审核和日志鉴证更彻底的支持。我们还在开发其它的VFR规则,希望这些规则能够使我们检测和防止更大范围的对社会有危害的网络行为。最终,我们正在继续我们在XenoXP上的工作,最重要的工作就是编写网络和块设备驱动实例,工作的目标是完全支持企业级的服务器应用(如IIS)。
5.2结论
Xen提供了一个优秀的平台,在这个平台上能够配置广泛的多样化的以网络为中心的服务,比如动态web内容的局部镜像,媒体流的编码转换和分发,多用户游戏和虚拟现实服务器,还有为瞬时连接设备提供短暂网络连接的“智能代理”[2]服务。
Xen直接解决的是在部署服务时遇到的最大障碍,即当前不能够在低实例化开销的前提下,对瞬时服务器操控较短的时间(//瞬时服务器:时有时无,有时候需要有时候不需要;而即使是每次需要,也只是操作很短的时间,马上就又不需要了;所以这样的话,频频切换,就需要很大的实例化开销,因为每次启动瞬时服务,就要实例化一次;但是Xen中,反正我可以跑多个系统,那就专门留一个或几个系统给你跑瞬时服务,同时还不耽误我其它服务的性能)。通过允许100个操作系统运行在单台服务器上,我们减少了两个数量级的相关开销。更进一步的,我们可以把对每个操作系统进行设定和配置的过程转变为软件行为,这样就能够更容易地操控更细粒度的时间片。
正如我们在第4部分给出的实验结果,在Xen上运行XenoLinux的性能几乎与本地Linux系统的性能相同。之所以会有这样的结果,主要得益于对两个部件之间接口(//就是VMM吧?操作系统和底层硬件两个部分之间的接口)的细致设计,这使得我们几乎感觉不到在使用资源管理工具时带来的开销。我们的下一步工作是移植BSD和Windows
XP的内核到Xen上来验证Xen提供的接口的普适性。