这是一篇论文:https://academiccommons.columbia.edu/doi/10.7916/D8D238J2/download
学习ARM虚拟化非常好的材料,这里翻译了其中几个章节。
-----------------------------------------------------------------------------------------------------------
Popek和Goldberg的虚拟化理论[66]确定特定的指令集架构(ISA)是否可以由陷阱和模拟管理程序达到虚拟化目的。该管理程序直接在真实CPU上执行来自VM的大多数指令。 满足本文所述要求的体系结构被分类为可虚拟化和不可虚拟化的体系结构。 该定理适用于任何现代计算机体系结构,它满足许多属性,例如具有至少两种独立模式,一种是特权模式,一种是非特权模式; 通过分段或分页实现的虚拟内存; 以及与处理器状态管理相关的一些其他属性。 Bugnion等。 [25]详细介绍了这些属性,并解释了1974年陈述的属性如何解释并应用于现代架构x86,ARM和MIPS。
如果计算机体系结构满足理论中的要求,则可以构建陷入和模拟管理程序,其中虚拟机是“高效,隔离,重复的真实机器”。 这意味着与原生执行相比,VM的执行性能只有轻微的下降; 管理程序始终保持对真实硬件的控制,并且一个VM中的软件不能干扰或访问另一个VM中的状态,类似于在两个单独的物理机器上运行; 最后,VM与底层机器非常相似,在真实机器上本地运行的软件也可以在VM中未经修改地运行。 我们将VM的这些属性称为效率,隔离和等价属性。
该理论的核心要求是基于ISA中指令的分类。 控制敏感型指令是指那些可以控制硬件配置的指令,例如更改执行级别,禁用中断或配置虚拟内存设置。 行为敏感指令是指根据它们执行的CPU模式以及整个系统的配置状态去执行不同的行为指令。 例如,启用或禁用虚拟内存的指令是控制敏感的,读取当前CPU模式是行为敏感的。 敏感指令是对控件敏感或对行为敏感的指令,以及既不是无害的指令。 特权指令是那些只能从特权模式执行的指令,最重要的是,在非特权模式下执行时会陷入特权模式。 该理论然后陈述:
对于任何传统的第三代计算机,如果该计算机的敏感指令集是该组特权指令的子集,则可以构造虚拟机监视器。
也就是说,如果所有敏感指令都具有特权,我们可以在现代计算机体系结构上构建陷入和模拟管理程序。管理程序剥夺客户操作系统的权限并在非特权用户模式下运行整个VM,包括写入以内核模式运行的客户操作系统。所有无害指令都直接在CPU上执行,无需管理程序的干预。敏感指令陷入以内核模式运行的hypervisor,然后hypervisor模拟它们在软件中的行为,使其在虚拟机抽象而不是真实机器上运行。例如,禁用中断的控制敏感指令。当客户操作系统禁用中断时,由于该指令也具有特权,它还会陷入管理程序,管理程序通过在其VM数据结构中记录虚拟中断不应向VM发送信号来模拟该行为。不允许guest虚拟机禁用真实硬件上的物理中断,因为这会违反VM隔离要求,并且虚拟机管理程序将失去对真实机器的控制,并且其他VM将不再受到任何中断的影响。作为一个反例,考虑一种非虚拟化架构,其中禁用中断指令不是特权,而是在用户模式下执行时被忽略。在这种情况下,当客户OS执行指令时,没有任何反应,并且管理程序将继续向VM发送虚拟中断信号。未修改的客户操作系统将在此类系统上发生故障,因为管理程序无法意识到客户操作系统已尝试禁用中断,并且客户操作系统将在其认为已禁用中断时接收中断。
由于ARM体系结构不是经典的可虚拟化(参见第1章),因此ARM引入了虚拟化扩展(VE)作为ARMv7 [11]和ARMv8 [13]体系结构的可选扩展。 诸如Cortex-A15之类的ARM CPU [10]包括对虚拟化的硬件支持,针对服务器和网络市场的所有ARMv8 CPU都包括虚拟化扩展。 我简要介绍了ARM虚拟化扩展。
要运行VM,必须虚拟化特权CPU模式以保持隔离,并使管理程序保持对物理硬件的控制。如果允许VM完全控制底层硬件,那么它们会妨碍系统上的其他任务和VM运行,关闭CPU,甚至危及系统其他部分的完整性。如第1章所述,可虚拟化体系结构可以通过在非特权用户模式下运行所有VM(包括guest内核)来虚拟化特权CPU模式,并将每个敏感操作捕获到管理程序并在软件中模拟操作。要在非虚拟化架构(如ARM)上支持CPU虚拟化,一种选择是将架构更改为所有敏感操作都陷入,从而使架构可虚拟化。但是,这种方法不一定能提供良好的性能,并且要求管理程序正确有效地实现所有敏感操作的仿真。相反,如果硬件可以通过某种方式改变这些操作,以在虚拟CPU状态而不是物理CPU状态下操作来支持直接在VM中执行敏感操作,则可以实现隔离而不会过于频繁地捕获到管理程序。这是ARM和x86硬件虚拟化支持对许多操作采用的方法。例如,当使用ARM虚拟化扩展运行VM时,当VM禁用中断时,这实际上不会陷入虚拟机管理程序,而是禁用虚拟中断,这将在第2.1.3节中进一步详细说明。 ARM通过引入新的更高权限的CPU模式来运行虚拟机管理程序来实现这些功能。
图2.1和2.2显示了ARM CPU模式和权限级别,包括可选的安全扩展(TrustZone)和新的权限级别和CPU模式,ARMv7的PL2(Hyp)和ARMv8的EL2。实现安全扩展的ARM CPU将执行分为两个世界:安全和非安全。提供特殊模式监视模式,以在安全和非安全世界之间切换。虽然实现安全扩展的ARM CPU总是在安全的环境中启动,但固件通常会在早期阶段转换到非安全的世界。安全世界用于运行可信执行环境(TEE)以支持数字权限管理或身份验证等用例。通过使用安全世界进行虚拟机管理程序执行,TrustZone似乎对虚拟化很有用,但这并不容易,因为安全世界无法控制非安全世界对普通非安全RAM的访问,并且只能配置从非安全到陷阱的陷阱在一些选择操作上保护世界。例如,当OS配置虚拟内存时,没有办法将在非安全世界中执行的一般OS操作捕获到安全世界。因此,以最高非安全权限级别运行的任何软件都可以有效地访问所有非安全物理内存,从而无法隔离在非安全世界中运行的多个VM。
在具有TrustZone地址空间控制器(TSAC)的系统上使用安全扩展来构建虚拟机管理程序是可能的,该机制可以用于禁止对物理内存区域的非安全访问,但是这种方法存在严重的限制,例如 不能通常虚拟化物理内存,但只能静态分区,我认为任何此类解决方案对于通用虚拟化都表现不佳。
ARMv7和ARMv8 CPU保护机制之间的差异不会影响虚拟机管理程序设计,但我强调了完整性的重要变化。差异主要涉及非安全和安全世界与命名之间的相互作用。在ARMv7上,在监控模式下运行时的权限级别是安全PL1,因此监控模式不比PL1内核模式更具特权。在ARMv8上,EL3是一种比安全EL1更具特权的模式。例如,ARMv8定义了几个系统寄存器,这些寄存器只能从EL3访问而不能从安全EL1访问。这种特殊的差异会影响固件如何配置非安全世界的实现,但不会影响仅限于非安全状态的虚拟机管理程序的设计。 ARMv7定义了多个PL1模式;实际上有六种PL1模式,Supervisor,Abort,Undefined,FIQ,IRQ和System。这些模式共享相同的权限级别,但具有许多专用寄存器,例如它们自己的堆栈指针。系统模式很特殊,因为它与用户模式共享相同的寄存器,但在PL1上运行。 ARMv8消除了每个权限级别的多个模式,并降低了单个概念(异常级别)的复杂性。因此,ARMv8有三种非安全CPU模式,EL0,EL1和EL2。当ARMv7中首次引入虚拟化扩展时,虚拟机管理程序CPU模式被命名为Hyp模式,但是当后来引入ARMv8并且只有异常级别的概念时,等效的异常级别称为EL2。因此,我在本文中可互换地使用术语Hyp模式和EL2来指代CPU的管理程序执行模式。
引入了Hyp模式以支持ARM体系结构上的虚拟化,并且旨在尽可能简单地运行虚拟机管理程序。 Hyp模式严格比用户和内核模式更具特权。在Hyp模式下运行的软件可以配置硬件以在各种事件上从内核和用户模式陷入Hyp模式。例如,在Hyp模式下运行的软件可以选择性地选择读取CPU的ID寄存器这个动作是否应该陷阱到虚拟机管理程序,或者是否可以由VM直接读取它们。能够根据软件配置配置这些陷阱很有用,因为虚拟机管理程序可以选择模拟与VM的底层硬件CPU完全相同的CPU,在这种情况下,在ID寄存器读取时,不需要从陷阱中引入额外的性能损失,管理程序也可以选择仅向VM公开硬件CPU功能的子集,在这种情况下,管理程序将要模拟ID寄存器的值。作为另一个示例,管理程序软件可以选择性地选择捕获对浮点寄存器的访问或允许对VM的这些寄存器的完全访问。当在多个VM之间复用单个浮点寄存器库时,这很有用,因为浮点寄存器通常很大,因此保存和恢复到内存很昂贵。允许浮点寄存器访问上的可配置陷入,允许管理程序仅按需上下文切换浮点寄存器,同时一旦寄存器状态属于VM的执行上下文,就允许对VM的浮点硬件的完全访问。从VM到虚拟机管理程序的陷入类似于从用户空间到内核的陷入,并更改CPU模式并跳转到内存中的预定义位置,称为异常向量。
要运行VM,虚拟机管理程序必须从Hyp模式配置陷入(traps)和内存虚拟化。并执行异常返回用户或内核模式,具体取决于VM是要运行其用户空间还是内核。然后,VM在用户和内核模式下正常执行,直到达到需要管理程序干预的某些条件。此时,硬件陷入Hyp模式,从而控制管理程序,然后管理程序可以管理硬件并在VM之间提供所需的隔离。一旦管理程序处理了该条件,管理程序就可以执行另一个异常返回到用户或内核模式,然后VM可以继续执行。并非所有陷阱和异常都由Hyp模式处理。例如,系统调用或页面错误导致的陷阱直接从用户模式陷阱到VM的内核模式,以便客户操作系统处理它们而无需管理程序的干预。这可以避免在每次系统调用或页面错误时进入Hyp模式,从而减少虚拟化开销。同时,可以将硬件中断配置为始终陷入Hyp模式,以便管理程序可以保持对硬件的控制。
ARM体系结构允许对从用户和内核模式到Hyp模式的事件和指令陷阱进行细粒度控制。可以禁用进入Hyp模式的所有陷阱,并且单个非虚拟化内核可以在内核模式下运行并完全控制系统,从而有效地绕过Hyp模式。这是例如Linux在未运行虚拟机管理程序时使用的配置。
ARM围绕与现有内核模式不同的独立CPU模式设计了虚拟化支持,因为架构师在更复杂的丰富OS内核下设想了一个独立的虚拟机管理程序,因为它非常适合架构的现有权限层次结构[38]。为了简化芯片实现并减少验证空间,与内核模式相比,ARM减少了Hyp模式下可用的控制寄存器数量。例如,ARMv7及更高版本的内核模式具有两个页表基址寄存器,为内核和用户空间提供上下虚拟地址空间,但Hyp模式只有一个页表基址寄存器。 ARM还重新定义了Hyp模式使用的页表条目中权限位的含义,因为它们没有设想管理程序与用户空间中运行的软件共享页表。例如,Linux以内核模式运行,但用户模式和内核模式都使用相同的页表,并且通过使用页表中的权限位来防止用户空间访问内核数据和代码,这会阻止用户模式访问内核内存,但允许内核直接访问用户空间内存,这是许多Linux系统调用(如read或write)的一种非常常见的操作。
ARM体系结构通过单个地址转换阶段提供虚拟内存支持,该阶段转换使用页表将虚拟地址转换为物理地址。 虽然可以在VM的上下文中重用现有的虚拟内存支持,但它需要捕获VM对虚拟内存控制寄存器的访问并构建影子页表。 已知影子页表会增加性能开销并显着增加实现复杂性[1]。 因此,让VM管理自己的虚拟内存结构而不会陷入虚拟机管理程序,同时允许虚拟机管理程序完全控制物理内存资源非常重要。 通过在管理程序的控制下在硬件中实现第二阶段的地址转换,可以满足这两个要求。
ARM虚拟化扩展提供第2阶段转换,作为在Hyp模式下运行的虚拟机管理程序的机制,以完全控制对物理内存的所有访问。通过两个转换阶段,第一阶段将虚拟地址(VA)转换为访客物理地址(GPA),第二阶段将GPA转换为物理地址(PA)。 ARM体系结构有自己的命名法,与常用术语略有不同。ARM使用术语中间物理地址(IPA)代替GPA。图2.3显示了完整的地址转换序列,它使用阶段1转换的三级分页和阶段2转换的四级分页。每个翻译阶段的数量级别可以根据VA,GPA和PA空间的大小而变化。启用阶段2转换后,使用一组专用页表将所有GPA从内核和用户模式转换为PA。可以独立启用和禁用第1阶段和第2阶段的转换。禁用第1阶段转换时,VA和GPA是相同的,类似地,当禁用第2阶段转换时,GPA和PA是相同的。 ARMv7第2阶段页表使用LPAE [22]页表格式,而在64位模式下执行的ARMv8使用VMSAv8-64转换表格式[13],这是LPAE格式的略微扩展版本。阶段2页表使用与阶段1页表不同的格式。
运行VM时,VM管理自己的第1阶段页表和第1阶段内存控制寄存器,而不会捕获到虚拟机管理程序。第1阶段页表将VAs转换为GPA,然后使用第2阶段翻译系统将其进一步转换为PA。阶段2转换只能从Hyp模式配置,并且可以完全禁用和启用。因此,管理程序管理GPA到PA转换。在内核和用户模式下执行的非虚拟化操作系统可以在禁用第2阶段转换时直接管理物理内存。 Hyp模式使用自己的地址转换机制,它仅使用特殊页表格式(不同于内核模式使用的第1阶段页表格式)仅支持第1阶段转换,仅用于Hyp模式。由于Hyp模式不支持第2阶段转换,因此Hyp模式第1阶段页表直接从VAs转换为PA,而不通过GPA,因为在Hyp模式下运行的管理程序可以完全控制物理内存。阶段2翻译(如阶段1翻译)可以缓存在TLB数据结构中以提高性能。为了避免在VM之间切换时无效TLB,ARM支持VMID,VMID由管理程序基于每个VM配置,并且所有TLB条目都标记有与当前阶段2上下文关联的VMID。
阶段1和阶段2页表都允许使用一组内存权限和属性标记每个页面。 第2阶段页表可以覆盖第1阶段页表上的权限,例如将读/写页标记为只读。 这对于页面重复数据删除和交换等技术非常有用。 内存属性配置内存访问是可缓存还是不可缓存,这意味着它们可以绕过缓存并直接访问主内存。 遗憾的是,ARMv8.4之前的第2阶段页表不会覆盖第1阶段属性的属性,而是以最强的语义优先。 例如,阶段1中的不可缓存映射不能被修改为通过其阶段2映射可缓存,因为不可缓存被认为是更强的存储器属性。 此设计决定限制了模拟某些类型的支持DMA的设备(如VGA帧缓冲器)的能力[87]。
中断用作从其他硬件组件到一个或多个CPU的异步通知机制。 例如,I / O设备可以向CPU通知需要CPU注意的某些事件,并且一个CPU可以向另一个CPU发出信号以请求某些操作。 在虚拟化的环境中,管理程序通常管理能够生成中断的所有硬件组件,因此即使在运行VM时,仍然必须通知管理程序来自这些组件的中断。 同时,VM将与一组虚拟设备进行交互,并期望来自这些虚拟设备的通知以中断的形式传递到虚拟CPU。 因此,区分和支持由硬件组件生成并传送到物理CPU的物理中断以及由虚拟设备生成并传送到虚拟CPU的虚拟中断非常重要。
ARM定义了通用中断控制器(GIC)v2和v3架构[9,12],它支持处理物理和虚拟中断。 GIC路由从设备到CPU的中断,CPU查询GIC以发现中断源。 GIC在多核配置中尤为重要,因为它用于从一个CPU到另一个CPU生成处理器间中断(IPI)。 GIC分为两部分:分配器和CPU接口。系统中只有一个分配器,但每个CPU都有一个GIC CPU接口。在GICv2上,CPU接口和分配器都通过内存映射接口(MMIO)进行访问。在GICv3上,通过系统寄存器访问CPU接口,仍然使用MMIO访问分配器。分发器用于配置GIC,例如配置中断的CPU亲和性,完全启用或禁用系统中断,或将IPI发送到另一个CPU。 CPU接口用于确认(ACK)和停用中断(中断结束,EOI)。例如,当CPU接收到中断时,它将读取GIC CPU接口上的特殊寄存器,该寄存器确认中断并返回中断号。在CPU使用从ACK寄存器检索到的值写入CPU接口的EOI寄存器之前,不会再次将中断引发到CPU,从而取消激活中断。
这是一个略微简化的视图,因为GIC支持称为拆分优先级丢弃和停用的概念。
一旦确认了中断,它就会进入激活状态,并且在中断被禁用之前,GIC不会再次向CPU发出中断信号。通常,写入EOI寄存器将禁用中断并执行优先级丢弃,这意味着GIC将接受与最近确认的中断具有相同优先级的其他中断。但是,通过拆分优先级丢弃和取消激活,对EOI寄存器的写操作将仅降低优先级,但不会取消激活中断。这有助于推迟处理中断的某些部分,并确保在某些事件通过之前不会再次看到特定的中断。例如,这可以用于通过将acked中断切换到不同的线程来实现线程中断处理,并且能够获取相同优先级的新中断。通过拆分优先级丢弃和取消激活,软件必须在写入EOI寄存器后写入单独的寄存器DIR寄存器。
GIC可以通过两个独立的信号IRQ和FIQ向每个CPU发出中断信号。软件可以将GIC配置为使用中断组将特定中断发送为IRQ或FIQ。 (我们避免在此处对中断组及其与安全性扩展的关系进行错综复杂的讨论,参考体系结构手册以获取更多信息。)每个信号IRQ和FIQ可以独立配置为陷阱到Hyp或内核模式。将所有中断捕获到内核模式并让内核模式下运行的OS软件直接处理它们是有效的,但是在VM的上下文中不起作用,因为管理程序失去了对硬件的控制。将所有中断捕获到Hyp模式可确保虚拟机管理程序保留控制权,但需要在软件中模拟虚拟中断以向VM发送事件信号。但这非常麻烦而且代价高昂,因为中断和虚拟中断处理的每个步骤(例如确认和去激活)必须通过管理程序。
GIC包括v2.0及更高版本的硬件虚拟化支持,称为GIC虚拟化扩展(GIC VE),它避免了模拟虚拟中断传送到VM的必须性。 GIC VE为每个CPU引入了一个虚拟GIC CPU接口,并为每个CPU引入了相应的管理程序控制接口。 VM配置为查看虚拟GIC CPU接口而不是真正的GIC CPU接口。通过写入GIC管理程序控制接口中的特殊寄存器(列表寄存器(LR))生成虚拟中断,虚拟GIC CPU接口将虚拟中断直接引发到VM的内核模式。由于虚拟GIC CPU接口包括对ACK和EOI的支持,因此这些操作不再需要陷入到虚拟机管理程序以在软件中进行仿真,从而减少了在CPU上接收中断的开销。例如,模拟的虚拟设备通常通过软件API向管理程序引发虚拟中断,管理程序可以通过将模拟设备的虚拟中断号写入列表寄存器来利用GIC VE。进入VM后,GIC VE直接将VM中断到内核模式,并允许客户操作系统确认并停用虚拟中断,而不会捕获到虚拟机管理程序。请注意,仍必须在软件中模拟分发服务器,并且VM对分发服务器的所有访问仍必须陷入到管理程序。例如,当虚拟CPU将虚拟IPI发送到另一个虚拟CPU时,这将导致虚拟机管理程序陷入,该虚拟机管理程序模拟软件中的分发服务器访问,并将IPI作为虚拟IPI提升到接收虚拟CPU。如果接收虚拟CPU正在VM内执行,则涉及将物理IPI发送到正在执行虚拟CPU的CPU,这会导致VM虚拟CPU陷入虚拟机管理程序,检测挂起的虚拟IPI,并编写列表寄存器在接收CPU的GIC管理程序控制接口上使用虚拟IPI。
图2.4显示了使用虚拟化扩展的GICv2的简化概述。 GIC中有三种类型的中断:专用外设中断(PPI),共享外设中断(SPI)和软件产生的中断(SGI)。器件可以向PPI发送PPI或SPI信号,只有每个SPI有一条中断线,每个处理器的每个PPI都有一条线,它们的中断号分配不同。例如,每个CPU可能有一个定时器,并且定时器中断与PPI相关联,因此它可以使用相同的中断号独立地向CPU发送信号。由于希望将IPI从一个CPU发送到另一个CPU的CPU的MMIO命令,SGI从GIC分配器内生成。分发器维护每个中断的状态和配置,并将挂起和启用的中断转发到接收CPU的CPU接口。 CPU接口使用IRQ和FIQ线路向CPU发出信号,但也可以通过MMIO从CPU访问它们,以确认和停用中断。直接在硬件上运行的操作系统访问真实的CPU接口,但VM中的客户操作系统访问虚拟CPU接口。由于GICv2访问都基于MMIO,因此管理程序使用第2阶段页表来确保VM只能访问虚拟CPU接口,并且VM无法访问控制接口。图2.4中显示的MMIO路径(由粗蓝线表示)显示了VM和管理程序配置,其中CPU在执行管理程序时访问管理程序控制接口,并在运行VM时访问虚拟CPU接口。列表通过管理程序控制接口进行控制,将内容提供给虚拟CPU接口,CPU在运行VM时会观察到这一点。
设备可以通过两种方式实现中断信号:边沿触发和电平触发中断。当观察到上升沿时,边沿触发的中断变为挂起,并且当线路降低并随后再次上升时,仅发出新的中断信号。只要线路的电平是高,电平触发的中断就会挂起,当线路的电平为低时,它会丢失它们的挂起状态。这意味着虽然GIC只需要观察边沿触发输入的变化,但它必须不断地对电平触发中断的线路电平进行采样。为了支持在VM上下文中对线路级进行采样,GIC VE在列表寄存器中支持一个额外的位EOI位,当VM EOI设置了该位的中断时,该位会导致到管理程序的陷阱。从GIC VE生成的陷阱使用专用硬件中断(称为GIC维护中断)发出信号。管理程序可以将列表寄存器中的EOI位设置为电平触发中断,以在完成电平触发中断时强制退出VM,然后管理程序可以重新采样相应中断的虚拟线状态,并注入如果电平仍然保持高电平,则会产生新的中断。因此,在VM的上下文中,级别触发的中断比边缘触发的中断更慢且更复杂。
中断实际上可以通过在上升沿或下降沿发出中断信号来实现不同的电气实现,或类似地通过在线路为低或高时考虑电平触发的断言来实现,并且GIC支持两种配置。 这种差异对软件设计或性能没有影响,因此我们将讨论局限于使用上升沿或高电平来断言中断的中断。
GIC VE还在列表寄存器中提供了一个额外的特殊位,称为HW位。此功能特定于ARM体系结构,是虚拟化计时器硬件的基础,因此值得一提。其思想是虚拟中断可以与物理中断相关联,这样当VM停用虚拟中断时,也可以停用相应的物理中断。当管理程序收到应作为虚拟中断转发给VM的物理中断时,管理程序可以选择不停用物理中断,但是将其推迟到VM停用虚拟中断。这是通过将列表寄存器中的HW位设置为用于发送虚拟中断并将虚拟中断号链接到物理中断号来完成的,在列表寄存器中使用两个单独的专用字段。 (请注意,由于VM可能呈现的中断号与物理主机的中断号不同,因此虚拟和物理中断号可能不同。)此机制对于不希望被设备中断直到VM的虚拟机管理程序非常有用。已完成处理先前的中断,这通常是当VM暴露于底层硬件的任何方面时的情况,例如在定时器或直接设备分配的上下文中。
系统软件需要一些机制来保持时间。提供系统时间以及可编程定时器产生的中断对于给应用程序提供服务的OS来讲是非常重要的。例如,图形用户界面(GUI)通常编程定时器以在编辑文本时使光标闪烁,并且OS内核通常在各个检查点检查进程运行的时间,以根据调度策略来调度任务。在运行VM时,虚拟机管理程序对两种计时机制都有类似的要求,并且在VM内运行的guest操作系统也需要保持时间。但是,时间的定义可以取决于底层硬件,可以从提供的时间范围从墙上时间到CPU的周期,或者使用其他可能停止计数或根据硬件系统的电源状态以较慢速率计数的恒定定时器。此外,当运行VM时,VM可能无法连续运行,因为管理程序可能会运行其他任务而不是VM,而不会通知VM,因此,墙上时间可能与虚拟处理时间无关它与原生处理时间有关。
ARM通用定时器架构(也称为架构定时器)包括对定时器虚拟化的支持。 目标是为VM提供对其自己的计时硬件的访问权限,以便VM可以告诉时间和程序计时器而不会陷入虚拟机管理程序,并允许VM分辨虚拟时间和物理时间的某些概念之间的差异[68]。 为了实现这一目标,ARM提供了三个独立的定时器:管理程序计时器,物理计时器和虚拟计时器。 这个想法是管理程序计时器代表实际的时间流逝,并且由管理程序专门拥有,其中物理和虚拟计时器都由VM使用。 物理计时器还表示实际的时间流逝,其中虚拟计时器可以由管理程序调整以提供虚拟时间的度量,例如基于实际允许VM运行的时间。
更具体地说,ARM上的每个定时器包括计数器和计时器。计数器只是一个向上计数的器件,定时器是一个小的逻辑电路,可以对其进行编程,以便在计数器达到预设值时产生中断。管理程序计时器只能从Hyp模式访问,任何从内核或用户模式对其进行编程的尝试都会产生内核模式的异常。管理程序可以配置在VM中执行时是否在访问物理定时器时陷入,或者VM是否可以访问物理定时器。虚拟计时器始终可在VM中访问。管理程序可以编程一个特殊的虚拟计数器偏移寄存器,只能在Hyp模式下访问,它会改变虚拟定时器相对于物理定时器的计数器值。管理程序可以使用此功能,例如,当VM尚未运行时从虚拟计数器中减去时间,并且VM可以使用此信息来调整其调度策略或报告被盗时间(stolen time),对用户来讲是虚拟CPU没有运行的时间。
虽然对定时器的虚拟化支持允许VM直接对定时器进行编程而不进行陷入,但是当定时器触发时,管理程序仍然涉及向VM发送中断信号。当定时器触发时,它们会产生硬件中断。这些硬件中断与系统上的其他硬件中断没有什么不同,无论发出信号的定时器如何,因此遵循系统配置,执行本机操作系统时,可以直接传送到内核模式;当执行VM时,陷入到虚拟机管理程序。当管理程序处理来自分配给VM的计时器设备(例如虚拟计时器)的中断时,它通常使用GIC VE编程虚拟中断以向VM发信号通知计时器已到期。但是,由于定时器输出信号是电平触发的,定时器输出将保持有效,直到可以直接访问定时器硬件的VM取消定时器或将其重新编程为将来触发。如果管理程序代表VM取消激活中断,GIC将从定时器重新对输出行进行采样,并且由于它仍处于断言状态,它将立即向CPU发出新的待处理中断信号,并且CPU不会使任何前进的进展,因为它将忙于处理连续的中断流。 ARM体系结构专门用于将定时器的功能与GIC集成,方法是将分配给VM的定时器的物理中断保持活动状态,从而防止进一步发出中断信号,并将定时器的虚拟中断注入VM,通过设置列表寄存器中的HW位,将其与基础物理中断相关联。这样,当VM运行并观察挂起的虚拟定时器时,它将运行定时器的中断服务程序(ISR),取消定时器,降低定时器输出信号,并最终停用中断,例如通过写入到虚拟CPU接口上的EOI寄存器。由于HW位已设置,因此取消激活虚拟中断会禁用物理和虚拟中断,并且当计时器稍后再次触发时,它将陷入虚拟机管理程序。
每个CPU都有自己的一组专用定时器,在跨物理CPU迁移虚拟CPU时,管理程序负责正确迁移和同步定时器。
Virtualization Support |
ARM VE |
Intel VMX |
CPU |
Separate CPU Mode |
Duplicated CPU modes (root vs. non-root operation) |
VM/hypervisor transition |
Trap (software save/restore) |
VMX Transition (hardware save/restore with VMCS) |
Memory |
Stage 2 page tables |
EPT (2nd+ gen.) |
Direct Timer Access |
Physical/Virtual Timers |
No equivalent support |
Interrupts |
VGIC |
APICv |
Direct virtual interrupt injection |
GICv4 |
Posted Interrupts |
Table 2.1: Comparison of ARM VE with Intel VMX
ARM虚拟化扩展与来自Intel和AMD的x86的硬件虚拟化支持之间存在许多相同点和不同点。 Intel VMX [50]和AMD-V [2]非常相似,因此我们将其与ARM VE和Intel VMX的比较限制在一起。差异总结在表2.1中。 ARM通过单独的CPU模式Hyp模式支持虚拟化,Hyp模式是一种独立且严格更高权限的CPU模式,而不是之前的用户和内核模式。相比之下,英特尔具有root和非root模式[50],它们与CPU保护模式正交。虽然ARM上的敏感操作陷阱到Hyp模式,但敏感操作可以从non-root模式陷入root模式,同时在Intel上保持相同的保护级别。两种硬件设计之间的关键区别在于,英特尔的根模式支持与非根模式相同的全范围用户和内核模式功能,而ARM的Hyp模式是一种严格不同的CPU模式,具有自己的一组功能。例如,在Intel根模式下执行的软件使用与在非根模式下执行的软件相同的虚拟内存系统和相同的虚拟地址布局。在ARM上,Hyp模式使用不同的页表,并且与内核模式相比具有有限的VA空间。这些差异表明,英特尔想象任何软件堆栈,类似于现有的操作系统,以root和非root模式运行,其中ARM想象一个较小的独立虚拟机管理程序以root模式运行。
ARM和Intel都陷入了各自的Hyp和root模式,但Intel为内存中的VM控制结构(VMCS)提供了特定的硬件支持,用于在切换到root模式和从root模式切换时自动保存和恢复CPU的执行状态。 VMX转换VM Entry和VM Exit。这些转换在硬件中定义为单个原子操作,用于在guestOS和管理程序执行上下文之间切换时,使用VMCS自动保存和恢复所有CPU的执行状态。 ARM是一种RISC风格的体系结构,它具有更简单的硬件机制,可以在EL1和EL2之间进行转换,但需要通过软件来决定需要保存和恢复哪个状态。与在x86上的根和非根模式之间切换相比,这在EL1和EL2之间转换时需要完成的工作量提供了更大的灵活性,但对管理程序软件实现提出了不同的要求。例如,与在优化的x86服务器硬件上从根模式转换到非根模式的数百个周期相比,在第一代ARM VE硬件上捕获ARM的Hyp模式仅花费数十个周期。 ARM和英特尔在支持虚拟化物理内存方面非常相似。两者都引入了一组额外的页表来将guest虚拟机转换为主机物理地址。 ARM在第二阶段翻译中得益于后见之明,而英特尔在其第二代虚拟化硬件之前不包括其等效的扩展页表(EPT)支持。
ARM对虚拟定时器的支持没有真正跟x86对应。 x86计时世界由无数的计时设备组成,部分原因在于PC平台的历史和遗产。现代x86平台通常支持8250系列PIT用于传统支持和本地APIC(LAPIC)计时器。英特尔对虚拟化的硬件支持增加了VMX-Preemption计时器,该计时器允许虚拟机管理程序独立于其他计时器的编程方式对VM的退出进行编程。添加了VMX-Preemption计时器以减少计时器触发和注入虚拟计时器中断的管理程序之间的延迟。这是因为管理程序不必处理来自LAPIC计时器的中断,而是可以直接从VM出口告知抢占计时器已到期。与ARM相反,x86不支持完全控制VM的各个定时器硬件。 x86允许VM直接读取时间戳计数器(TSC),而ARM允许访问虚拟计数器。 x86 TSC的分辨率通常高于ARM计数器,因为TSC由处理器的时钟驱动,其中ARM计数器由专用时钟信号驱动,尽管频率最低为50 MHz。
最近的英特尔CPU还包括对APIC虚拟化的支持[50]。 与ARM的GIC VE虚拟中断支持类似,英特尔的APIC虚拟化支持(APICv)还允许虚拟机完成虚拟中断,而不会陷入虚拟机管理程序。 与ARM类似,APICv允许VM直接访问中断控制器寄存器而无需捕获到管理程序。 在ARM上,GIC虚拟CPU接口支持直接访问所有CPU接口寄存器。 APICv在内存中为VM的虚拟APIC状态提供后备页面。 在没有APICv的Intel上,完成VM中的虚拟中断陷阱到root模式。
ARM和x86都引入了对直接传递虚拟中断的支持。 直接传送意味着执行VM的CPU可以观察到新的虚拟中断,该虚拟中断直接从直通设备向VM发送信号,而无需从VM捕获到管理程序。 英特尔已将已发布的中断作为英特尔定向I / O架构的一部分[51]。 发布中断支持直接从物理设备或其他CPU传递虚拟中断。 ARM推出了GIC架构4.0版本(GICv4)[12],它支持从信令MSI的物理设备直接传递虚拟消息信号中断(MSI)。 遗憾的是,在撰写本论文时,没有可用的GICv4硬件,因此我专注于GICv2和GICv3,可以使用它们来验证和评估虚拟化软件。
KVM / ARM不是在虚拟机管理程序中重新实现复杂的核心功能(这样做的话潜在地引入棘手和致命的错误),而是构建在原有KVM上并利用Linux内核中的现有基础架构。虽然独立的虚拟机管理程序设计方法具有更好的性能和更小的可信计算基础(TCB)的潜力,但这种方法在ARM上不太实用。 ARM硬件在很多方面比x86更加多样化。硬件组件通常由不同的设备制造商以非标准方式紧密集成在ARM设备中。例如,许多ARM硬件缺乏硬件发现功能,例如标准BIOS或PCI总线,并且没有建立在各种ARM平台上安装低级软件的机制。但是,几乎所有ARM平台都支持Linux,并且通过将KVM / ARM与Linux集成,KVM / ARM可以在运行最新版本Linux内核的任何设备上自动使用。这与Xen [26]等裸机方法形成对比,后者必须积极支持他们希望安装Xen管理程序的每个平台。例如,对于Xen希望支持的每个新SoC,开发人员必须在核心Xen虚拟机管理程序中实现SoC支持,以支持在串行控制台上简单地输出内容。在ARM平台上引导Xen通常还涉及手动调整引导加载程序或固件,因为Xen需要同时引导两个映像,Xen和Linux Dom0内核映像。另一方面,KVM / ARM与Linux集成,并遵循现有支持的Linux内核引导机制。
虽然KVM / ARM在可移植性和硬件支持方面与Linux集成得益,但我们必须解决的一个关键问题是ARM硬件虚拟化扩展旨在支持独立的虚拟机管理程序设计,其中虚拟机管理程序与任何标准内核完全分离功能,如2.1节所述。 以下部分描述了KVM / ARM的新颖设计如何能够从与现有内核的集成中受益,同时利用硬件虚拟化功能。
完全在ARM的Hyp模式下运行虚拟机管理程序很有吸引力,因为它专门设计用于运行虚拟机管理程序,并且比用于运行VM的其他CPU模式更具特权。 但是,由于KVM / ARM利用现有的内核基础结构(如调度程序和设备驱动程序),因此在Hyp模式下运行KVM / ARM需要在Hyp模式下运行整个Linux内核。 这有问题至少有两个原因:
首先,Linux中的低级体系结构相关代码被编写为在内核模式下工作,并且在Hyp模式下不能未经修改地运行,因为Hyp模式是与正常内核模式完全不同的CPU模式。 例如,内核的异常进入路径,进程切换逻辑和许多其他内核子系统访问内核模式寄存器,并且必须进行修改才能在Hyp模式下工作。 修改完整的操作系统内核(如Linux内核)以在Hyp模式下运行是非常有争议性的。更重要的是,为了保持与没有Hyp模式的硬件兼容并将Linux作为客户操作系统运行,低级代码必须是兼容在Hyp和内核模式下工作,可能导致缓慢和复杂的代码路径。 举个简单的例子,页面错误处理程序需要获取导致页面错误的虚拟地址。 在Hyp模式下,该地址存储在与内核模式不同的寄存器中。
其次,在Hyp模式下运行整个内核会对本机性能产生负面影响。例如,Hyp模式有自己独立的地址空间。内核模式使用两个页表基寄存器来提供用户和内核虚拟地址空间之间的分割,而Hyp模式使用单页表基址寄存器,因此不能轻易地直接访问用户地址空间。支持来自内核的用户访问将涉及使用来自不同翻译机制的相同页表,这放松了隔离保证并增加了TLB无效频率,或修改了常用功能以访问用户存储器,以在软件中历遍处理页表并明确地映射用户空间数据进入内核地址空间,随后执行必要的页表拆卸和TLB维护操作。这两个选项都可能导致ARM本机性能不佳,并且难以集成到主线Linux内核中。第4章更详细地探讨了在ARMv8等效于Hyp模式(EL2)中运行Linux的挑战。
在hypervisor模式下运行Linux的这些问题不会出现在x86硬件虚拟化中。 x86根操作与其CPU权限模式正交。 整个Linux内核可以在root操作中运行,因为在root和非root操作中都有相同的CPU模式集。
KVM / ARM引入了分离模式虚拟化,这是一种新的虚拟机管理程序设计方法,可以分离核心虚拟机管理程序,使其在不同的特权CPU模式下运行,以利用每种CPU模式提供的特定优势和功能。 KVM / ARM使用分离模式虚拟化来利用Hyp模式支持的ARM硬件虚拟化支持,同时利用在内核模式下运行的现有Linux内核服务。 分离模式虚拟化允许KVM / ARM与Linux内核集成,而无需对现有代码库进行破坏性的修改。
这是通过将虚拟机管理程序拆分为两个组件(lowvisor和highvisor)来完成的,如图2.5所示。 lowvisor旨在利用Hyp模式中提供的硬件虚拟化支持来提供三个关键功能。首先,lowvisor通过适当的硬件配置来设置正确的执行上下文,并在不同的执行上下文之间实施保护和隔离。 lowvisor直接与硬件保护功能交互,因此非常关键,代码库保持在绝对最小值。其次,lowvisor从VM执行上下文切换到主机执行上下文,反之亦然。主机执行上下文用于运行管理程序和主机Linux内核。主机执行上下文和VM执行上下文之间的转换通常被称为世界交换,因为整个内核和用户模式配置被更改,给人以在世界之间移动的印象。由于lowvisor是唯一在Hyp模式下运行的组件,因此只有它可以负责执行世界切换所需的硬件重配置。第三,lowvisor提供了一个虚拟化陷入处理程序,它处理必须陷入虚拟机管理程序的中断和异常。低级管理程序仅执行所需的最少量处理,并且在世界切换到高级管理程序完成后,将大部分工作推迟到高级管理员。
作为主机Linux内核的一部分,highvisor以内核模式运行。 因此,它可以直接利用现有的Linux功能(如调度程序),并可以利用标准内核软件数据结构和机制来实现其功能,例如锁定机制和内存分配功能。 这使得更高级别的功能更容易在高级管理器中实现。 例如,当lowvisor提供低级陷阱处理程序和从一个世界切换到另一个世界的低级机制时,高级管理程序处理来自VM的第2阶段页面错误并执行指令仿真。 请注意,VM的某些部分在内核模式下运行,就像高级管理程序一样,只是启用了阶段2转换和捕获到Hyp模式。
由于管理程序在内核模式和Hyp模式之间分离,因此在VM和highvisor之间进行切换涉及CPU模式之间的多个转换。 在运行VM时,陷入highvisor的时候会先陷入lowvisor的HYP模式运行。 然后,在重新配置内核模式的保护设置后,lowvisor将执行异常返回内核模式以运行highvisor。 类似地,从highvisor到VM需要从内核模式捕获到Hyp模式,然后切换到VM。 因此,分离模式虚拟化会导致切换到highvisor和从highvisor切换双陷入成本。 在ARM上从内核模式进入Hyp模式的唯一方法是引发陷入,例如通过发出超级调用(HVC)指令,因为Hyp模式是一种更高特权的模式,因此CPU保护机制是专门设计的。 但是,如第2.4节所示,这个额外陷入对ARM来说并不是一个显着的性能成本。
我们将此成本称为双陷阱成本,即使它在技术上是陷阱和异常返回的问题,但是,由于异常返回是具有与陷阱类似的性能特征的指令同步事件,因此该术语仅具有轻微的误导性。
当从highvisor进入VM时,通过lowvisor,highvisor和lowvisor必须相互通信。例如,highvisor必须指定即将运行的VCPU的状态。 KVM / ARM使用内存映射接口在highvisor和lowvisor之间共享数据。在Hyp模式下运行的lowvisor使用单独的虚拟地址空间,由lowvisor本身使用控制寄存器配置。一种简单的方法是重用主机内核的页表,并在Hyp模式下使用它们以使地址空间相同。遗憾的是,这不起作用,因为Hyp模式使用与内核模式不同的页表格式。但是,Hyp模式使用的页表只是内存中的页面,并且highvisor可以完全访问系统上的所有内存,因此可以自由配置Hyp模式使用的页表。这是一个微妙但重要的见解。即使Hyp模式管理虚拟内存控制寄存器,只要highvisor知道页表的地址,highvisor就可以配置Hyp模式的虚拟内存映射。唯一的限制是,如果更改任何已建立的映射,则highvisor必须要求Hyp模式执行特定于Hyp的TLB失效,因为Hyp模式TLB失效指令仅在Hyp模式下可用。 KVM / ARM分配一整套页表以在引导期间映射完整的Hyp VA空间,并在lowvisor的初始配置期间将指针传递给lowvisor上的页表。现在,highvisor可以分配一个内存页面,将其映射到lowvisor的地址空间和的地址空间,并将指针从highvisor的地址空间转换为lowvisor的地址空间,并将指针传递给lowvisor,然后访问共享页面中的数据。highvisor还使用该技术在lowvisor的初始配置期间映射代码段。
剩下的问题是如何为lowvisor建立一个有效的运行时。 最初的KVM / ARM手写汇编代码来实现仅映射单页,以实现低级管理程序功能,并在每次创建新VM时以Hyp模式映射每个VM的共享数据结构。 事实证明,在lowvisor中运行C代码只需要在单独的部分中链接lowvisor代码,以便它可以在Hyp模式下映射,在Hyp模式下映射只读段,并映射每个CPU的页面以用作 Hyp模式下的堆栈。 使用C代码可以更轻松地维护lowvisor实现并添加逻辑,例如支持调试VM。 请注意,即使在C语言中实现lowvisor时,它也无法自由访问所有内核数据结构或调用主机操作系统中的内核函数,因为它仍然在单独的地址空间中运行,只有lowvisor运行时和明确共享的数据结构是可用。
要虚拟化CPU,KVM / ARM必须提供与VM的接口,该接口与底层实际硬件CPU(等效属性)基本相同,同时确保hypervisor保持对硬件的控制(隔离属性)。 CPU状态包含在寄存器中,包括通用寄存器和控制寄存器,KVM / ARM必须捕获和模拟对这些寄存器的访问或上下文切换它们。 由于Hyp模式与内核和用户模式完全分离,使用其自己的配置寄存器状态,所以在VM和主机之间转换时,所有内核和用户模式状态都可以由lowvisor进行上下文切换。 但是,一些不经常使用的寄存器可以配置为在访问时捕获,并在此以后切换上下文以提高性能。
即使Hyp模式与VM分离,lowvisor仍然必须确保它最终将重新获得对CPU的控制,比如在硬件定时器触发中断时陷入lowvisor。 lowvisor通过将CPU配置为在接收物理中断时捕获到Hyp模式,并且在调度计时器中断时陷入来实现此目的。 这样,即使VM运行无限循环,例如,硬件总是可以最终陷入lowvisor,这可以抢占VM并切换回highvisor。
表2.2和表2.3显示了在主机和VM上以内核和用户模式运行的软件可见的CPU状态,在VM和主机之间切换时必须进行上下文切换。Number of register指定了每种类型的寄存器数量。当从VM转换到主机时,这些寄存器中的每一个都可以被读取和写入,反之亦然。比如说任何的通用寄存器。KVM / ARM必须首先读取VM的GP寄存器状态并将其存储到存储器,然后将主机的GP寄存器状态从存储器加载到寄存器,然后返回到highvisor。返回VM后,KVM / ARM必须再次读取主机的GP寄存器状态并存储内存,最后将VM的GP寄存器状态从内存加载到寄存器,然后返回VM。无论每个寄存器被访问多少次,表2.2和2.3都会对每个寄存器进行一次计数。此外,这些表仅包括在VM中的正常操作下运行工作负载的最小寄存器集,但不包括更高级的功能,例如使用性能监视单元(PMU)监视VM中的性能或调试VM。
VFP浮点寄存器的保存和恢复成本很高,VM并不总是使用它们。 因此,KVM / ARM执行这些寄存器的延迟上下文切换,这意味着寄存器最初配置为在访问时陷入到lowvisor,并且lowvisor不会在VM和管理程序之间的每次转换时保存和恢复它们。 当VM最终尝试访问浮点寄存器时,它会陷入到lowvisor,然后上下文切换主机和VM之间的所有VFP寄存器状态,禁用VFP寄存器上的陷入,然后返回到VM,此时的VM可以直接访问寄存器了。
KVM / ARM对某些敏感指令和寄存器访问执行陷入,如表2.4所示。如果VM执行WFI指令导致CPU断电,则KVM / ARM陷入。因为此类操作应只能由管理程序执行以维持对硬件的控制。当物理CPU are oversubscribed 的时候,KVM/ARM执行WFE会陷入。因为由于WFE指令通常用于自旋锁循环,发出指令的guest虚拟机很好地表明VM中的锁是争用的,并且在允许保持VCPU运行之前不会释放。对于超额订购的CPU,运行的VCPU多于物理CPU,KVM / ARM通过调度另一个VCPU来处理来自WFE的陷入。SMC指令被捕获,因为不应允许VM与系统上的任何安全运行时进行交互,但是这些访问应该由hypervisor.模拟。DEBUG和PMU寄存器被捕获,因为性能监视单元和调试硬件属于主机操作系统,并且不支持虚拟化和虚拟机管理程序,因此可以捕获和模拟访客对这些资源的访问。最后,当最初启动VM时,KVM / ARM会捕获对VM的第1阶段MMU控制寄存器的访问权限,并且上下文也会切换这些寄存器,因为虚拟机管理程序必须能够检测VM何时启用第1阶段MMU并执行所需的缓存维护确保guest的连贯内存视图。客户操作系统启用第1阶段MMU后,将禁用陷入,VM可以直接访问MMU控制寄存器。
请注意,VM和管理程序都在内核和用户模式下执行代码,但这些模式的配置取决于执行上下文。 例如,lowvisor配置CPU在切换到VM时捕获WFI指令,并在切换回管理程序时再次禁用这些陷入,让主机OS在需要时关闭CPU。 运行VM和主机的硬件有不同的配置设置,两者都使用相同的内核和用户CPU模式。 在Hyp模式下运行的lowvisor在运行VM时以下列方式配置这些设置:
1.将所有主机GP寄存器存储在Hyp堆栈中。
2.配置VM的VGIC。
3.配置VM的计时器。
4.将所有特定于主机的配置寄存器保存到Hyp堆栈中。
5.将VM的配置寄存器加载到硬件上,这可以在不影响当前执行的情况下完成,因为Hyp模式使用自己的配置寄存器,与主机状态分开。
6.配置Hyp模式以捕获延迟上下文切换,陷阱中断,陷阱CPU暂停指令(WFI / WFE),陷阱SMC指令,陷阱特定配置寄存器访问和陷阱调试寄存器访问的浮点操作。
7.将特定于VM的ID写入影子ID寄存器。
8.设置第2阶段表格基址寄存器(VTTBR)并启用Stage-2地址转换。
9.恢复所有来宾GP寄存器。
10.执行异常返回到用户或内核模式。
CPU将保留在VM世界中,直到发生事件,从而触发陷阱进入Hyp模式。 陷阱可能由表2.4中列出并在上面的步骤6中显示的任何陷阱,第2阶段页面错误或硬件中断引起。 由于事件需要来自管理程序的服务,要么模拟VM的预期硬件行为或服务设备中断,KVM / ARM必须切换回管理程序和主机。 从VM切换回主机会执行以下操作:
1.存储所有VM GP寄存器。
2.禁用第2阶段转换,使主机完全控制系统和所有内存。
3.配置Hyp模式不捕获任何寄存器访问,指令或中断。
4.保存所有特定于VM的配置寄存器。
5.将主机的配置寄存器加载到硬件上。
6.配置主机的定时器。
7.保存特定于VM的VGIC状态。
8.恢复所有主机GP寄存器。
9.执行异常返回内核模式。
KVM / ARM通过在VM中运行时为所有内存访问启用第2阶段转换来提供内存虚拟化。第2阶段转换只能在Hyp模式下配置,其使用对VM完全透明。highvisor管理第2阶段翻译页表,仅允许访问专门为VM分配的内存。其他访问将导致陷入到hypervisor的第2阶段页面错误。此机制可确保VM无法访问属于虚拟机管理程序或其他VM的内存,包括任何敏感数据。在highvisor和lowvisor中运行时,阶段2转换被禁用,因为highvisor可以完全控制整个系统,并使用自己的第1阶段虚拟到物理映射直接管理物理内存。当管理程序执行到VM的世界切换时,它启用阶段2转换并将阶段2页表基址寄存器配置为指向由管理程序为特定VM配置的阶段2页表。尽管highvisor和VM共享相同的CPU模式,但第2阶段转换可确保保护highvisor免受VM的任何访问。
KVM / ARM使用分离模式虚拟化来利用现有内核内存分配,页面引用计数和页表操作代码。 KVM / ARM通过考虑故障的GPA来处理第2阶段页面错误,如果该地址属于VM内存映射中的正常内存,KVM / ARM只需调用现有的Linux内核内存分配函数为VM分配页面,并将分配的页面映射到第2阶段页面表中的VM。此外,Linux还免费带来额外的好处。内核同页合并(KSM)在多个VM中查找相同的页面,并将它们合并到一个页面中以节省内存。页面被标记为只读,如果VM稍后写入页面,则会分成多个页面。透明大页面(THP)通过将连续的物理页面合并为一个更大的页面来提高性能。较大的页面仅占用TLB中的单个条目而不是多个条目,覆盖相同的虚拟地址空间,因此降低了TLB压力。在ARM上,较大的页面在TLB未命中时也需要较少的页面遍历,这也可以提高性能。相比之下,裸机管理程序将被迫静态地为VM分配内存或编写一个全新的内存分配子系统。
KVM / ARM通过利用流程和线程等现有Linux概念来管理VM的内存。每个Linux进程都有自己的虚拟地址空间,该空间在该进程中的所有线程之间共享,这类似于具有单个物理内存资源和多个共享相同内存视图的CPU的机器。 KVM / ARM将每个VM表示为一个进程,将每个VCPU表示为该进程中的一个线程,共享相同的内存管理结构。 KVM / ARM依靠用户空间来管理VM的物理内存映射。用户空间使用标准内存分配函数(例如malloc和mmap)为客户物理内存的每个连续区域分配虚拟内存区域。然后,用户空间将ioctl调用到KVM,告知KVM特定虚拟内存区域应被视为指定偏移处的客户物理内存区域。例如,对于具有2GB连续客户物理内存位于0x40000000的VM,用户空间将分配2GB的虚拟地址空间,并告知KVM在某个虚拟地址处分配的区域对应于2GB的物理内存区域。 0x40000000之后。当VM开始消耗新内存时,当MMU将GPA转换为PA时,将导致第2阶段页面错误。 KVM / ARM低级管理器将从Hyp控制寄存器捕获此GPA,并通过共享存储器接口将其传递给高级管理员。然后,管理员可以查找由用户空间注册的物理内存区域,并计算来自客户物理内存区域的基址的偏移量,在上面的示例中为0x40000000。将偏移量添加到VM进程的虚拟地址空间中的内存区域的地址会导致正常的Linux进程VA,并且KVM通过调用标准Linux功能来分配物理页面,例如, get_user_pages。
KVM / ARM可以非常轻松地利用Linux中更先进的内存管理技术。例如,将页面交换到磁盘(服务器和桌面部署中的一个重要功能)几乎得到了支持,因为Linux只是在内存压力下遵循其正常例程将页面交换到磁盘。与其他托管虚拟机管理程序相比,KVM的一个关键优势是能够更改内核的其他部分以提供虚拟机管理程序功能所需的接口。例如,在Windows上运行的VMware工作站必须依赖Windows内核中的现有API,其中KVM开发人员只是简单地修改了Linux以满足KVM的需求。其中一个特性是Linux内存管理通知器[28],它是Linux内存管理子系统的一个有点侵入性的附加内容,专门用于更好的VM支持。 KVM / ARM使用这些通知程序向Linux内存管理子系统注册,以通知属于正在交换的VM进程的页面。然后,KVM / ARM只需扫描其第2阶段页表以获取到该页面的映射,从第2阶段页表中删除映射并使TLB无效。透明大页面(THP)以类似的方式工作,只要用户空间分配一个大页面大小对齐的虚拟内存区域(在使用4K页面的ARM上通常为2MB),KVM / ARM在分配页面时要求Linux,如果页面属于一个巨大的页面,并且在这种情况下,使用单个阶段2页表项将2MB的物理连续GPA空间映射到PA。
在较高的层面上,KVM / ARM的I / O模型与其他架构的KVM I / O模型非常相似,但底层实现细节略有不同。 KVM大致支持三种不同类型的虚拟化I / O:(1)仿真I / O,(2)半虚拟化I / O,(3)直接设备分配。
主机OS的用户空间支持仿真I / O,其中用户空间KVM驱动程序(通常为QEMU)模拟真实设备。除了直接设备分配(通常仅用于网络适配器等设备的一小部分)之外,KVM / ARM永远不会允许VM直接对设备进行物理访问。因此,当VM尝试与设备通信时,它将陷入虚拟机管理程序。在ARM上,所有I / O操作都基于内存映射I / O(MMIO),并且通过阶段2页表控制对设备的访问。这与x86略有不同,x86除了MMIO之外还使用x86特定的硬件指令(如inl和outl)进行端口I / O操作。当VM访问设备时,它将生成第2阶段缺页异常,该错误在Hyp模式下陷入lowvisor。 lowvisor可以回读系统寄存器,其中包含有关导致陷入的指令的信息,并且仅在Hyp模式下可用。这些寄存器包含重要信息,例如导致故障的地址,访问大小(如果是读取或写入操作)以及哪个寄存器用作访问的源或目标。如果陷入是写入,KVM会将此信息与寄存器有效负载一起转发回主机用户空间应用程序,该应用程序根据异常GPA将请求路由到模拟设备,并且用户空间KVM驱动程序模拟访问。由于这种捕获阶段2异常的路径,将上下文更改回主机操作系统,以及将请求从主机内核转发到主机用户空间要复杂得多,并且比与真实硬件设备交互要慢得多,因此该技术主要使用对于不考虑带宽和延迟的设备。例如,该技术通常用于模拟串行端口(UART),实时时钟和低带宽USB设备,如鼠标和键盘。在x86上,此技术还用于模拟IDE和软盘以支持OS安装程序和早期VM引导代码。
半虚拟化I / O主要用于提高块存储和网络连接的性能。半虚拟化I / O使用专门设计和优化的接口,而不是模拟真实的硬件接口,这可能需要VM和主机OS用户空间之间的许多陷入,以便在VM和管理程序后端之间进行通信。为此目的开发的流行通信协议是Virtio [70,71,79],它描述了一系列设备如何在VM和管理程序边界之间进行有效通信。例如,可以将Virtio设备模拟为模拟ARM SoC上的直接映射MMIO设备,也可以将其模拟为插入虚拟模拟PCI总线的设备。最新版本的KVM / ARM和Linux上支持这两个选项(请注意,ARM上早期版本的Linux仅支持直接映射设备,因为PCI尚未标准化并且在ARM上受支持。)Virtio通过建立共享内存来合并数据传输VM和主机操作系统之间的区域,使得任何一方都可以将大量数据放入存储器并尽可能少地发信号通知另一端。例如,如果VM希望通过网络发送数据,它可以将几个网络数据包放入存储器,最后对Virtio设备中的寄存器执行单次写操作,该寄存器陷入管理程序,导致更少的转换VM和管理程序。对称为VHOST的半虚拟化I / O的进一步改进将大部分设备仿真从主机用户空间移动到主机内核中,这样KVM就不需要转到用户空间来代表VM执行I / O。这通过用于映射内存区域、信号事件和中断的文件描述符与内核中的其他线程通信来工作。 KVM / ARM完全支持VHOST与Virtio over PCI设备,并重用现有的大部分KVM和Linux基础设施。
直接设备分配的工作原理是为VM分配专用硬件设备,让VM直接访问物理设备。 KVM / ARM通过与Linux的集成再次支持此配置。另一个Linux子系统vfio,可以使用户空间进程直接访问外设资源,诸如内存区域,DMA区域和中断之类的设备资源。 VFIO经过精心设计,可以在可以安全分配给VM的设备上运行,而不会违反VM的隔离要求,例如使用IOMMU等安全措施来隔离DMA。 KVM再次受益于将VM表示为进程,并且可以通过设置所需的第2阶段页表映射,轻松检测进程中的VFIO分配设备并建立到VM内部设备的映射。但是,能够执行DMA的设备不使用CPU的第2阶段页表,但可以访问所有系统内存。如果允许VM自由地将任何内存用作DMA的话,则VM可以使用直接分配的设备来中断隔离并获取对敏感信息的访问权限。为避免此问题,仅在具有IOMMU的系统上支持直接设备分配,IOMMU可以转换来自分配给VM的设备的所有DMA请求。 KVM利用VFIO确保IOMMU将指定设备的内存事务限制为KVM VM进程中指定为DMA区域的内存。任何其他访问都会导致IOMMU故障,该故障将陷入虚拟机管理程序。
有关ARM和x86上I / O虚拟化性能的更详细讨论,请参见第3章和第4章。
KVM / ARM利用其与Linux的紧密集成来重用现有的设备驱动程序和相关功能,包括处理中断。 在VM中运行时,KVM / ARM会将CPU配置为将所有硬件中断捕获到Hyp模式。 当发生中断时,CPU以Hyp模式从VM陷阱到lowvisor,该模式切换到highvisor,主机OS处理中断。 在主机和highvisor中运行时,中断直接进入内核模式,避免了通过Hyp模式的开销。 在这两种情况下,所有硬件中断处理都是通过重用Linux现有的中断处理功能在主机中完成的。
但是,VM不时需要来自仿真设备的虚拟中断形式的通知,而多核客户操作系统必须能够将虚拟IPI从一个虚拟核心发送到另一个虚拟核心。 KVM / ARM使用虚拟GIC分配器(VGIC)将虚拟中断注入VM,以减少Hyp模式的陷入数量。如2.1节所述,通过在GIC管理程序控制接口中编程列表寄存器,虚拟中断被引发到虚拟CPU。 KVM / ARM配置阶段2页表以防止VM访问GIC的任何其他部分而不是虚拟CPU接口,包括管理程序控制接口,物理GIC分配器和物理CPU接口。 GIC虚拟CPU接口保证包含在单个页面中,因此可以映射到VM,而无需VM访问任何其他资源。 GICv3使用系统寄存器访问虚拟CPU接口,在这种情况下,不会映射GICv3硬件的任何部分以直接MMIO访问VM。相反,CPU接口系统寄存器访问由硬件重定向到虚拟CPU接口寄存器。在GICv3上,管理程序控制接口通过仅在Hyp模式下可访问的系统寄存器来控制。此配置确保对于GICv2和GICv3,只有管理程序可以对控制接口进行编程,并且VM可以直接访问GIC虚拟CPU接口。但是,客户操作系统仍将尝试访问GIC分配器以配置GIC并将IPI从一个虚拟核心发送到另一个虚拟核心。此类访问将陷入管理程序,管理程序必须模拟虚拟分发服务器作为VGIC的一部分。
KVM / ARM引入了虚拟分发器,这是GIC分发器的软件模型,是高级管理员的一部分。在lowvisor中实现模拟分发器很有吸引力,因为它可能允许更快地处理来自VM的陷入,但这不起作用,因为分发器模拟必须能够使用内核自旋锁,发送物理IPI和处理处理中断时内核线程发生的事件,这些功能是主机操作系统内核的一部分,在低级管理程序中不可用。虚拟分发器向用户空间和内核的其余部分公开接口,以便用户空间中的模拟设备可以向虚拟分发器引发虚拟中断。内核的其他部分,包括用于虚拟Virtio设备后端的VHOST和用于直接设备分配的VFIO,可以通过直接在其他内核子系统和虚拟分发器之间发送文件描述符来引发虚拟中断。 KVM / ARM进一步向VM公开与物理GIC分配器相同的MMIO接口。虚拟分发器保持关于每个中断的状态的内部软件状态,并且每当调度VM以对列表寄存器进行编程以注入虚拟中断时使用该状态。例如,如果虚拟CPU0向虚拟CPU1发送IPI,则分发器将为虚拟CPU1编程列表寄存器,以在下次虚拟CPU1运行时引发虚拟IPI中断。
请注意,列表寄存器是每个物理CPU都有的。 这意味着希望向VCPU注入虚拟中断的物理CPU(在不同的物理CPU上运行)必须首先在软件中注册挂起的虚拟中断,然后将物理IPI从发送物理CPU发送到接收物理CPU ,正在运行VCPU。 物理IPI导致CPU退出VM并返回到高级管理程序,它将检查VCPU的待处理虚拟中断,然后才会编程自己的列表寄存器。 因此,大多数针对正在运行的VM的虚拟中断注入方案涉及底层的跨物理CPU通信,包括物理IPI。
理想情况下,虚拟分发器仅在必要时访问硬件列表寄存器,因为设备MMIO操作通常比缓存的内存访问慢得多。在调度不同的VM以在物理核心上运行时,需要完整的列表寄存器上下文切换,但在简单地在VM和管理程序之间切换时不一定需要。例如,如果没有挂起的虚拟中断,则不必访问任何列表寄存器。注意,一旦管理程序在切换到VM时将虚拟中断写入列表寄存器,它在切换回管理程序时也必须读回列表寄存器,因为列表寄存器描述了虚拟中断的状态并指示,例如,如果VM已确认虚拟中断。 KVM / ARM的初始未优化版本使用简化方法,该方法完全上下文切换所有VGIC状态,包括VM和管理程序之间的每个转换上的列表寄存器,反之亦然。有关优化VGIC性能的更多信息,请参见第4章。
读取计数器和编程定时器是用于进程调度和定期轮询设备状态的许多OS中的频繁操作。 例如,Linux读取计数器以确定进程是否已过期其时间片,并编程计时器以确保进程不超过其允许的时间片。 应用程序工作负载通常还会出于各种原因利用计时器。 为每个此类操作捕获到hypervisor可能会产生明显的性能开销,并且允许VM直接访问计时硬件通常意味着放弃硬件资源的时序控制,因为VM可以禁用计时器并长时间控制CPU 时间。
KVM / ARM利用通用计时器的ARM硬件虚拟化功能,允许VM直接访问读取计数器和编程计时器,而无需捕获到Hyp模式,同时确保管理程序保持对硬件的控制。虽然架构除物理和虚拟定时器外还提供专用的Hyp定时器,如2.1.4节所述,Hyp定时器仅在Hyp模式下可用,因此不适合主机Linux OS的计时需求。相反,主机Linux操作系统使用物理计时器。由于Linux在VM中运行时仅使用虚拟计时器而不使用物理计时器(它检测到它在EL1而不是EL2中引导),因此KVM / ARM当前为VM分配虚拟计时器并使用物理计时器作为主机。此外,KVM / ARM当前在运行VM时禁用对物理定时器的访问,以便为主机操作系统保留,并且对物理定时器的VM访问将被捕获并在软件中进行模拟。当切换到VM时,有可能将物理定时器状态移动到管理程序中的管理程序计时器,从而在使用分离模式虚拟化时使虚拟和物理定时器硬件都可用于VM。作为访客操作系统运行的Linux内核仅访问虚拟计时器,从而能够直接访问计时器硬件而不会陷入虚拟机管理程序。
遗憾的是,ARM体系结构不提供任何直接从计时器向VM注入虚拟中断的机制。 定时器总是生成陷阱到hypervisor的物理中断。 当VM运行并且虚拟计时器到期时,物理中断陷入lowvisor,该lowvisor在切换到highvisor时禁用虚拟计时器,因此highvisor从未实际处理来自虚拟计时器的中断。 当从VM返回到highvisor时,highvisor始终检查VCPU的虚拟计时器状态是否已过期,如果有,则注入虚拟中断。
硬件仅为每个物理CPU提供单个虚拟计时器,并且可以在该单个硬件实例上多路复用多个虚拟CPU。为了在这种情况下支持虚拟计时器,KVM / ARM使用类似于x86的方法,并在VM捕获到虚拟机管理程序时检测未到期的计时器,并利用现有的OS功能在虚拟计时器被触发时对软件计时器进行编程, VM已经运行了。当这样的软件定时器触发时,执行回调函数,其使用上述虚拟分配器向VM引发虚拟定时器中断。常见的情况是空闲VCPU调度未来的定时器,然后使用ARM等待中断(WFI)指令暂停VCPU。在VM中执行WFI会捕获到虚拟机管理程序,再次将Linux进程和线程抽象到VM和VCPU非常有用。在VM中执行的WFI指令仅通过告知Linux调度程序相应的VCPU线程没有任何工作来模拟。当软件计时器到期并且调用回调函数时,VCPU线程被唤醒并且Linux调度该线程。进入VM后,管理员将检测VCPU的待处理虚拟计时器,并注入虚拟中断。