虚拟机(VM)是一种执行环境,它为软件呈现一台计算机或抽象机,无需软件直接与底层硬件交互。系统虚拟机提供运行整个操作系统所需的资源,而进程虚拟机则为运行单个程序提供环境。这两种用途都遵循通过软件(通常辅以硬件支持)实现机器接口的原则,但在范围和目的上有所不同。
系统虚拟机允许在虚拟机监视器(也称管理程序,hypervisor)的控制下,让多个操作系统共享一台物理计算机。它们支持工作负载隔离、硬件整合以及计算环境的迁移或复制,因而成为数据中心和云计算的重要基石。进程虚拟机(包括 Java 虚拟机等语言运行时)支持跨平台程序执行,并可提供自动内存管理和运行时验证等服务。
虚拟机技术源于 20 世纪 60 年代的大型机分时系统和实验性操作系统,现已发展为广泛应用的基础设施与编程语言技术。其设计涉及兼容性、性能、资源共享与安全性之间的权衡。要理解这些权衡,需要将虚拟机与硬件模拟器、操作系统容器以及其他形式的软件隔离技术区分开来。
机器接口与分类
虚拟机(VM)是一种实现机器接口的执行环境;所谓机器接口,即软件用以操作计算机器的一组规范化指令、资源和操作。面向该接口的软件无须直接控制相应的物理资源即可执行。执行环境与其实现(如虚拟机监视器或语言运行时)、配置(如分配的处理器数量与内存大小)以及存储镜像(如包含可启动磁盘的文件)各不相同。同一种实现可以运行多个虚拟机实例,一个磁盘镜像也可以为多个实例提供初始内容。镜像本身并非正在执行的机器,且磁盘镜像也不必包含恢复挂起实例所需的内存和处理器状态。
系统虚拟机提供足以运行客户机操作系统的硬件级环境。它提供所选平台所需的虚拟处理器、内存以及设备和启动接口。例如,配置了 4 个虚拟处理器和 8 GiB 内存的虚拟机向其客户机呈现这些资源;该配置并不一定独占保留 4 个物理处理器核心或 8 GiB 的专用物理内存。客户机是在虚拟环境中执行的软件,尤指其操作系统和应用程序。宿主机是提供虚拟化设施的环境;根据设计不同,宿主机可以指宿主操作系统,也可以指直接运行在硬件上的虚拟机监视器。物理机则是底层的计算机,包括其实际的处理器、内存和外围设备。系统虚拟机能够引导启动、运行多个应用程序,并独立于任何单个客户机应用程序保持运行状态。
进程虚拟机(亦称应用虚拟机)主要为程序提供执行环境,而非提供完整的客户机操作系统。其接口既可能类似于操作系统的进程环境,也可能是为编译型语言指令所定义的抽象机。例如,Java 虚拟机(JVM)定义了类文件结构、指令集和执行规则,各实现则在不同的宿主平台上对其提供支持。单个 JVM 实例可以在不提供独立客户机内核的情况下执行多个类和线程。这类环境通常是为执行某一应用程序而创建,并在该程序执行结束时终止;而系统虚拟机的生命周期则遵循客户机的启动与关机周期。这一区别在于所呈现的抽象层级,而不仅仅取决于运行的应用程序或线程数量。
这些分类可以通过所实现的接口边界来理解。指令集架构(ISA,如 x86-64 或 AArch64)定义了处理器指令和架构状态;其系统级机制还支持中断处理和特权执行等操作系统功能。应用二进制接口(ABI)则规范了二进制约定,包括寄存器使用、调用约定、数据布局以及与操作系统服务的交互。因此,支持现有原生应用程序二进制代码的环境,可能需要在不启动整个客户机操作系统的前提下,同时复现用户级指令和相关的操作系统接口。语言级指令集构成了另一层边界:例如,Java 字节码描述的是对抽象运行时值的操作,而非针对特定物理处理器的指令。指令究竟是被解释执行、翻译执行还是直接执行,仅属于实现方式的选择,而非界定这些分类的根本标准。
仅凭虚拟内存并不构成虚拟机。虚拟内存将软件使用的地址映射到底层后备内存,并可为每个进程提供独立的地址空间,但它本身并不能实现独立机器执行环境所需的处理器、设备、操作系统或语言级接口。使用虚拟内存的普通进程依然在操作系统的原生进程模型下执行。反之,虚拟机在内部使用虚拟内存的同时,还能对外提供更为广泛的机器接口。
历史发展
系统虚拟化于20世纪60年代在IBM公司剑桥科学中心作为分时系统研究的一部分发展起来;分时技术使多名用户能够与一台昂贵的计算机交互,而不再局限于按顺序提交批处理作业。1964年,在罗伯特·克里西(Robert Creasy)的领导下,实验性系统 CP-40 的研发工作启动。CP-40 运行在经过改造的 System/360 40 型机上,提供了多个相互隔离的虚拟机,每台虚拟机都提供了一个可供操作系统运行的接口。其控制程序(Control Program)负责分配物理资源,而剑桥监视系统(Cambridge Monitor System)则在虚拟机内部提供交互式的单用户环境。这种设计将共享物理计算机的问题与为每位用户提供编程环境的问题分离开来。
CP-40 于1967年投入运行。其后继系统 CP-67 将这种方法应用到了 IBM System/360 67 型机上,该机型的动态地址转换硬件支持虚拟机运行所需的内存映射。CP-67 于1967年投入使用,CP-67/CMS 则于1968年得到了更广泛的分发。与把所有用户置于单一操作系统实例中的分时系统不同,CP-67 可以并发运行独立的操作系统,包括正在测试或修改的系统。交互式计算与操作系统的开发由此相互促进:开发人员可以在单个虚拟机内进行实验,而无需独占整台物理大型机。
IBM 于1972年面向 System/370 系列发布了 VM/370,确立了虚拟化作为受支持的大型机商用产品、而不仅是研究设施的地位。其两大核心组件延续了早期的职责划分:控制程序(CP)管理虚拟处理器、内存和外设;会话监视系统(Conversational Monitor System,简称 CMS)在各个虚拟机内提供交互式操作环境。CMS 保留了早期剑桥监视系统的英文缩写,但更改了全称的含义。用户可以在独立的 CMS 实例中工作,而其他客户机则运行不同的操作系统。CP/CMS 模型证明了机器兼容性、资源共享与交互式使用可以兼得,其架构体系在后来的 IBM VM 系列产品中得以延续。
另一条并行的发展路线则利用抽象机器来实现程序的可移植性,而非复制一台完整的物理计算机。20世纪70年代中期,肯尼斯·鲍尔斯(Kenneth Bowles)在加利福尼亚大学圣迭戈分校领导开发了 UCSD Pascal 系统。UCSD p-System 将程序编译为 p-code(伪代码),再由中间机器的某种实现在不同的计算机上执行。昇阳电脑(Sun Microsystems)于1995年推出的 Java 平台将类似的便携模型推广给了更广泛的用户群体:Java 编译器生成包含Java虚拟机指令的 class 文件,由特定于平台的运行时环境负责执行。微软的通用语言基础架构(CLI)为多种编程语言标准化了类似的基础;其首个 ECMA 标准于2001年发布,随后于2002年推出了 .NET Framework。这些系统使中间指令集及相关的运行时服务成为应用程序部署的核心,不过这些抽象机器面向的是程序本身,而非完整的客体操作系统。
随着几项关键技术的发展,虚拟化扩展到了通用的 x86 计算机上。VMware 于1999年发布了 VMware Workstation,使桌面计算机能够运行多个操作系统。早期的 x86 处理器给直接虚拟化带来了阻碍,因为某些敏感指令在非特权级别下执行时不会触发陷阱;VMware 结合了直接执行与二进制翻译技术,以处理这些存在问题的操作系统代码。剑桥大学研究团队于2003年发布的 Xen 最初主打半虚拟化(paravirtualization),即对客户机内核进行修改,使其明确与虚拟机监视器协同工作。随后,英特尔于2005年推出的 Intel VT-x 和 AMD 于2006年推出的 AMD-V 为硬件辅助虚拟化提供了处理器层面的机制,使支持未经修改的客户机操作系统变得更为简便。
基于内核的虚拟机(Kernel-based Virtual Machine,简称 KVM)于2007年在 2.6.20 版本中合并入主线 Linux 内核,利用硬件虚拟化扩展以及 Linux 现有的调度与内存管理机制开展工作。这些进展共同使虚拟机托管在日益普及的服务器硬件上变得切实可行。它们还为商业云计算奠定了重要基石:亚马逊于2006年进入公测阶段的 EC2 最初就是利用 Xen 来提供远程配置的计算实例。虚拟化由此将成熟的资源共享技术与自动化配置及大规模数据中心运营紧密结合在一起。
Hypervisor 架构与虚拟化理论
杰拉尔德·J·波佩克(Gerald J. Popek)与罗伯特·P·戈德堡(Robert P. Goldberg)在 1974 年发表的论文《第三代可虚拟化架构的形式化需求》(Formal Requirements for Virtualizable Third Generation Architectures)中,通过三个特性界定了虚拟机监控器(virtual machine monitor,即 Hypervisor):等价性(Equivalence)要求在监控器下运行的程序,除由于执行时序和资源可用性导致的允许差异外,其行为本质上应与在原始物理机上运行时完全一致;资源控制(Resource control)要求监控器保有对机器资源的最终控制权,客户机不能自行获取未分配给它的资源;高效性(Efficiency)要求虚拟处理器指令中占统计绝大多数的子集无需监控器介入即可直接在物理处理器上执行。这是一项结构性准则,而非对低开销的数值化保证。他们的形式化模型针对的是具备管态和目态两种执行模式以及基址-限长内存模型的传统第三代计算机;其结论依赖于这些架构假设,不能毫无变动地直接套用于所有现代处理器。
该理论将特权指令(在用户模式下执行会发生陷阱、而在管态下不会的指令)与敏感指令区分开来。控制敏感指令试图改变资源配置;行为敏感指令产生的结果则取决于该配置或执行模式。构建监控器的一个核心充分条件是所有敏感指令都必须是特权指令。如此一来,客户操作系统便可在非管态下执行:普通指令直接运行,而敏感操作则陷入监控器,由监控器将这些操作的作用复现于虚拟机器状态而非物理机器状态。这就是“陷入并模拟”(trap-and-emulate)虚拟化的基础。在引入硬件虚拟化扩展之前,部分 x86 指令违背了该条件。例如,在特定的保护模式特权级设置下,POPF 指令可以在不发生陷入的情况下直接忽略对中断允许标志位的修改,而不是像客户机内核预期的那样执行更改。这类情况阻碍了直接采用陷入并模拟技术进行虚拟化,但并不意味着无法通过其他技术实现虚拟化。
1 型与 2 型 Hypervisor 的区分描述了它们与底层平台的关系。1 型即裸机(bare-metal)Hypervisor,直接控制客户操作系统之下的硬件,Xen 和 VMware ESXi 是常见的例子。2 型即寄居(hosted)实现,依赖传统的宿主操作系统来提供设备访问、调度和内存分配等服务,典型的桌面安装版 Oracle VirtualBox 便是如此。实际架构使这种二分法变得更为复杂。Xen 将大量的管理与设备服务职责委托给一个特权域(通常称为 Domain 0),但该域本身并不是 Hypervisor。KVM 将虚拟化支持置于 Linux 内核内部,并通常在用户空间中使用 QEMU 来实现设备模型。因此,仅凭是否存在通用内核或用户空间设备进程并不能断定其分类;对架构的描述应当明确哪些组件实际控制着处理器执行、内存保护以及设备。
完全虚拟化、半虚拟化与硬件辅助虚拟化描述的是不同的设计维度,而非互斥的产品分类。完全虚拟化呈现出足够完备的硬件接口,使操作系统无需针对虚拟化进行修改即可运行。半虚拟化则公开专门的接口,供能够感知虚拟化环境的客户机显式使用,通常通过请求特权服务的“超级调用”(hypercall)来实现。硬件辅助则提供了处理器层面的机制,以实现受控的客户机执行以及对选定操作的拦截。因此,未修改的客户机内核可以利用硬件辅助执行机制,同时结合可选的半虚拟化驱动程序来加速存储或网络吞吐。相反,早期的 x86 产品在尚未引入后来的处理器扩展时,通过结合直接执行与软件翻译技术来实现完全虚拟化。
执行机制还必须与管理组件区分开来。Hypervisor 的核心机制负责建立客户机执行上下文、强制执行资源归属权以及处理被拦截的事件。设备后端和特权服务域可以代表它执行额外的支撑工作。管理软件则通过控制接口创建并配置客户机、分配资源并请求生命周期操作;图形控制台或编排服务仅仅因为管理着客户机,并不意味着它自身会去执行客户机指令。这些角色可以驻留在不同的进程、内核或物理机上,因此在架构描述中,必须将完整的虚拟化平台与其执行控制组件区分开来。
处理器执行与硬件辅助
虚拟 CPU(vCPU)表示对客户机可见的处理器架构状态:包括通用寄存器、指令指针、状态标志、控制寄存器,以及(在暴露的情况下)浮点和向量寄存器。当客户机执行时,部分状态驻留在硬件中;其他状态则维护在虚拟机监控程序(hypervisor)的数据结构中。虚拟机监控程序将处于就绪态的 vCPU 调度到物理中央处理器上运行,并在执行切换时保存和恢复状态。因此,拥有四个 vCPU 的虚拟机并不需要持续占用四个物理核心。客户机的执行与虚拟机监控程序对特定事件(如被拦截的指令、某些异常以及对虚拟硬件的访问)的处理交替进行。普通的客户机指令无需调用虚拟机监控程序即可直接执行。
在陷阱与模拟(trap-and-emulate)执行模式下,客户机无法直接执行的操作会引发处理器陷阱;虚拟机监控程序会检查该操作,在虚拟处理器状态上复现其效果,随后恢复执行。历史上,x86 架构使这种方法变得复杂,因为某些敏感指令在降权执行时行为会发生改变,却不会触发陷阱。纯软件虚拟化系统通过动态二进制翻译解决了这一问题:它们检查客户机指令块,用受控指令序列替换有问题的操作,并缓存已翻译的代码块以备复用。VMware 早期的 x86 实现结合了对客户机内核代码的翻译执行与对大部分应用程序代码的直接执行。翻译并不需要重写每条指令,其正确性取决于能否保留架构层面的行为效应,包括异常和对可执行代码的修改。
英特尔于 2005 年率先在处理器中推出的 Intel VT-x,以及 AMD 于 2006 年推出的 AMD-V,为客户机的执行提供了显式的硬件支持。英特尔区分了通常由虚拟机监控程序使用的 VMX root 操作模式与由客户机使用的 VMX non-root 操作模式;两者都保留了传统的 x86 特权级,从而允许客户机内核在 Ring 0 运行而无法控制宿主机。虚拟机控制结构(VMCS)用于记录客户机状态、宿主机状态和执行控制。VM entry 负责启动或恢复客户机执行,而 VM exit 则将控制权转交给虚拟机监控程序,并记录执行中止的原因。AMD 则通过虚拟机控制块(VMCB)和 VMRUN 指令提供对应的机制。可配置的拦截机制允许许多操作直接进行,同时将特定操作(如对特定控制寄存器的访问)路由给虚拟机监控程序处理。
Arm 在 Armv7-A 中引入了虚拟化扩展;AArch64 则提供了用于运行虚拟机监控程序的异常级别 EL2,传统客户机内核运行在 EL1,应用程序运行在 EL0。陷阱与异常返回负责在客户机与虚拟机监控程序的执行之间进行切换,尽管它们的术语和状态结构与 x86 的 VM entry 和 VM exit 有所不同。中断传递同样需要虚拟化:客户机必须按照自身的中断控制器状态、屏蔽规则和处理器优先级来感知中断。虚拟机监控程序可以在 vCPU 准备好接收中断时安排注入。包括英特尔 APIC 虚拟化和 Arm 通用中断控制器(GIC)虚拟化支持在内的硬件设施,减少了在受支持中断操作上所需的软件干预;但它们并不能消除所有的 exit 退出或调度工作。
跨架构执行需要实现一套不同的指令集架构,而不仅仅是控制对宿主处理器的访问。解释器可以逐条解码并执行客户机指令,而翻译器则将客户机指令块转换为宿主机指令。例如,QEMU 的微型代码生成器(TCG)支持通过翻译在 x86 宿主机上执行 Arm 软件。在兼容的架构上,QEMU 可以改用 Linux KVM 以硬件加速的方式执行客户机指令。因此,仿真(emulation)与同架构虚拟化并非互斥的技术:系统可以直接执行其客户机 CPU,同时模拟外围设备;也可以在翻译外来架构 CPU 的同时,保留类似的虚拟设备基础设施。
在嵌套虚拟化中,被称为 L0 的外层虚拟机监控程序运行客户虚拟机监控程序 L1,后者再运行内层客户机 L2。L0 必须向 L1 暴露虚拟化扩展的虚拟实现,同时保留对物理执行的最终控制权。例如,L1 请求的拦截控制必须与 L0 的需求合并,而来自 L2 的 exit 必须由 L0 适时处理或反映给 L1。虚拟化控制结构、中断状态和地址转换控制都需要额外的记录与维护工作。软硬件优化可以降低这些开销,但仅向客户机暴露普通的 CPU 指令本身并不能直接实现嵌套虚拟化。
内存虚拟化
系统虚拟机通常涉及三种地址空间。应用程序使用客户机虚拟地址(GVA),客户机操作系统通过其页表将其映射为客户机物理地址(GPA)。虚拟机监控程序则另行将客户机物理地址映射为宿主机物理地址(HPA),以此标识实际的物理机器内存。因此,从概念上讲,地址转换路径为 。在无虚拟机监控程序运行的物理机上,操作系统的页表通常直接将虚拟地址映射到机器物理地址。但在虚拟机内部,“物理”仅代表客户机视角的地址概念:相邻的客户机物理页并不一定占用相邻的宿主机物理页帧;且配置给客户机的某些内存甚至在初次被访问之前,根本没有实际的宿主机页帧作为后备支撑。在宿主型虚拟化实现中,用户空间的虚拟机进程还可能拥有客户机 RAM 的宿主机虚拟映射;这些映射与客户机自身的地址空间彼此独立。
影子页表通过软件手段将这两层映射合并。客户机维护描述 GVA 到 GPA 转换的页表,而虚拟机监控程序则构建可供硬件直接使用的影子页表,把 GVA 直接映射至 HPA。客户机对页表的修改必须被及时捕获并同步到影子页表中,这通常利用写保护和截获写操作来实现,并辅以对地址空间切换及失效操作的拦截处理。这种方法避免了硬件执行第二阶段页表遍历,但会导致频繁的映射变更开销极大。与之相对,硬件辅助的第二阶段转换允许处理器先遍历客户机管理的页表,然后再通过虚拟机监控程序管理的页表对这些客户机物理地址进行转换。英特尔将此技术称为扩展页表(EPT);AMD 称之为嵌套分页,亦称快速虚拟化索引(RVI);Arm 则在客户机的一阶段转换之后提供第二阶段转换。这些硬件机制能够明确区分是客户机层面的映射缺失或违规,还是需要虚拟机监控程序介入处理的第二阶段缺页异常。它们减轻了软件同步客户机页表的负担,不过若发生地址转换缓存未命中,遍历这两套页表可能会引发多次相互依赖的内存访问。
配置的内存容量并不一定等同于预留的物理内存。通过按需分配,虚拟机监控程序仅在客户机实际触及内存时才分配后备物理页;未触及的区域不会占用等额的宿主机 RAM。内存气球(ballooning)则借助一个与之协同的客户机驱动程序:当被要求充气膨胀时,该驱动在客户机内分配并持有页面,将其报告给虚拟机监控程序,从而允许宿主机回收其底层的后备内存。客户机操作系统可能会通过回收缓存或将其他内容换出到自身的存储设备上来应对内存紧缩。气球放气时,这些容量又会被归还给客户机使用。相比之下,宿主机交换直接将客户机内存内容置换到宿主机管理的存储设备上,而不依赖客户机的内存管理决策。若宿主机与客户机同时发生换页,这两种机制可能会产生恶性相互干扰。因此,内存预留量、配额上限和超分配策略决定了虚拟机能够获得多少有保证的物理内存支撑,这一指标独立于虚拟机内部可见的 RAM 容量。
页面共享通过识别内容相同的页面并将其映射至同一个宿主机页帧来降低内存消耗。共享映射通常设为只读;一旦发生写入,便会通过写时复制分配私有副本。2009 年合并进 Linux 2.6.32 的内核同页合并(Kernel Samepage Merging,KSM)即为符合条件的内存区域(包括 KVM 客户机所用内存)提供了这一机制。页面共享需要进行扫描和比对计算,且内存节省效果取决于实际数据内容,而非仅取决于是否运行相同的操作系统。此外,由于跨客户机的页面共享可能通过访存时延差异泄露信息,实际实现中可能会对共享加以限制或将其保持在禁用状态。
转译后备缓冲区(TLB)负责缓存地址转换结果,以减少重复的页表遍历;支持虚拟化的处理器能够缓存组合后的转换条目,并利用地址空间标识符来避免不必要的整表刷新。尽管如此,映射关系的变更仍需执行相应的失效操作。大页可以扩大单条转换缓存项所覆盖的地址范围:例如在 x86 架构中,一个 2 MiB 的大页覆盖的内存是一个 4 KiB 页面的 512 倍。然而,仅靠客户机使用大页并不能确保最终生成同样的大页组合映射;宿主机的后备支持、物理地址对齐以及第二阶段映射同样起决定性作用。在非统一内存访问(NUMA)系统中,内存相对于物理处理器的分布位置构成了另一重约束。客户机内部或许能感知到虚拟 NUMA 节点,但访存局部性取决于其虚拟 CPU 实际在哪个物理核心上执行,以及其物理内存究竟分配在何处。宿主机节点间的线程迁移或不均衡的内存分配,都可能将原本预期的本地访问转变为高延迟的远程跨节点访问。
虚拟设备、存储与网络
模拟设备重现了硬件接口(例如 Intel e1000 网卡或 IDE 磁盘控制器),使得客户机可以使用现有的硬件驱动程序。客户机写入设备寄存器并提交命令;宿主机端的设备模型解释这些操作,访问存储或网络服务,并返回完成状态和中断。半虚拟化接口则提供专为虚拟化设计的规范契约。借助 virtio,客户机驱动程序将缓冲区描述符放入共享内存队列中,而后端负责处理请求并报告完成状态。后端可在用户空间设备模型、宿主机内核或专用进程中运行。批量处理请求和抑制不必要的通知可以减少客户机与宿主机之间的上下文切换。设备直通将常规的设备操作移出了这条软件路径:客户机驱动程序直接控制所分配的硬件,但宿主机软件仍负责配置访问权限、中断以及设备归属。
输入输出内存管理单元(IOMMU)负责转换设备生成的直接内存访问(DMA)地址,并限制设备可访问的内存区域。这与处理器对客户机内存访问的地址转换截然不同。PCI 直通将物理 PCI 功能分配给客户机;其安全隔离还依赖于平台的设备分组、复位机制以及中断重映射支持。单根 I/O 虚拟化(SR-IOV)允许支持该特性的 PCIe 设备在由宿主机管理的物理功能下暴露多个虚拟功能。客户机可以获得各自独立的虚拟功能,每个虚拟功能都具备设备专用的队列和资源,而无需占用整个适配器。中介访问(Mediated access)则采取了另一种方法:宿主机软件向客户机呈现加速器(例如图形处理器)的受控部分,并协调对共享硬件的访问。支持的操作、隔离机制和调度策略取决于具体设备及其实现方式。
虚拟磁盘向客户机呈现为一个按块寻址的设备,但其底层后备存储可以是宿主机文件、逻辑卷或远程块设备。稀疏分配预留了逻辑容量,而不会立即为每个块分配物理存储;只有向先前未分配的区域写入数据时,才会消耗宿主机的存储空间。写时复制镜像可以从基础镜像中读取未更改的块,同时将修改记录在单独的层中。因此,读取操作可能需要查询多个层,而写入操作则在可写层中分配空间。磁盘镜像格式与客户机文件系统处于不同的抽象层级:qcow2 镜像描述块映射和镜像元数据,而这些块中可能包含分区表以及 ext4 或 NTFS 文件系统。宿主机无需理解该文件系统即可处理块请求。Discard 命令可以告知某些块已不再需要,但物理空间的回收仍取决于整个存储栈的支持。
虚拟网络接口同样将客户机可见的网卡与数据包转发分离开来。例如,客户机 virtio 驱动程序通过发送队列提交以太网帧;后端将其传递给宿主机网络端点(通常是 TAP 接口或内核内交换路径)。接着,虚拟交换机将该帧转发给另一个客户机、宿主机接口或物理上行链路。桥接将多个接口置于同一个二层转发域中,而路由则在 IP 网络之间传输数据包。网络地址转换(NAT)重写地址以及(通常包含)传输层端口,从而允许拥有私网地址的客户机通过宿主机的外部地址进行通信。覆盖网络(Overlay networks)在主机之间增加了封装:例如,VXLAN 将以太网帧封装在 UDP 数据包中,并使用 24 位网络标识符来区分逻辑网络。外部流量最终必须通过适当的物理接口或网关离开覆盖网络。
虚拟时钟和固件完善了仅靠处理器指令执行无法提供的接口。客户机可能需要传统定时器设备、本地 APIC 定时器,或是根据处理器计数器和宿主机提供的换算参数推导时间的半虚拟化时钟。定时器的截止时间可能在虚拟 CPU 未被调度期间到达;虚拟机监控器必须在该 CPU 恢复运行时安排一个挂起的中断。时钟偏移与缩放有助于在调度中断及受支持的迁移过程中维持客户机可见时间的连贯性。固件接口还决定了硬件发现与引导过程:BIOS 或 UEFI 初始化虚拟平台,ACPI 表描述设备和电源管理设施,中断控制器描述则告知客户机中断的路由方式。因此,兼容性不仅取决于能否执行客户机的指令集,还取决于寄存器行为、DMA、中断、计时以及固件描述的一致性。
进程虚拟机与托管运行时
进程虚拟机为程序提供执行环境,而非为完整的客户机操作系统提供虚拟硬件。其中间指令集将程序的表示形式与最终由处理器执行的指令解耦。在 Java 工具链中,javac 通常将源文件编译为包含字节码、符号引用和元数据的类文件(class 文件);Java 虚拟机(JVM)随后加载并执行这些文件。JVM 规范定义了类文件格式与指令语义,但并未强制要求采用特定的执行策略。在微软的 .NET 工具链中,C# 等语言的编译器会生成包含通用中间语言(CIL)指令和元数据的程序集。公共语言运行时(CLR)利用这些信息解析类型、管理执行并提供运行时服务。这些中间格式还支持多种源语言:Java、Kotlin 和 Scala 均可面向 JVM 编译,而 C#、Visual Basic 和 F# 则可面向 .NET 编译。
JVM 和 CIL 主要采用基于栈的指令集:指令从求值栈中获取操作数,而不是直接指定通用处理器寄存器。例如,JVM 指令 iload_0、iload_1 和 iadd 会加载两个整型局部变量,并将操作数栈上的这两个值替换为其和。JVM 方法调用会创建一个栈帧,其中包含局部变量表、操作数栈以及用于动态链接的信息。而基于寄存器的虚拟指令集则显式指定虚拟寄存器;Android 的 Dalvik 可执行文件格式便采用了这种设计,其后继运行时 ART 依然沿用了相关机制。无论是哪种设计,都不决定编译后物理寄存器中数值的分配方式。异常处理依赖元数据来标识受保护的指令区间及对应的处理程序;若当前调用中不存在匹配的处理程序,执行流便会向上层调用者展开。运行时链接负责将符号名称解析为具体的类型、字段和方法(有时在首次使用时才进行解析),并且即使源码编译成功,运行时链接仍可能因错误而失败。
解释执行通过软件调度机制来执行中间指令,而即时(JIT)编译则在执行期间将方法或代码区域翻译为机器原生指令。预先(AOT)编译在执行前完成翻译,虽然可能减少启动开销,但会改变可用的优化信息与部署约束。实际实现往往综合运用这些策略。OpenJDK 的 HotSpot 可以在初期采用解释执行并结合分层编译,在应用更耗时的优化前收集执行计数与类型特征。基于性能剖析的内联优化可能会假定某处调用通常指向特定的单一实现;若该前提假设失效,去优化机制可以从编译状态重构解释器栈帧,以更为保守的方式恢复执行。这些机制属于具体的实现特性,并非所有 JVM 的硬性规范。在 .NET 中,分层编译与动态按性能剖析优化同样可以优化高频执行的代码;ReadyToRun 映像包含可与 JIT 编译并存的预编译代码,而 Native AOT 则采用不同的部署模型,完全不依赖运行时的 JIT 编译器。
托管运行时还负责协调内存分配、垃圾回收、类型检查、同步以及类或程序集的加载。垃圾回收器判定哪些对象仍然可达,并回收其余对象占用的存储空间;移动式垃圾回收器在搬迁对象时还会更新相关引用。字节码验证在执行前检查结构与类型层面的性质,而非证明程序的任意逻辑正确性。JVM 的检查项包括控制流汇合处操作数栈状态的兼容性,以及类型化指令的合法使用。CIL 同样具备验证规则,但并非所有可执行的 CIL 都是可验证的,不安全代码与原生互操作性需要额外的机制区分。此类安全保证并不能构建操作系统级别的安全边界:托管代码依然可以消耗资源、访问已授权的文件,或以该进程拥有的权限调用原生函数。
因此,可移植性不仅取决于统一的指令格式。较旧的 Java 运行时可能不支持更新的类文件版本或所需的库 API;.NET 应用程序同样依赖兼容的运行时及库功能。Java 原生接口(JNI)调用、.NET 平台调用(P/Invoke)以及其他外部函数接口,都会引入对原生库、处理器架构和调用约定的依赖。文件系统规范、可用的图形服务以及平台特有的 API 也可能影响运行行为。此外,语言解释器不一定需要实现一套独立规范的虚拟机:其具体实现可以直接求值语法树,也可以采用不构成稳定可移植性约定的内部字节码格式。
镜像、状态与生命周期管理
创建系统虚拟机既要建立虚拟硬件配置,也要确立客户机操作系统用于引导的存储空间。安装介质允许客户机安装程序对虚拟磁盘进行分区并安装软件;而模板则提供预先制作好的磁盘镜像,通常在首次启动时通过注入凭据、网络设置和机器标识来进行自定义。配置项指定了虚拟 CPU 数量、内存大小、固件类型、磁盘控制器以及网络接口。启动虚拟机会初始化这些设备,并进入其固件或引导程序流程。挂起操作会将执行状态(包括虚拟 CPU 寄存器、内存以及受支持的设备状态)保存在宿主机内存或状态保存文件中。恢复操作则是继续先前的执行状态,而不是重新引导。关机会终止执行,但通常会保留持久化磁盘;删除虚拟机与删除其关联的存储是两个相互独立的操作。
磁盘快照记录了虚拟存储历史中某个可恢复的时间点,并不一定包含完整的运行中机器状态。在写时复制(copy-on-write)存储机制下,后续写入会进入新的增量层,而未更改的数据块仍保留在先前的层中。因此,删除或合并快照可能需要合并数据块,而不仅仅是删除一个独立的文件。包含内存的快照还会额外捕获 RAM 和执行状态,从而允许恢复活动的会话。完整克隆会将所需的存储复制到独立实例中;链接克隆则保留对父镜像或快照的依赖。备份则是在单独的保留策略与故障防护机制下保存可恢复的数据。位于同一存储系统上的快照本身并不是独立的备份:一旦基础镜像或存储介质损坏,整个快照链都会不可用。
捕获的状态在一致性层面上也有所不同。崩溃一致性磁盘快照类似于突发断电后残留的持久化存储状态:仅保存在客户机内存中的写入操作会丢失,文件系统或数据库可能需要执行恢复流程。应用一致性捕获则在创建快照之前协调各应用程序、刷写相关缓冲区,并确立一个可恢复的事务边界。例如,微软卷影复制服务(Volume Shadow Copy Service)会协调参与应用程序的写入器(writer),而客户机代理接口可以在受支持的客户机中请求冻结文件系统。仅冻结文件系统并不能确保所有应用程序或外部服务之间的一致性。恢复虚拟机也无法回滚另一台服务器已接收的支付操作,或已被其他系统消费的消息;此类依赖关系需要超出被捕获虚拟机本身的外部协调。
冷迁移在虚拟机处于关机状态时进行迁移,在目标节点启动之前先传输其配置及所需存储。热迁移则是在服务中断时间极短的情况下转移正在运行的虚拟机。在预拷贝(pre-copy)迁移中,源节点在继续执行的同时复制内存;脏页跟踪机制会记录复制过程中被修改的页面,以便在后续轮次中重新传输。最后的短暂停顿会传输剩余的页面、处理器寄存器以及设备状态,随后虚拟机在目标节点恢复执行。频繁变动的内存可能会导致迁移无法收敛。后拷贝(post-copy)迁移则先传输足以在目标节点启动虚拟机的基本状态,随后(包括通过缺页异常触发请求)从源节点获取缺失的内存页。这种方式避免了反复复制脏页,但使虚拟机的执行依赖于内存传输的最终完成。这两种方法都需要共享存储或协调传输磁盘内容,并需要通过网络配置来维持客户机的网络连通性。
检查点机制记录可恢复的执行状态,而复制(replication)则在另一个系统上维护存储或机器状态的副本。异步复制可能会导致恢复点落后于最新完成的工作。基于重启的高可用性机制会在检测到宿主机故障后,利用可访问的持久化磁盘在其他节点启动受影响的虚拟机;客户机内存通常会丢失,应用程序需经历重启恢复。连续容错系统则在备用机器上维持高度同步的执行流程或状态,力求在无需重启客户机的情况下无缝继续运行。恢复或迁移捕获的状态需要两端具备兼容的处理器特性、虚拟设备模型、固件以及管理程序状态格式。直通分配给虚拟机的物理设备可能会带来额外的限制,因为其内部状态可能无法导出,也无法在另一台宿主机上重现。
部署与应用
服务器整合将以往分配给多台独立物理服务器的工作负载,整合到数据中心共享宿主机上的独立虚拟机中。每个客户机都保留各自的操作系统和软件配置,而虚拟化平台负责分配处理器时间、内存、存储和网络连接。在基础设施即服务(IaaS)中,这些资源变成了可按需调配的实例;2006 年推出公测版的亚马逊弹性计算云(Amazon EC2)便是早期的商业范例。资源调配请求通常会指定镜像、实例规格、存储卷、网络接入及访问凭据。云管理软件选择合适的宿主机,创建所需资源,并通过虚拟机监控器与基础设施 API 协调启动。2010 年推出的 OpenStack 将这些职责划分至不同的服务中,包括负责计算的 Nova、负责网络的 Neutron 以及负责块存储的 Cinder。资源规格划分指定了虚拟 CPU 和内存等数量,但资源的分配并不等同于对专用物理硬件的独占预留。
开发与测试系统利用虚拟机来隔离不兼容的依赖项,并能够反复构建出已知的软件环境。自动化构建节点可以从特定版本的镜像启动,拉取源代码,完成编译与测试并导出结果,随后即可销毁。这样可以减少先前任务带来的环境污染,尽管网络服务以及外部下载的依赖项仍可能发生变动。操作系统兼容性测试可以在不同版本的 微软 Windows 或 Linux 上运行同一个应用程序,每个虚拟机都保留有各自的库、注册表或配置文件以及安装历史。模板与配置自动化有助于提升可复现性,但单凭镜像并不能记录所有相关的输入条件:编译器版本、软件包仓库、外部数据库以及依赖处理器的特定行为也可能产生影响。涉及物理设备或高精度计时的测试可能需要特定硬件,而虚拟测试环境无法完全复现这些硬件特性。
桌面虚拟化将用户的显示器和输入设备与运行桌面应用程序的机器分离开来。在虚拟桌面基础设施(VDI)中,连接代理将用户分配到一台桌面虚拟机,并由远程显示协议传输屏幕更新、键盘事件和光标输入。持久化桌面会保留特定于用户的机器状态;非持久化桌面池则可以使用基于通用镜像生成的实例来替换桌面,同时单独存储用户配置文件。远程访问的桌面并不一定就是虚拟机:微软远程桌面服务(RDS)可以在单一服务器操作系统实例中提供多个会话,而由虚拟机托管的桌面则拥有独立的客户操作系统。反之,桌面虚拟机也可以在个人电脑本地运行,无需任何远程显示连接。这些差异会影响软件兼容性、系统管理以及资源核算。
当把旧有应用程序迁移到现代平台需要进行大幅修改时,遗留应用托管可以通过保留旧版操作系统及其依赖项来运行这些程序。历史环境还可用于数字保存与教学:学生可以在不更换工作站操作系统的情况下,探究已淘汰的界面或网络配置。互联网档案馆(Internet Archive)于 2016 年推出的 Windows 3.1 浏览器合辑,正是通过仿真计算机环境提供历史软件的一个典型例子。此类案例可能需要跨架构仿真,而非普通的同架构虚拟化。保留磁盘镜像并不能保证功能的完整性:应用程序可能依赖于无法访问的许可证服务器、硬件加密狗、专用外设或外部服务。复现原有的软件环境,也不同于完整保留原物理机的时间特性和设备行为。
虚拟网络设备将路由、防火墙、负载均衡以及虚拟专用网终结等功能打包为可部署的软件镜像。网络流量通过虚拟网络接口流入,并经过该设备客户机的网络协议栈,随后被转发或过滤。网络功能虚拟化(NFV)最早由电信运营商在 2012 年的一份白皮书中倡导,随后通过 ETSI 规范逐步发展,它将这种方法应用于以往通常由专用硬件设备所承载的功能。管理与编排系统负责协调虚拟化网络功能的部署、扩缩容、网络连接与故障恢复。服务链可以按指定顺序将流量引导通过虚拟路由器、防火墙及其他功能单元。数据包处理能力取决于接口实现、处理器分配以及硬件加速机制;仅仅将物理设备替换为虚拟机,并不能自然保证原有的吞吐量或延迟水平。
隔离与安全边界
系统虚拟机的安全边界将客户机控制的执行与虚拟机监控器、其他客户机以及宿主机资源隔离开来。该威胁模型通常假定攻击者能够在客户机内部获得管理员权限并发出任意指令和设备请求,但不得以此控制宿主机。在传统虚拟化中,可信计算基包括虚拟机监控器以及一旦遭受破坏便可能瓦解这种隔离的任何组件:宿主机内核设施、特权设备后端、管理服务以及相关固件。其范围取决于架构;受限于非特权进程的设备仿真器与以无限制宿主机访问权限运行的设备仿真器拥有不同的权限。管理接口构成了另一条独立的控制边界:挂载磁盘、检查内存、替换启动映像或访问虚拟机控制台的权限,都可以绕过客户机内部实施的保护机制。
当源自客户机的活动突破预期的隔离边界时,即发生虚拟机逃逸(VM escape),这可能使攻击者得以在宿主机进程中执行代码或控制宿主机资源。设备仿真会将解析器、队列、寄存器和状态机暴露给不可信的客户机输入;边界检查或对象生命周期管理中的错误可能使这些接口沦为攻击路径。2015 年披露为 CVE-2015-3456 的 VENOM 漏洞便是 QEMU 虚拟软盘控制器中的缓冲区溢出漏洞,该漏洞波及了多个使用该设备模型的虚拟化产品。客户机集成功能——包括共享文件夹、剪贴板同步以及宿主机–客户机通信代理——在虚拟硬件之外创建了额外的交互接口。共享存储和网络后端同样至关重要:攻破由多个客户机共同使用的某项服务,即便没有攻破处理器虚拟化代码,也能够跨越租户边界。进程隔离、限制特权和沙盒技术能够减轻后端漏洞带来的后果,但并不能保证其接口本身不存在漏洞。
跨虚拟机侧信道攻击利用的是共享资源的可观察效应,而非对另一客户机地址空间的越权访问。例如,攻击者可以预填(prime)处理器缓存组,让受害者执行,然后测量随后的访问时间,以推断受害者使用了哪些缓存资源。2009 年,托马斯·里斯滕帕特(Thomas Ristenpart)及其同事在亚马逊 EC2 中展示了同驻(co-residency)检测与跨虚拟机信息泄露,说明了虚机放置策略与共享硬件会对云环境的隔离性产生何种影响。2018 年披露的推测执行漏洞进一步揭示了微架构状态的重要性。此类泄露与直接映射另一台虚拟机的内存或读取其磁盘截然不同:体系结构级的访问检查可能完好无损,而时序测量却能泄漏信息。暴露风险与缓解措施取决于处理器设计、资源共享方式、软件行为以及调度策略;仅靠内存隔离无法消除此类信道。
存储和传输中的虚拟机状态在执行环境之外形成了新的安全边界。磁盘映像可能包含凭据、私钥、应用程序数据,或残留在较旧的写时复制层中的已删除信息。内存快照还可以捕获从未写入磁盘的明文机密与活动会话状态。因此,访问控制与加密需独立应用于映像仓库、快照存储区、备份及其加密密钥。热迁移会传输包括内存页在内的执行状态,因此在不可信网络中传输时,其端点需要进行身份验证,其传输过程也需要机密性与完整性保护。管理凭据尤其关键,因为控制平面权限可以无视客户机登录限制,直接导出映像或获取控制台访问权。恢复旧快照还可能会恢复过期的凭据或存在安全隐患的状态。
安全启动(Secure Boot)、虚拟可信平台模块以及机密计算应对的是不同的威胁。UEFI 安全启动依据已配置的信任数据库检查启动组件的签名;这并不能证明已签名的软件不存在漏洞。虚拟可信平台模块提供了度量启动寄存器、密钥保护以及将数据封印至指定平台度量值等功能,但其安全保证取决于其实现及存储状态受保护的程度。AMD SEV-SNP 与 Intel TDX 将防护范围拓展至防御来自特权宿主机软件的威胁,采用硬件强制隔离与内存保护,并结合描述客户机初始状态和安全配置的远程证明凭据。它们预期的保护边界并不包括消除所有侧信道、防范客户机内部已存在的恶意代码,或在宿主机停止调度时保障可用性。外设、共享 I/O 缓冲区、证明验证和密钥配置仍然是必须明确考量的信任要素。
性能与资源权衡
虚拟化开销主要来自几个不同的途径:处理器操作的拦截、虚拟中断传递、设备仿真或后端处理、额外的地址转换以及宿主机调度。大部分普通的客户机代码可以直接在处理器上执行,而特定事件则会将控制权转移给虚拟机监视器(Hypervisor)。因此,开销取决于事件发生的频率及处理成本,而非仅仅由执行指令的数量决定。硬件辅助内存转换避免了维护影子页表,但在转换缓存未命中时可能需要额外的内存访问。虚拟设备处理可能会引入退出(VM-exit)、队列操作、数据复制或执行上下文之间的切换。因此,并没有一个适用于所有虚拟机的统一开销比例:以算术运算为主的计算型工作负载,其表现可能与以小网络数据包或同步磁盘写入为主的工作负载截然不同。
CPU 超额分配指分配的虚拟 CPU 数量多于宿主机能够同时执行的上限。例如,在不考虑同时多线程的情况下,向拥有 16 个物理核心的宿主机分配 32 个虚拟 CPU 会产生名义上 2:1 的超分比;这并不意味着每个客户机都能持续获得半个核心的算力。当客户机大多处于空闲状态时,这种安排可以在不产生严重争用的情况下提高资源利用率。而当它们同时处于就绪状态时,虚拟 CPU 就必须等待宿主机的调度。某些客户机操作系统会将这部分延迟记录为“窃取时间”(steal time),不过不同平台的统计口径有所不同。因延迟调度且持有客户机内部锁的虚拟 CPU,还可能导致其他虚拟 CPU 停滞。吞吐量衡量单位时间内完成的工作量,延迟衡量单个请求的完成时间,而可预测性则关乎它们的变化幅度:可观的总体吞吐量与偶尔出现的较长卡顿完全可能并存。
性能瓶颈取决于工作负载以及处理器时间之外的共享资源。受 CPU 限制的数值计算可能主要取决于核心分配、处理器主频、缓存容量和内存局部性。而 I/O 密集型的数据库则可能受制于存储延迟、队列争用或事务提交所需的持久化操作。在固定比特率下,数据包越小,网络处理的负担就越大,因为需要处理的数据包数量更多。即使拥有各自独立的虚拟 CPU,不同的客户机仍可能争用末级缓存、内存带宽、存储控制器队列以及网络链路——即所谓的“吵闹邻居”(noisy-neighbor)效应。直接透传 GPU 或网络设备可以减少软件路径上的开销,但并不能消除设备本身的性能上限或系统其他部分的争用,而且还会限制虚拟机的迁移与资源共享。
有意义的对比评估需要基于等效的工作负载并明确记录配置细节。对比虚拟化与非虚拟化环境的测试,应充分考虑处理器型号、固件设置、激活核心数、多线程技术、内存容量及布局、存储、网络拓扑、操作系统版本以及应用程序设置。虚拟 CPU 的限额、预留、宿主机超额分配情况以及设备后端配置也必须予以说明。如果在一种环境中测试缓存中的数据集,而在另一种环境中进行未缓存的存储访问,所测出的差异就早已超出了虚拟化本身的范畴。托管运行时可能需要预热以进行即时编译,而存储测试则必须将缓存效应与设备的持续性能区分开来。重复测量并详细记录配置有助于保证测试结果的可复现性。
测试结果应当包含吞吐量和延迟分布,而非仅仅呈现平均值。99 分位延迟为 100 毫秒意味着约 99% 的受测请求在该时间内完成,而其余请求耗时可能明显更长。对比时必须指明施加的负载、并发量、测试时长,以及请求的生成是否独立于服务器响应时间。在接近饱和状态下测试的系统,即使吞吐量仅有微小变化,排队延迟也可能会急剧上升。独占宿主机的测试与存在竞争客户机的测试代表了不同的运行工况,只要前提条件明确,二者都能提供有价值的信息。
服务器整合通过将需求模式互补的工作负载部署在共享硬件上,从而减少开机运行的服务器数量。潜在的收益包括节省宿主机电耗、散热成本、机架空间以及硬件开支,但管理、软件授权、存储和弹性高可用需求同样会影响运维成本。服务器的功耗并不会随 CPU 利用率成比例下降,因此关停一台利用率低下的宿主机,所能节省的能量要多于将同样的工作负载稀疏分散到多台宿主机上。反之,过高密度的整合可能会加剧资源争用,并且需要预留冗余容量以应对故障或峰值需求。因此,在对比能耗时,既需要考量总消耗,也需要考量在特定性能要求下完成的有效工作量——例如每笔事务消耗的焦耳数。
容器、轻量级虚拟机与设计方向
系统级虚拟机与操作系统容器呈现出不同的执行边界。系统级虚拟机通常在虚拟处理器、内存和设备之上运行独立的客户机内核。容器则在保留宿主机内核的前提下对进程进行隔离:在 Linux 上,命名空间隔离了进程标识符、挂载点和网络等资源视图,而控制组则用于计量和限制资源消耗。容器镜像通常打包应用程序二进制文件、库和文件系统内容,而非可引导的客户机内核。因此,Linux 容器需要兼容的 Linux 内核接口;在 Windows 或 macOS 上运行它们通常需要借助 Linux 虚拟机。只要虚拟硬件兼容,虚拟机即可支持不同的客户机操作系统。这两种边界本身都不能绝对保证安全性,但容器将共享的内核置于每个容器的隔离边界之内,而虚拟机则在客户机内核与物理资源之间引入了虚拟机监控器。
容器与虚拟机常常组合使用,而非互斥替代。容器编排的工作节点本身就可以是虚拟机,这使基础设施运维人员能够独立于应用程序打包与调度来分配机器。如此一来,多个容器共享每个工作节点的客户机内核,而不同工作节点之间则保持虚拟机级别的隔离。沙盒容器运行时提供了另一种架构方案:Kata Containers 在轻量虚拟机内部运行容器工作负载,在接入容器管理接口的同时提供独立的客户机内核。根据运行时和配置的不同,其隔离单元可能是一个包含若干协作容器的 Pod,而非为每个进程单独分配一台虚拟机。这种方式在很大程度上保留了容器的部署模型,但也带来了客户机引导、内存分配以及宿主机-客户机通信的开销。
微虚拟机(microVM)是围绕极简化虚拟硬件配置设计的系统级虚拟机,并非一种独立的处理器虚拟化类别。亚马逊云计算服务(AWS)于 2018 年推出了开源虚拟机监控器 Firecracker,用于支持 AWS Lambda 和 AWS Fargate 等服务。它利用 Linux 的基于内核的虚拟机(KVM)接口,并采用精简的设备模型(仅保留 virtio 网络和块存储),省去了通用机器模型中常见的大量传统硬件模拟。据报道,其极简配置可实现低于 125 毫秒的启动时间以及低于 5 MiB 的内存开销;这些数据对应的是特定配置,并不代表应用程序完全就绪的普遍保证。较少的模拟设备可以减少初始化工作量,并缩减暴露于客户机输入的代码面,尽管系统安全性依然取决于所保留的设备实现、宿主机内核以及管理环境。
单内核则是精简客户机内部的软件,而非仅仅简化其虚拟硬件。单内核将应用程序代码与挑选出的操作系统服务整合为一个专有镜像,通常通过库操作系统构建。2013 年在 ASPLOS 上发表的 MirageOS 研究展示了这一方案,它将 OCaml 应用程序与所需的网络及系统组件一同编译。与普通容器镜像不同,面向虚拟机监控器的单内核不依赖宿主机内核来提供其应用程序的系统调用环境。与传统的客户机系统安装相比,它可以省去通用 Shell、包管理器以及多进程用户环境。这些设计选择改变了调试与部署模式:更新应用程序通常意味着重新构建并替换镜像,而不是修改正在运行的通用系统。
快速启动不仅涉及拉起虚拟处理器。镜像分发、存储挂载、网络配置、客户机初始化以及应用程序预热,都可能在就绪时间中占据主要部分。快照恢复可以跳过重复的初始化过程,但克隆的状态需要谨慎处理随机数生成、凭据、时钟和网络标识等问题。当客户机使用直通分配的设备时,迁移会面临不同的挑战:如果无法同时捕获和恢复设备队列、固件状态或加速器内存,仅传输处理器和内存状态是远远不够的。因此,对 GPU 或其他设备的迁移支持取决于具体设备、驱动程序、固件和虚拟化技术栈,并不会随着支持虚拟机而自动具备。
异构处理器使可移植性进一步复杂化,因为源机器与目标机器之间的指令集扩展、加速器接口和内存一致性机制可能存在差异。屏蔽特定的处理器功能可以提高迁移兼容性,但也会阻止应用程序使用更新的指令。机密计算设计带来了另一重矛盾:针对基础设施软件加密客户机内存,会限制宿主机侧常规的检查、调试和状态提取。因此,迁移和可观测性需要明确支持的专用机制,例如受保护的状态传输协议或经客户机授权的遥测机制。这些机制的可用性及安全保证仍然受限于具体的硬件架构与实现。