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)。同