Xen和虚拟化技术(三)
2005-08-02 02:13:57 来源:WEB开发网核心提示:3. DETAILED DESIGN — 细节设计在这一部分,我们介绍构成基于Xen的服务器的各个主要子部分的设计细节,Xen和虚拟化技术(三),在各个设计中,我们对Xen和guest OS的功能作了清楚的说明,直到传输完毕后再发起一次中断,很显然,当前的关于guest OS的讨论主要集中于XenoLinux,这主要是
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方式效率高。
在这一部分,我们介绍构成基于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方式效率高。
赞助商链接