NOTE:该篇主要内容来自Best Damn Server Virtualization Period Book
5.Xen简介
Xen是一个开源的VMM,或hypervisor,适用于32位或64位的处理器架构。它以一个在主操作系统上执行的软件的形式运行,使你能够在主操作系统上运行多个子操作系统,并在同一时间与其分享同样的计算机硬件。虚拟机以安全、高效、接近本地运行的性能运行。
Xen计划源于剑桥大学计算机实验室系统研究组的一个科研计划。这个计划被命名为Xenoserver计划,并由英国工程和物理研究委员会(Engineering and Physical Science Research Council, EPSRC)资助。这个项目的目标是提供一套全球都可以访问的公共基础设施,从而达到广域分布式计算的目的。由于整个研究组的对系统的特殊专注,在经验丰富的研究员Ian Pratt的带领下,这个项目最终产出了Xen虚拟层作为它们的核心技术。
Xen通过两步公布于世。首先,Pratt和其他几个最早对该项目有贡献的研究员于2003年10月,在两年一度的Symposium on Operating Systems Principles(SOSP)上发表了一篇名为“Xen and Art of Virtualization”的文章,描述了虚拟层和它如何在x86架构的CPU中实现虚拟化。与此同时,Xen的公共版本v1.0提供了下载。自此以后,Xen开始成长并成熟,在许多产品应用中起到了关键作用。Xen也同时是托管和“软件即服务”(Software-as-a-Service)模型另一种实现方式的基础技术。
Xen在开源社区中的开发现在由Ian Pratt创建,现在由XenSource开展。虽然XenSource的商业化企业级的解决方案是基于Xen技术的,它也对Xen社区的成长贡献良多,培养和鼓励开发者将虚拟层开发得更好,并将自己拥有的资源贡献到开发工作中去。表5.1罗列前15位对Xen代码库贡献最大的企业或个人。值得注意的是,大多数编码工作是由XenSource的工程师完成的,其次是Xen社区的贡献。在与XenSource有技术合作的合作者中,IBM和Intel从v3.0版本开始对项目贡献最多。
表5.1
从这张表中,可以发现一个有趣的现象——这些对Xen项目有贡献的企业从XenSource自身到x86系统和CPU架构领域的领头羊(尤其是那些在x86研究和开发商有最大预算的企业),再到主要Linux的发布者。政府和教育机构甚至都对Xen有所贡献。汇集了具备各种背景的贡献者,Xen最有可能满足你的组织的需求,不论你的组织规模的大小、预算的多少、是私人的还是公共的。
5.2 Xen的特征
Xen提供了一套强劲的企业级功能,使其对无论是运行着关键业务的应用程序大型应用还是中小企业都适用。这些特征包括:
- 接近本地服务器性能的虚拟机。
- 对x86(32位)、物理地址扩展的x86(Physical Address Extension,PAE)和x86(64位)的完全支持。
- 支持所有具有有效的Linux驱动的硬件。
- 支持每个子操作系统(Windows或Linux)多路虚拟CPU,最多可达32路。
- 通过虚拟CPU的热插拔进行动态资源分配(如果子操作系统支持的话)。
- 两台物理主机之间无中断地、实时地迁移运行着的虚拟机。
- 支持来自Intel(Intel-VT)和AMD(AMD-V)的硬件辅助处理器,从而允许子操作系统不需要修改即可运行在虚拟机上。
Xen支持两种模式的虚拟化:半虚拟化(paravirtualization)和完全虚拟化(full virtualization)。对于半虚拟化来说,子操作系统必须进行修改,其内核需要被重新编译从而能够支持与Xen VMM的合理交互。虽然这限制了可以在这种模式下运行的操作系统的选择,但也提供了性能方面的优势。完全虚拟化在硬件辅助处理器架构的支持下,在3.0版本中被引入。这种模式只在拥有Intel-VT或AMD-V处理器的物理主机上可以有效运行。完全虚拟化使未被修改的子操作系统可以运行,也是运行微软Windows子操作系统的唯一途径,虽然这种模式会在性能上有一定折扣。
5.3 XenServer系列产品
正如我们之前讨论的,有两种类型的Xen产品:开源的和商业产品。两种产品的特征大致相同,只不过是商业产品还附带了一个新的管理工具,并从整体上更加精致一些。尤其是XenSource的商业软件XenServer产品系列,在开源的Xen虚拟层上包含了许多周边的附加软件。你可以从这几方面收益:
- 易于使用 XenEnterprise、XenServer和XenExpress都以打包的形式提供,并支持虚拟化。它们分别都包括了3个元素,即Xen虚拟层、Linux和NetBSD的端口和用户模式下运行的管理工具。XenServer可以非常简单地通过CD启动或通过网络在裸机上进行安装。它利用Linux驱动来支持各种各样的网络和存储硬件。
- 健壮的虚拟化 一个高度可扩展的平台,XenEnterprise可以运行多达64个接口的支持多进程(Supporting Multiple Process,SMP)的系统,并在一个虚拟机中最多可支持32课虚拟CPU核心。这将带来超常的性能,尤其是在通过使用Intel和AMD最新处理器来利用硬件虚拟化的配置中。这种硬件虚拟化同时也使XenEnterprise可以运行Windows和Linux的虚拟机,并通过一个管理工具进行管理。
- 强劲的管理 用户可以再本地或远程运行快捷资源供应、性能监视和通过XenServer管理控制台实现的一些趋势分析、物理机向虚拟机(Physical-to-Virtualization, P2V)的迁移以及命令行用户空间工具这些功能,使XenEnterprise能够被有效的管理,并运行虚拟机。
表5.2显示了XenServer系列产品之间的一个对比,包括开源的版本。这些特征功能都来自于XenServer3.2版本和开源Xen3.0.4版本。
表5.2
5.4 Xen的虚拟化模型探索
虽然由于服务器虚拟化与运行在一个硬件环境(物理或虚拟的)下的操作系统有关,我们曾探讨过它,但是任何正在为他们的系统架构部署虚拟化技术的组织都在关注它如何投入应用。服务器平台上只托管应用程序,而应用程序和数据本身也是知识产权,实际上也为企业提供价值。如何实现企业应用程序组合的整合、优化、更高的可用性和更强的灵活性才是虚拟化真正诱人之所在。这些使得客户实现真正的商业价值,并从IT投资中获益。
5.4.1 架构概览
通过共享服务(shared-service)的形式,有很多方式能够实现在一台服务器上运行多种应用程序。最基础的方法在一个操作系统实例上托管多个应用程序的文件和进程。虽然利用传统的操作系统技术能够实现某种程度的进程之间的保护,这些进程也不像在专用的硬件平台上那样相互隔离。事实上,某个应用程序的错误不仅会影响在同个操作系统上执行的其他应用程序,它也将影响整个操作系统本身,使得对所有进程的恢复变得困难,甚至是不可能的,比如配置如何使特权模式的软件(设备驱动、操作系统API等等)达到精确的平衡,尤其是那些共享了公共库或二进制文件的软件。然而,比错误隔离更常见的是对性能隔离的需求。
共置和执行在同一个操作系统实例上的“成堆”的应用程序常见的一个问题就是资源的竞争。CPU的并发处理、内存或I/O的边界处理将使主机非常容易超额租用(oversubscribed)。每个进程都相信它们资源上量的积累,它们能够根据需要对这些资源进行使用并将试图进行使用。虽然在一些特定的操作系统中可以创建一些容器,来帮助确保所有进程都能获得它们所分配到的正确的资源,并没有更多可以使用,这种隔离技术没有显示出持续的有效性或可靠性。
通常情况下,企业级的操作系统,在运行最小数目的进程时,都有调度程序(scheduler)代理向操作系统提出资源请求。然而,当面向它自有的设备时,应用程序进程将不受系统中其他进程的影响,尽情地使用有效的资源。操作系统通过提供能够对进程资源利用进行细粒度管控制的资源管理器,来增加这种调度限制。然而,这些工具一般来说应用和管理起来比较复杂,并会在增加应用程序的过程中带来更大的管理开支。
虽然一个操作系统实例专用的物理服务器硬件提供了企业青睐的错误和性能的隔离,但这在成本上是不划算的。这个问题的真正解决方法是服务器虚拟化。正如在第一章中所解释的,现在有多种虚拟化技术可供使用,每种技术都有它们的优势和劣势。Xen已经利用半虚拟化(它最初的虚拟化技术)和完全虚拟化技术,开发出了适用于x86架构的可靠的、可用的虚拟机监视器。
5.4.2 处理器架构
Xen现在已经支持一系列硬件和操作系统的互补。其中一些计划正在成为适用于商品化硬件的商品化VMM,使你能够虚拟化常用的操作系统。虽然Xen原本是一个开发适用于x86架构的VMM的项目,它也为其他平台开发和测试了一些实验端口,如Intel的Itanium、Itanium2(IA-64架构)以及IBM的PowerPC。表5.3列举了它现在支持的CPU架构,以及在这些架构上支持的Xen的功能。
表5.3
对于3.0.0版本而言,Xen也支持Intel和AMD最新处理器提供的硬件虚拟化的功能,分别支持Intel-VT和AMD-V。对于Intel的处理器而言,这个功能被称为XVM,而AMD宣称这项功能为SVM。而在Xen的命名法则中,由于它们在功能上相同并为Xen提供了同样的帮助,它们都被简单地称为HVM。硬件虚拟化使Xen能够托管那些不能被修改的操作系统,并以传统的半虚拟化的形式运行。尽管Intel和AMD采用的方法是不同的,最终的结果都是对CPU和内存有效和高效的虚拟化,使Xen能够运行各种虚拟化的操作系统。甚至对HVM来说,特殊的半虚拟化设备驱动能够使HVM的子操作系统利用Xen的虚拟I/O架构,来达到效率、性能、安全和隔离的最大化。
5.4.3 Xen的半虚拟化
Xen对一个复杂的结构进行了一个精巧简单的设计。它的结构来源于解释x86架构CPU平台的特性如何适用于VMM或虚拟层的尝试。这个设计的其中一个目标就是尽可能地区分“如何做”、“何时做”和“做什么”(换句话说,就是制定规则和机制)。其他的x86 VMM的方案将所有的任务都交给虚拟层。虽然这提供了很大程度的灵活性和x86 VMM运行的成功,这也牺牲了虚拟层的性能。然而,Xen的开发者相信,虚拟层设计的最优途径是让虚拟层去解决一些低级的复杂性问题,如CPU调度和访问控制,而不是那些适合子操作系统环境自己解决的一些更高级别的事件。
作为这种结构设计方法的结果,Xen结构中的分界点变成了一个关于控制和管理的问题。虚拟层需要关注的是基本的控制操作控制(即机制,或“如何做”),而将决策制定(即规则,或“做什么”和“何时做”)交给子操作系统。这非常符合VMM的本质特性——即虚拟层只负责处理一些需要直接的特权访问的任务。从本质上来看,Xen模拟了一层与底层硬件平台特别相似的虚拟机抽象层,而不需要针对每个硬件创建特定的拷贝。这项技术使所谓的半虚拟化的核心。
为了实现这种技术,半虚拟化需要对子操作系统进行一系列的修改,从而使其能意识到底层的虚拟层,使其能够更好地与它所存在的虚拟化世界以及它所运行之上的物理世界实现更好的交互。虽然应用程序的二进制文件也不需要进行修改,对操作系统的修改有助于提高性能并更好地支持处理时间敏感(time-sensitive)的操作。半虚拟化对内存管理、CPU和设备I/O还是由一定的要求。如表5.4所示。
项目类型 |
项目 |
要求或特殊考虑因素 |
内存管理 |
分段(segmenting) |
不能插入特权的段描述符,并不能覆盖线性地址空间的顶端 |
分页(paging) |
子操作系统有权读取硬件层的分页表(page tables),但是是由虚拟层对更新进行批处理或单独执行以及验证。 |
|
CPU |
保护(protection) |
子操作系统所运行的层级必须比Xen所在的层具备更少的权限——也就是说,子操作系统不能运行在Ring-0。 |
异常(exception) |
子操作系统必须注册一个异常捕获处理器的表。 |
|
设备I/O |
系统调用(system calls) |
子操作系统会为系统调用指令安装一个处理器,允许一个应用程序或操作系统本身直接调用。其中一些调用指令不需要直接由Xen处理。 |
中断(interrupts) |
硬件中断被一个事件提示机制取代。 |
|
时间(timing) |
子操作系统必须意识到并能够处理实际时间(wall-clock time)和虚拟时间。 |
|
网络和磁盘(network and Disk) |
可以很容易访问虚拟设备。通过异步的I/O环和类似中断的通信传输到子操作系统上的数据将通过事件提示进行处理。 |
表5.4
5.4.4 Xen的“域”(domain)
图5.1展示了典型的、适用于x86架构的完全虚拟化VMM的系统结构。虽然与其他VMM在功能上类似,Xen有自己独特的系统结构。它将自身的系统结构拆分为底层硬件、VMM(或虚拟层)、控制区域和虚拟机,如图5.2中所示。虚拟层是一层包括了管理(Management)和虚拟硬件(Virtual Hardware )API的抽象层,包含了允许用户与底层硬件直接或间接交互的一个控制界面。与管理API进行交互的界面是一个有特权访问硬件并包含用户模式管理工具的控制区域。无论是控制区域还是虚拟机都被称为“域”。在启动的时候,系统会初始化一个特殊的域,这个域可以使用控制界面,被称为Domain-0(dom0)。这最早出现的域保管了用于管理Xen环境的用户模式管理软件,无论是命令行工具还还是XenSource的管理控制台。这个域还负责通过控制界面启动和停止其他低等级域类型,称为子域(guest domain),也被称为Domain-U(domU)。dom0还可以控制domU的CPU调度、内存分配和对设备的访问,例如物理磁盘存储和网络界面。一个domU也被称为一个Xen的虚拟机,或XenVM。
图5.1
图5.2
控制域dom0和标准的Linux系统一样运行操作。你可以运行用户模式的应用程序——例如那些管理Xen环境的软件——也可以安装一些用来支持硬件平台的设备驱动。由于具备编译和运行任何Linux驱动可支持的设备,Xen可以支持很多的硬件。这为IT组织在选择硬件网络和存储设备时可以有更大的灵活性,并可以使Xen运行在几乎所有的x86环境中。
注意:在dom0中运行尽可能少的进程。这个域是一个具有高权限的域。因此,任何由运行着的某一进程引发的不稳定性和破坏也将影响所有的子域。除此之外,dom0也共享了其他子域使用的物理硬件。dom0使用越多的资源,其他子域将获得更少的资源。
虚拟硬件API包含了一个控制界面,这个控制界面通过以下三个虚拟I/O设备对硬件设备的显示进行管理,包括创建和删除:
- 虚拟网络界面(VIFs)
- 虚拟防火墙和路由器(VFRs)
- 虚拟区块设备(VBDs)
每个虚拟I/O设备都有一个与其关联的访问控制列表(Access Control List,ACL)。与一个文件系统或网络资源的访问控制列表类似,这个列表包含了对设备具有访问权的domU的信息,同时也包括了允许访问的类型和一些限制——如只读、写入等等。
这个控制界面汇总了显示Xen系统中组件状态的数据,并提供给运行在dom0中的一系列用户模式管理工具。这些工具可以用来:
- 创建和删除domU
- 创建和删除虚拟I/O设备
- 监控网络活动
- 从一台Xen主机向另一台迁移运行着的虚拟机
- 监控整个系统或域级别的域的性能
更完整的管理工具列表,包括本地的和第三方的,我们将在后面的章节中具体讨论。
虚拟硬件API还负责管理Xen和运行其之上的domU交互信息的传递。它通过使用超级调用(hypercall)来实现这种管理。每个域向Xen进行同步调用,而事件机制(一个异步传输的机制)将提示从Xen发送到相应的域。
超级调用界面使子操作系统可以通过在虚拟层捕获指令来进行特权操作。这种方式类似于操作系统对底层硬件进行的系统调用,除非他们发生在Ring-1到Ring-0之间。这些受限于CPU和内存的指令将被虚拟层执行,而不是物理硬件资源。
相比之下,Xen与域之间通过一个异步事件机制进行通信,这和子操作系通过设备中断进行通信的方式相同。这个控制界面利用这个事件机制来发送其他重要事件,例如事件终止的要求。每个事件以一个标志(flag)的形式发送给特定类型的事件发生(occurence)。这个发生的事件用来确认一个域请求的完成,也可以用来提示一个域在运行环境下的状态的变化。虽然虚拟层负责更新每个域的挂起事件的队列,但是还是由域自身来负责响应事件提示、事件处理的延迟和队列的重设。这使得这个事件机制从虚拟层的角度看非常轻量化,并实现了我们之前所讨论过的目标,即让虚拟层与子操作系统分担事件的处理。
为了更好地理解Xen的结构,我们将进一步探究Xen的内部工作原理,包括虚拟机的抽象和端口。我们将思考Xen是如何虚拟化以下三个计算机元素的:
- CPU
- 内存管理
- I/O
5.4.5 CPU虚拟化
在x86的处理器架构上,CPU的虚拟化出现了一些挑战,尤其是让子操作系统“相信”它们直接运行在硬件的Ring-0上。虽然x86架构提供了4层权限环或层级,典型的操作系统都只用其中两层:Ring-0和Ring-3。在一个典型的适用于x86架构的VMM中,操作系统会与Ring-3上的应用程序共存。为了保护自身,操作系统将运行在一个完全不同的、特有的地址空间,间接地通过VMM向应用程序传递控制或接受来自应用程序的控制请求。这将导致一些隔离的低效率,从而影响性能和灵活性。
一个高效的解决方法就是通过使用没有使用过的权限层级来解决这个问题,即利用Ring-1和Ring-2。这些层级从IBM的OS/2起就没有被x86的操作系统使用过。从技术角度上说,任何遵守这个规则(即只使用Ring-0和Ring-3)的操作系统都可以运行在Xen上。这个操作系统将被进行一些修改,使其能够运行在Ring-1上。这种情况下,Xen还是能够为这个子操作系统提供与其他子域的隔离,对其提供保护,也避免了它执行Ring-0中的高权限指令。然而,这种方式却改善了其与用户模式中应用程序之间的隔离。
Xen通过在Xen中对指令的验证和执行进行授权来对一个子操作系统进行半虚拟化。如果一个子域尝试执行执行一条特权指令,这条指令将被虚拟层捕获并取消执行。然而,dom0中存在着一个扩展界面,允许其创建和启动其他的域。这个界面也使得dom0可以访问高级别的控制操作,例如CPU调度。Xen对CPU的虚拟化必须应对异常、CPU调度和时间。表5.5中列举了一些Xen中常用的、用来管理CPU虚拟化的超级调用(hypercall)指令。
分类 | 超级调用指令 | 描述 |
虚拟CPU设置 | set_callbacks | 为事件处理注册事件回调(callback)。 |
set_trap_table | 向每个域单独的指令捕获处理表中插入实体。 | |
vcpu_op | 为虚拟CPU提供管理。这条语句可以用来启动和停止虚拟CPU,从而测试它们现有的状态。 | |
CPU调度和计时 | sched_op_new | 通过虚拟层来请求计划一项特权操作。 |
set_timer_op | 用来对一个特定的系统时间请求计时事件。 | |
环境切换 | stack_switch | 用来从虚拟层中请求一个内核的栈开关。 |
fpu_taskswitch | 用来创建、存储和恢复一个浮点指针状态,使得子操作系统能够捕获这样的活动,并在一个单独的调用中存储或恢复这个状态。 | |
switch_vm86 | 使所有子操作系统能够在vm86模式中运行。 |
5.4.5.1 异常
Xen采用了一种独有的方法来处理异常。通过使用一种特殊的超级调用set_trap_table,每个域都能维护一个独有的、专门的存有指令捕获处理器的表。例如内存错误和操作系统指令捕获,都能通过使用在一个专门的异常栈框架中发现的指令处理器来解决,并只需要对子操作系统中的异常处理代码做很少的修改。因此,异常将通过两种方式来处理。第一种方式是去顺应子操作系统中的系统调用,并使用一个加速的异常处理器。这个处理器被每个子操作系统注册,并由处理器直接访问。虽然Xen还是要对捕获的特权指令进行验证,这些系统调用将被以一个接近本地的速度得以处理,而不需要在Ring-0中执行。换句话说,Xen事实上没有以子操作系统的身份护理这些系统调用。第二种方式是用来处理分页调度的错误。这类错误如果没有虚拟层的参与将是一个很难解甚至不可能处理的异常。用来处理系统调度的技术不能用于处理分页调度错误,因为Xen必须在Ring-0中处理分页调度错误。这类异常由Xen来处理,用来检索的注册值将被储存在Ring-1中的子操作系统中。
5.4.5.2 CPU调度
CPU调度是服务器虚拟化技术中的关键技术。这是虚拟层在底层CPU架构上实现虚拟CPU的方式。为了优化调度和达到接近本地的性能,调度机制必须高效并不浪费任何处理周期。这种类型的机制被称为连续工作机制(work-conserving)。在这种机制下,CPU资源不允许被闲置。只要有足够的资源来执行指令并有指令可被执行,连续工作机制将把捕获到得指令分配给物理CPU执行。如果工作量没有造成拥堵,这类机制将以一种简单的“先进先出”(First-in-First-out,FIFO)队列机制运行。然而,当这个处理队列变得拥堵时,这些指令将形成队列,并根据在调度机制设定的优先级高低和重量的大小来决定执行顺序。
相比之下,非连续工作队列机制将允许CPU资源有所空余。这种情况下,更快地执行指令相比执行必要的指令来说没有任何优势。底层的物理CPU资源也将由于设定一个执行指令必须达到的比率而被空置。在同一个虚拟层中将两种机制都整合在一起是有可能的。
Xen中的一个有效的CPU调度器是基于一个虚拟时间借用(Borrowed Virtual Time,BVT)的调度机制。这是一种混合的算法——它能支持连续工作,并有为低延迟的调度设计的机制;也支持域的唤醒(wake-up),如果一个域收到一个事件提示,它将被唤醒。后者在一个虚拟层中很重要,它能够最小化虚拟化对以一种及时的方式运行的底层操作系统的影响。BVT通过使用虚拟时间的扭曲来实现自身低延迟的特点。这种机制打破了公平调度规则,并在一个事件到来时给予相应域的唤醒一个更早的、低延迟的帮助。Xen提供了两种调度机制——简单最早截止日期优先(Simple Earliest Deadline First,sEDF)和信用调度器(Credit scheduler),并允许你通过使用统一的CPU调度器的API,来实现一个你自己拥有的、常用的机制。信用调度器是为支持多进程处理平台优化的,是一个比较好的选择。基于信用的调度因为sEDF机制在Xen未来的版本中被放弃而存活了下来。BVT就是信用调度器。
通过使用在dom0中运行的用户模式管理工具,每个域的调度参数都被管理了起来。虽然典型的、运行在支持多进程处理的主机上的信用调度器,将以一种连续工作的方式动态地在物理CPU之间移动,从而最大化域和系统处理器的产出,虚拟CPU也可以被限制运行在托管的物理主机的子集上,称为锁定(pinning)。例如,你想用一个特定的子域来运行应用程序服务器,这个子域只运行在4个CPU上的CPU-2和CPU-3上。即使CPU0和CPU1处在空闲状态,它们也不将为这个域执行指令——因此,这就是混合调度模型的非连续工作能力。
为了查看所有域上现在的sEDF调度的设置,需要运行以下指令,不需要任何参数:
xm sched-sedf
为了查看基于信用的调度器的设置,你可以执行xm sched-credit,并加上一个-d的选项,表示目标域(比如“1”),如下:
xm sched-credit-d 1
输出的内容将显示限制以及相对的重量(默认值为256),如下所示。注意,这个指令将给你一个域的值,而不是所有域整体的情况,就像xm sched-sedf这条指令一样。
{'cap':0,'weight':256}
提示:
Xen提供了一个命令行界面来管理调度器。第一个例子将给出为一个特定的子域定制sDEF调度器的语义表达,而第二个将给出一个定制信用调度器的语义表达。
例子1:sched-sedf
例子2:xm sched-credit –d
5.4.5.3 时间
在一个虚拟设施中,计时变得至关重要,并对子操作系统而言是容易混淆的。他们需要知道真实时间和虚拟时间。真实时间,又称为“挂钟”时间,是被虚拟层管理的;然而,虚拟时间对子操作系统和对时间敏感的调度操作更加重要。在讨论Xen中的时间时,这里有几个概念需要考虑。以下列举了这些概念,以及相应的简短描述:
- 系统时间 一个64位的、精确到纳秒的计数器从系统启动开始就开始进行计时。在开始计时的那一天,一个子操作系统的时间表示了从这个域被创建或启动起来的时间。
- “挂钟”时间 这个是dom0所关注的时间,也由dom0来维护。这个时间精确到秒和毫秒,从1970年1月1日开始,能够自动为闰年进行调整。作为一个关键的参考时间,尽可能保持这个时间的精确非常重要。这样做的一种方法是在dom0中使用一个网络时间协议(Network Time Protocol, NTP)客户端。
- 域虚拟时间 这个时间与系统时间类似,不过这个时间从一个域被安排后开始计时,将在安排取消后停止计时。虚拟时间是重要的(在前一章关于CPU调度的部分中提到过),因为一个域能够获得的CPU资源取决于其虚拟时间增长的比率。
- 循环计数器时间 这个计时器是用来提供细粒度的时间参照。循环计数器时间是用来推断其他计时器或时间参照的时间的。每个物理CPU之间这个时间的同步是非常重要的。
Xen为系统时间和挂钟时间维护时间戳,并通过Xen和每个域之间共享的内存分页来输出这些时间戳。Xen在计算每个时间戳和以Hz为单位的CPU频率时提供循环计数器时间。如果不进行推断,时间参照将变得不精确,因为在共享分页中维护的那个时间的值将会是过时的。另外,这些时间戳都有它们相应的版本号。这个版本号在每个时间戳的每次更新时都会增加两次。第一次,它在更新时间戳之前增加,接着在更新成功后增加第二次。子操作系统将对比两个版本号,来确定两个数字是不是匹配。如果它们匹配,子操作系统会认为时间读数是在一个连续的状态。如果不匹配,子操作系统将知道时间戳的更新正在进行,它将在更新结束后回来继续读这个时间的值。
除了真实时间和虚拟时间,Xen整合了一些计时器来保持子操作系统中时间的精确。Xen使用了两种类型的计时器。第一种是周期性计时器,它会每10毫秒向正在运行的(醒着的)域发送计时器事件。Xen的调度器也会发送一个计时器事件给一个被安排的域。子操作系统利用这些事件,在它不活动的时候对自身时间进行调整。第二种是每个域使用set_timer_op的超级调用指令特意安排的计时器。子操作系统可以使用这个计时器去实现超时(timeout)的概念。例如,如果一个子操作系统使用一个超级调用安排了一项定时的事件去写入虚拟内存,但当这个事件被收到后子操作系统没有收到成功的提示,这时,子操作系统就可以使这个操作因超时而取消。
5.4.6 内存虚拟化
内存虚拟化可能是VMM需要执行的最困难的任务。Xen对负责管理给分配给每个域的物理内存,以及保证硬件分页调度和分割的安全使用。由于几个域共享了同一个内存,必须要注意保持域的隔离。虚拟层必须保证两个非特权域(或domU)能够访问同一个内存范围。每个分页或目录表的更新必须通过虚拟层来验证,以保证这些域只操纵它们自己的表。这些域将通过连续的更新来批处理这些操作,以使它们更加具有效率。每个分割也以一种类似的方式进行虚拟化,将虚拟层作为一个“门卫”的角色,来保证这些域的分割在某种情况下不会重叠或无效。
与CPU虚拟化相似,Xen的子操作系统必须捕获任何要求Ring-0访问并用超级调用命令传输给虚拟层的特权系统调用指令。表5.6显示了用于管理内存虚拟化的主要超级调用指令。为了更好地理解Xen如何管理内存,我们将讨论内存分配、内存地址翻译、分页表和分割。
种类 | 超级调用指令 | 描述 |
分页表管理 | nmu_update | 更新一个域的分页表,通常是使用批处理的方式 |
update_va_mapping | 用来为一个特定的虚拟内存地址更新分页表项(page table entry,PTE),而不是通过批处理的方式 | |
mmuext_op | 用来执行扩展的内存操作指令,例如缓存清空;或者重新分配分页的占有权。 | |
vm_assist | 用于在多种内存管理模式之间进行切换。 | |
虚拟内存翻译 | set_gdt | 用来为一个域创建一个新的全局描述符表(global descriptor table,GDT) |
update_descriptor | 用来更新全局或本地描述符表(GDT/LDT) |
表5.6
5.4.6.1 内存分配
正如我们所讨论的,除了分配一部分物理系统的RAM以为私用,Xen也保存了每个虚拟地址空间的一小部分。这种分配依赖于底层硬件,如图5.3和5.4所示。
注意:虽然x86架构在物理地址扩展(PAE)模式下最多可以支持到64GB的内存,在x64的模式中可以支持多达8TB的内存,但只有XenEnterprise可以在这种系统中充分利用RAM的量。XenServer和XenExpress只分别限制使用8GB和4GB的内存。
图5.3
图5.4
在32位的、支持和不支持物理地址扩展的x86架构中,分割是为了保护虚拟层不被操作系统内核影响。在两种模式中,内存的保留量分别是64MB或168MB。这些保留的区域将不影响系统调用指令服务的速度。相比之下,x64架构在给Xen分配内存保留量时十分慷慨。然而,在这种架构中,分割界线之间不像x86架构中没有任何保护。这要求Xen采用页面级(Page-level)的保护策略,从而在内核内存操作指令中保护虚拟层。
当聚焦到子域的物理内存分配上时,每个域有一个物理内存分配的最大值和当前消耗量。Xen为每个域都单独使用了一种“气球驱动(balloon driver)”的概念,使得操作系统将它当前分配到的内存调整到所配置的最大值限制。这使得“闲置”的内存分配可以供其他区域使用,可能会考虑到稳定的内存资源过度使用。由于这种持续变化的内存分配,加上域的创建和终止,内存将在页面级的粒度下被动态地分配和释放。Xen不能保证一个域将能持续地得到物理内存的分配供以使用。虽然这打破了大多数兼容x86架构操作系统的原则,Xen通过使用相对于物理机的“伪”物理内存或真正的内存来弥补。虚拟机内存涉及到物理主机中的RAM总量,包括任何为Xen保留的量。机器内存无论是分配给Xen的还是给子域的机器内存都是由4kB的帧组成的,并以一种线性的形式计算,而不顾内存的分配。“伪”物理内存是每个域基础上的那些帧的抽象。
这就是Xen如何“欺骗”每个域,使它们以为它们有一大片的、连续的、总是从第0帧开始的内存。由于机器内存的帧总是在忽略帧的标号,“伪”物理内存要持续代表一个连续标号的、连续的页面空间。它的实现不必使用任何“仙尘”技术(pixie dust),只需要使用两个表。第一个表要对虚拟层和所有域和映射中要映射到“伪”物理帧(machine-to-physical)的帧可读。第二个表被独立提供给每个域,并以一种相反的方式映射,从“伪”物理帧映射到虚拟机的帧(physical-to-machine)。使用这两个表,这个域将能够提供一个抽象层来运行甚至是最挑剔的操作系统。
5.4.6.2 分页表和分段
在x86的架构中,内存地址被分为三种——逻辑、线性和物理。一个逻辑地址一个可能与物理地址位置直接或不直接关联的存储位置。逻辑地址通常在向一个控制器请求信息时而被使用。一个线性地址(或一个平面地址空间)是一个从0开始计数的内存地址空间。每一个连续的字节都是下一个字节编号的依据(0、1、2、3……),一直到内存地址的尽头。这是大多数非x86的CPU如何对内存寻址的方式。x86架构使用的是一个分段的地址空间,内存被分成许多64KB的段,而分段注册表始终指向当前所在的段的基地址。32位模式在这种架构中被当做一个平面地址空间,但它也使用了分段。一个物理地址是一个在物理地址总线上用位来表示的地址。物理地址可能与逻辑地址有区别,内存管理单元会将逻辑地址翻译成物理地址。
CPU利用两个单元来将逻辑地址转化为物理地址。第一个称作分段单元,另一个称为分页单元,如图5.5所示。
图5.5
分段控制单元模型背后的基本含义就是,用一套分段来管理内存。从根本上说,每一个分段都是内存自己的地址空间。一个分段由两部分组成,基址包括了一些物理存储位置的地址和表示段长度的段长值。每个分段都是一个16位的字段,称为段标识符或段选择符。x86硬件由一些可编写的注册表组成,这些注册表称为分段注册表,它保存了这些分段选择符。这些注册表是代码段(code segment,CS)、数据段寄存器(data segment,DS)和栈段寄存器(stack segment, SS)。每个段标识符用一个64位(8字节)的段描述符来表示一个分段。这些段描述符被保存在一个全局描述符表(global descriptor table,GDT)中,也可以被储存在一个局部描述符表(local descriptor table ,LDT)中。每次一个段选择符都被读取到段注册表上,相应的段描述符被从内存中读取到匹配的CPU注册表上。每个段描述符都有8个字节的长度,并表示内存中的一个分段。它们都被存储在局部或全局描述符表中。每个分段描述符实体包括了一个指向用基字段表示的相关分段中第一个字节的指针,以及表示该分段在内存中长度的一个20位的值(界线字段)。
分页单元,从另一方面,将线性地址翻译成物理地址,如图5.5中所示。一组线性地址汇集在一起组成了分页。这些线性地址从本质上说是相邻的——分页单元将这些相邻内存组映射到一个相应的互相邻近物理内存地址组,称为页帧。注意,分页单元使RAM看起来是被划分为许多固定大小的页帧。
这种将分页映射到页帧的数据结构被称为一个分页表。这些分页表被存储在主内存当中,并由操作系统内核在启动分页控制单元之前合理地初始化。
将线性地址翻译成它们相应的物理存储位置是一个两步的过程。第一步先将地址空间从一个称为分页目录的翻译表转换成一个分页表;第二步将地址空间从分页表转换成要求的页帧。
那这些对Xen来说意味着什么呢?
相比分段单元,Xen更多地使用的是分页单元。每一个段描述符都使用使用同一组地址来进行线性寻址,从而尽量减少使用分段单元来将逻辑地址转换成线性地址的需要。通过更多地使用分页单元,Xen极大地促进了虚拟层和子操作系统的高效内存管理。
回到我们关于内存虚拟化的讨论,你会回想起来Xen虚拟层在这方面只有一个角色,就是通过超级调用来验证分页表的更新,以保证安全和隔离。为了帮助这个验证过程,Xen在每一个物理页帧上关联了一个类型和一个引用标识。一个帧无论如何都会有以下类型中的任何一个(并且只有一个)类型:
- 分页目录
- 分页表
- 局部描述符表
- 全局描述符表
- 可写性
由于Xen只关注对“写入”操作的管理,子操作系统可以映射和访问它们自己任何的页帧,而不受虚拟层的干扰,也不用考虑帧的类型。然而,一个帧不能使用mmu_update的超级调用语句来重新分配到其他用途上,直到它的引用标识为0。帧也可以在虚拟层完成对一个实体的验证后绑定在一个分页目录或分页表上,使得子操作系统能够控制帧的分配,来进行分页表相关的操作。通过这个机制,Xen可以使子操作系统认为自己可以直接写入内存分页。当一个半虚拟化的子操作系统想要写入一个分页目录或分页表的内存分页中时,它可以同时使用vm_assist和mmu_update的超级调用指令来请求使该内存分页可写。然后Xen允许这个写入操作顺利执行,但捕获它。这个分页将暂时地从分页表中删除,直到Xen对其进行验证,然后重新连接。但是必须重申的是,被绑定的帧不能够被分配到其他帧类型中去,直到它们被解除绑定,并且引用标识为0.
然而这种级别的与虚拟层的交互,从资源的角度来看显得过于“昂贵”。通过批处理的方式——而不是每次操作执行一次超级调用指令的方式来执行一系列超级调用指令,将保持与虚拟层交互的效率。这可以通过使用multicall这个超级调用指令来实现。使用这种技术可以高度优化超级调用密集的行为,比如关联转换——它将同时向Xen虚拟层中的CPU和内存虚拟化组件请求调用。
5.4.6.3 虚拟地址转换
内存的管理直接受分页表和转换后备缓冲区(Translation Lookaside Buffer,TLB)的影响。分页表是计算机系统用于存储虚拟地址和物理地址之间映射的数据结构。TLB是CPU中用于提高虚拟内存转换速度的一个高速缓存。它拥有固定数目的实体,每个实体都包含将虚拟内存转换为物理内存的分页表的一部分。
TLB是一个典型的与存储器相联的内存。它将虚拟内存地址当做一个密钥,这个密钥被当做搜索内容输入并返回物理地址作为搜索的输出。如果搜索返回了一个与虚拟内存匹配的物理地址——换句话说,如果转换成功了——这个物理地址就用于访问内存。如果虚拟地址在转换后备缓冲区中无法被找到,转换过程就会通过另一种途径在分页表中寻找物理地址。这通常是一个较长时间的操作,从资源的角度说是比较低效率的。如果这个转换表被转出到类似于硬盘上的分页或转出文件这种备用存储上的话,这个转换过程将更加漫长。一些处理器架构提供了一种软件管理的TLB,尤其在RISC架构中,例如SPARC、Alpha和MIPS中。但是,x86的CPU架构中没有软件管理的TLB,还是依靠硬件来支持TLB请求和处理TLB丢失。我们将在下面解释什么是TLB丢失。
由于没有软件管理的TLB,这就为类似于Xen这样的、以x86架构为标的的VMM带来了挑战。为了实现最高的内存性能,所有有效的转换必须要出现在硬件可访问的分页表中。x86架构没有为TLB打标签(这是一个表示映射是否被使用的处理)。地址空间切换(虚拟层和域的地址空间)需要对TLB进行完全的清洗。把这些任务交给虚拟层或控制域(dom0)将会非常低效率。然而,Xen的开发者们选择用两种方式解决这个问题。第一种方法,运用与解决其他虚拟化难题一样的方式,让子操作系统负责分配和管理硬件分页表,从而减轻虚拟层的负担。虚拟层的角色又回到了为保证安全和隔离而进行分页表更新验证的角色。第二种方法,Xen划出一个专用的内存范围供自身使用,以避免在进入和离开虚拟层的时候发生TLB的清洗。本章前面“内存分配”的部分更加详细地讨论了这个问题。
什么是TLB丢失?
当一个TLB丢失发生时,在现代CPU架构中通常有两种方案。对于x86架构这种硬件TLB管理而言,CPU自身将遍历分页表,以查看是否有与特定虚拟地址匹配的有效分页表实体。如果这个实体存在,它将被读取到TLB中,并重试对TLB的访问。这次访问将成功,程序也可以正常得运行。如果CPU没有找到有与虚拟地址匹配的有效实体,它将产生一个分页错误异常,这个异常操作系统必须处理。处理分页错误通常需要将请求数据读取到物理内存中,设置一个分页表实体将出错的虚拟地址映射到正确的物理地址上,然后重新启动程序。
对软件管理的TLB来说,TLB丢失会产生一个TLB丢失异常,操作系统必须遍历分页表并在软件中进行转换。之后,操作系统会将转换结果读取到TLB中并从发生TLB丢失那条指令开始重新启动程序。和硬件管理的TLB类似,如果操作系统在分页表中没有发现有效的转换,一个分页错误会发生,于是操作系统必须处理这个错误。
为了展示TLB丢失在x86架构中对性能的影响,假设通常情况下需要0.5到1个时钟周期在TLB中发现一个虚拟地址。如果一个TLB丢失发生,且CPU凭借遍历分页表来找到虚拟内存,这个TLB丢失将带来10到30个时钟周期的损失。如果一个系统中TLB成功转换地址平均需要1个时钟周期,一个丢失将花费30个周期。而如果TLB丢失的发生率是10%,那一个有效的内存访问周期比率将达到平均每次内存访问需要3.9个时钟周期,是原来需要周期数的四倍。
将这个问题与CPU选择地问题关联起来,一个有较大高速缓存的CPU将得到显著的性能提升。甚至对x86和x64平台的多核架构来说,一个多核共享的较大的缓存比每个核单独使用一个缓存好。例如,一个主频3.0GHz、每个核有一个2MB的L2缓存的双核Intel Xeon 5050处理器,性能比主频1.6GHz、共享4MB的L2缓存的Intel Xeon 5110处理器差。虽然这不是CPU性能提升的唯一原因,但缓存模型确实对CPU性能有影响。由于两个核心都能够负载均衡地去执行操作,它们在共享的TLB中将获得更好地机会来找到虚拟地址,并减少分页表遍历的次数。
分页表的创建是由每个子操作系统来管理的。当一个新的分页表被创建起来时,子操作系统将从自己所拥有的内存地址中为其分配地址,并在初始化后将其在Xen上注册。虽然子操作系统运行在自己的内存空间中,子操作系统给予我们直接写入虚拟层的特权,而虚拟层通过超级调用验证所有从子操作系统传过来的分页表更新。Xen不能保证这个分页表内存是相邻的,虽然子操作系统认为它们是相邻的。事实上,许多情况下它们是不相邻的。除此之外,Xen保留下来的内存地址是不能够被任何子操作系统当做分页表内存进行映射的。
最后,Xen支持映像页表的使用。当使用这项技术的时候,Xen将为子操作系统分配和初始化的分页表创建相同的副本。子操作系统对它保有的那个分页表版本进行读写,而Xen实施的将这些改变复制到副本中去。如果一个域发生了分页表更新的错误,Xen将会向子操作系统发送一个事件提示来处理这个错误,或者在更严重的情况下,将终止这个域。无论用哪种方法,dom0和其他子域的安全性和独立性还是可以保持的。这项技术对那些CPU和内存操作不能被半虚拟化的操作系统非常有用,比如Windows。这项技术还能用于优化半虚拟化的子操作系统实现实时虚拟机迁移的过程。
5.4.7 I/O虚拟化
分类
|
超级调用指令
|
描述
|
事件通道
|
event_channel_op
|
用于对事件通道和端口的所有操作
|
授权表
|
grant_table_op
|
用于管理映射/取消映射、设置、放弃或传输指令
|
I/O配置
|
physdev_op
|
用于设置和请求IRQ配置详情和其他PCI BIOS操作
|
I/O数据在子域之间传输,同时Xen也使用共享内存的、异步修饰符的环。我们将简短地讨论这个问题,但是注意这些“环”是当在系统中传递缓存信息时提供与Xen通信的高性能机制的关键,而且同时为虚拟层提供了一种高效验证检查的方式。
除了让读者对硬件创新中的一些有技巧的、很有前进的技术有一个进一步的认识,本章将对多种进行有效的I/O半虚拟化必需的组件进行分类。这包括以下部分:
- 设备I/O环
- 事件通道
- 虚拟I/O设备和分裂驱动程序(Split Device Driver)
- 驱动域
- 软硬件管理单元(IOMMU)
5.4.7.1 设备I/O环
由于虚拟层的角色就是提供一个虚拟层同时保护子操作系统并将其与I/O设备隔离开来,采用一种以很小的系统资源消耗来快速传输数据的数据传输方式是非常重要的。两个因素决定了Xen中使用机制的设计:资源管理和事件提示。Xen的开发者试图在一个设备收到一个中断这个事件发生的时,减少Xen处理相关数据的必要工作量,从而提供一个级别的资源可说明性。这是通过以下步骤实现的:
- 以能够正确地解释相应的、受益的子域所消耗的处理时间的管理级别来管理异步缓存区。
- 通过要求子操作系统分配自己内存的一部分给设备I/O来减少I/O缓冲区之间的串话干扰,而不是使用共享的缓存池。
- 与之间讨论过的内存管理过程相似,使用虚拟层绑定底层页帧,从而在数据传输过程中保护数据。
I/O环的结构内部是一些入站队列和出站队列。这些队列不包含任何真实数据,而是作为一些指针指向子操作系统根据I/O修饰符所分配的I/O缓存。与这些队列共同工作的是很多对生产者(producer)和消费者(consumer)。每个域都有它们自己的生产者和消费者,用来访问I/O环;同时,Xen虚拟层也维护了一对用来与各个域通信的、共享的生产者和消费者。
让我们一步步地演示,以更好地理解这个机制是怎么运行的。这个过程已经在图5.6中展示出来。在我们演示的过程中,我们把整个过程想象成一个在长方形“环”之外、域和虚拟层之间旋转的一个圆桌面将会有助于我们的理解。虽然这个“圆桌”像一个按设定方向旋转的转轮一样,域和虚拟层的位置是保持不变的。当一个域想要使用一个I/O设备时,它在请求队列(旋转的转轮)中插入一个请求,这被称为“请求生产者”。这个请求会创建或更新一个在各个域和虚拟层之间全局共享的请求指针。然后Xen会在处理这个请求时删除这些请求,这被称为“请求消费者”,并建立一个Xen私有的、相关的请求消费者指针。对这些请求的响应也被放在这个“旋转的转轮”上的响应队列中。Xen在“环”中插入每个被称为“响应生产者”的响应,并为响应创建或更新一个共享指针。相应的子操作系统会取走它需要的响应,并创建或更新它私有的响应指针,被称为“响应消费者”。
图5.6
由于I/O环是异步传输的,传输顺序并不是必要的或重要的。每一个请求都有一个与之关联的、唯一的标识符,这是Xen用来标记相应响应的。这种这个机制异步传输的本质有一些优势。首先,Xen在与物理设备交互的过程中,能够高效地重新排列I/O的顺序,从而考虑到调度和优先级的因素。这对成组设备(block device)尤其有效,比如大容量存储驱动列阵。第二,当提出一个请求的时候,不需要发送正式的提示或事件。Xen不会主动接收一个域中的新请求,除非这个子域调用超级调用指令要求这样做。这使得每个子域能够将同种类型的一系列I/O处理请求排列起来,通过发送一次超级调用指令一次性向Xen发送这些请求,使Xen能够以更高效的方式接收和处理这些请求。这对于网络流量而言尤其有效。对于响应而言也同理。BVT快速传输的特性使得一个子域能够在一次单独的处理中被唤醒并检索一批相关响应,而不是为每次响应都消耗一定的时间周期进行接收。这个过程的高效性足以满足时间密集型的操作,例如一些子域中的TCP流量操作。
然而在一些情况下,子域可能不希望发生操作顺序的重新排列。这种情况下,子域可以清楚地向下传递重排障碍,以防止虚拟层通过批处理重排请求。这个障碍防止管理I/O操作执行的“升降调度器”执行循环,从而在域的批处理中按顺序执行操作。这对于一些成组设备操作而言非常重要,比如预写日志,其数据的完整性取决于数据到达的顺序。
5.4.7.2 事件通道
我们之前提到了Xen虚拟层用于与子域通信的事件提示机制。这种机制的根本就是事件通道。事件通道是Xen模拟的硬件中断这类事件传输的介质。“位翻转”事件的典型事件就是一个比特的值变成了1,从而引发了所发生的事件。在虚拟层和子域之间的反向通信中,事件提示是通过上行调用指令(超级调用的反向操作)从Xen中获得的。