An in-depth look into the ARM virtualization extensions

来源:互联网 发布:淘宝店铺花呗怎么开通 编辑:程序博客网 时间:2024/05/16 08:57

最近的高端ARM CPU支持硬件虚拟化。由于以前的ARM架构的局限性,虚拟化硬件往往变得越来越慢。一些特权指令在非特权模式下执行时不一定陷阱。这有效地防止了虚拟机管理程序在非特权模式下未修改的客户机操作系统的内核代码,并使用陷阱和仿真方法来处理特权指令。不要修改架构以关闭虚拟化漏洞,意思是在CPU状态的特权部分被修改时始终陷入困境,ARM决定保持向后兼容并扩展ARM v7架构以支持虚拟化。当VT引入x86架构时,ARM走了像Intel这样的路径。

虽然ARM的虚拟化扩展似乎与英特尔的方法非常相似,但是devil 在细节上。我们期待着探索一段时间,并乐意与您分享我们研究的见解。当从ARM虚拟化这一行开始,我们最有趣的问题之一是:如何将虚拟化整合到Genode( novel OS architecture)中,而不会增加在虚拟机旁边运行的应用程序的可信计算基础(TCB)?此外,与NOVA OS虚拟化架构采用的方法相似,我们如何使虚拟机(VM)独立于其他虚拟机(VMM)进行监视(VMM) ?

整体建筑

ARM虚拟化扩展基于安全扩展,通常称为TrustZone。有关TrustZone的更多信息,请参阅我们 以前发表的文章。为了实现不同虚拟机之间的切换,在处理器的正常世界中引入了新的特权级别,包括一个新的处理器执行模式 - “低”模式。

这里写图片描述

ARM特权级别(PL)和处理器模式

上图显示了ARM处理器可以执行代码的所有权限级别和模式的概述。如图所示,虚拟机只能在TrustZone的“正常世界”中执行。支持VM分离和世界交换机的处理器指令只能用于hyp或monitor模式。因此,第一个设计问题是管理程序相关代码是否应在正常世界中执行,或者至少部分管理程序是否应在“安全的世界”中运行?两个TrustZone世界中哪一个应该承载与虚拟化无关的普通用户地应用程序?

到目前为止,Genode曾经在正常世界或安全世界中执行,具体取决于平台。例如,当使用Pandaboard(low-power, low-cost single-board computer development platform )时,安全世界在引导过程中已被OEM固件锁定,Genode仅限于正常的世界。相比之下,在ARNDALE板上,Genode系统可以自由使用安全和正常的世界。我们决定忽略这里的安全世界,并在正常世界中执行一切。因此,Genode能够容纳所有可能的平台,包括TrustZone功能已被供应商锁定的平台。

目前工作的起点是Genode的集成ARM内核,我们称之为“base-hw”(“hw”表示在裸机上运行的Genode,而不是在第三方内核上运行)。在这个平台上,Genode的核心流程部分运行在内核模式(PL1)中,但大多数代码正在用户模式(PL0)运行。我们的TrustZone文章解释了此设计背后的理由 。我们扩展了这种方法,扩展了以hyp模式(PL2)运行的虚拟机管理程序特定代码的核心进程,从而获得了对硬件资源具有全局视图但在三个不同特权级别执行的二进制文件。从俯瞰的角度介绍不同虚拟机的基础平台的概念如下图所示。

这里写图片描述

Genode的ARM内核(核心)运行在所有权限级别

理想情况下,以hyp模式运行的核心进程的一部分仅包括管理程序特定的代码。这包括在不同虚拟机之间切换的代码以及在图片中表示为Dom0的Genode世界。为了使其复杂度尽可能低,管理程序应该不受任何设备仿真的影响。如果可能,它的功能应该归结为重新加载通用和系统寄存器,以及管理访客物理到主机物理内存转换。

与此相反的低复杂度管理程序,用户级VMM可以在不使系统的安全处于危险复杂。它包含潜在的复杂设备仿真代码,并将诸如内存和中断等硬件资源分配给VM。VMM是一个运行非特权的普通应用程序,可以每个虚拟机重新实例化。通过每个VM实例化一个VMM,不同的虚拟机之间相互分离。即使一个VMM发生故障,其他VM也不会受到影响。当然,简单的用户地应用程序不能直接使用硬件虚拟化扩展。这些扩展仅在hyp模式下可用,这是内核独有的。因此,需要VMM和内核之间的接口来共享虚拟机的状态。在为之前的TrustZone实验构建VMM时,我们面临着类似的问题。建立可用的解决方案是很自然的,必要时将其扩展。Core提供了一个所谓的VM服务。每个VM对应于该服务的会话。会话提供以下界面:

CPU状态

CPU状态函数返回包含虚拟机状态的数据空间。它启动VM,得到由每当切换来自VM走,并且可以通过VMM被用来解释来宾操作系统的行为管理程序更新之前初始填充由VMM。此外,它可以在虚拟机监视虚拟机的仿真指令后进行更新。这种机制可以与x86架构中的VMCS结构进行比较。

异常处理程序

第二个功能用于注册一个信号处理程序,当VM产生虚拟化故障时,该处理程序将被通知。

运行

运行功能启动或恢复VM的执行。

暂停

暂停功能从内核的调度程序中删除虚拟机。

鉴于这种高级架构,本文的其余部分涵盖了实现它的技术难题。我们首先要解释为了最初将平台从安全引导到正常世界,必须做些什么。我们遇到内存是我们必须解决的第一个硬件资源。部分内存虚拟化解释了我们虚拟化内存的方法。最重要的部分是CPU的虚拟化,这在CPU虚拟化部分中介绍。其次是虚拟化中断和虚拟时间部分虚拟化中断和时间的陷阱和积极意外的解释。最后,文章以当前状态的摘要结束。

引导到Genode的“Dom0”

为了实际开始探索虚拟化扩展,我们使用了包含三星Exynos5 SoC的ARNDALE开发平台,它基于两个Cortex A15 CPU内核。使用该板是有益的,因为Genode的基础平台已经支持ARNDALE,大多数设备驱动程序已经覆盖。此外,这种低成本设备提供UART和JTAG连接器,这在调查新的处理器功能时非常有利。

这里写图片描述

ARNDALE开发板与Exynos 5250

如前所述,ARNDALE板卡启动到TrustZone的安全世界。当我们决定在正常世界中运行整个系统时,第一步是引导到正常世界的模式。在离开安全世界之前,需要进行以下调整,以使正常世界能够访问所有硬件资源。必须将“非安全访问控制寄存器”(NSACR)配置为允许访问所有协处理器并允许调整“辅助控制寄存器”(ACTLR)的多处理器相关位。此外,所有中断必须在中断控制器上被标记为不安全,以便正常的世界能够接收它们。

当这样做时,我们发现了一个棘手的细节,关于中断控制器,使我们头痛。ARM的通用中断控制器(GIC)分为一个CPU接口,每个内核都有一个称为“分配器”的全局模块。通常,在分销商处调整的所有属性都涉及所有核心,包括确定是否在安全或正常世界内接收的中断的安全性分类。但这并不适用于每个内核私有的前32个中断。在我们的第一个实验中,我们让引导CPU将所有中断标记为不安全。一切顺利,直到第二个CPU内核必须从第一个CPU内核接收到一个处理器间中断(IPI)。然而,IPI,是一个核心私人中断。因此,其安全性分类需要在初始化期间由每个CPU内核设置,而不仅仅是第一个核心一次。

在使用中断控制器征服故障后,我们最终将“安全配置寄存器”(SCR)配置为:
启用管理程序调用,
禁用安全监视器呼叫,
不陷入安全的世界,从而有效地锁定安全的世界,
切换到正常的世界。

通过这些步骤,CPU以管理程序模式结束。在继续正常的内核启动过程之前,CPU不得不丢弃管理程序的权限级别PL2,并进入内核的常规权限级别(PL1)。在虚拟机管理程序模式下运行现有的内核代码而不进行修改将不起作用。几乎没有不兼容性阻止在PL2中为PL1编写的代码的执行。例如,当使用多重加载指令(LDM)访问用户模式特定寄存器或从异常返回时,结果在管理程序模式下未定义。为了能够从以较低权限级别运行的内核部分重新进入虚拟机管理程序模式,必须进行一些进一步的准备工作。首先,必须安装异常向量表,这是当与管理程序相关的异常发生时调用的函数表。通过“hyp矢量基地址寄存器”(HVBAR)设置管理程序的异常向量表后,常规引导过程可以在管理程序模式之外继续。

内存虚拟化

ARM的虚拟化支持通过可选的第二级地址转换扩展了前MMU,从而有效地实现了嵌套分页。此外,为了在虚拟机管理程序模式下使能虚拟内存,ARM还为此模式引入了专用MMU。

为了克服32位可寻址物理内存空间的限制,ARM提供了一种新的页表格式,将可寻址物理内存空间扩展到40位。在使用虚拟化时,所谓的“大型物理地址扩展”(LPAE)是强制性的。访客物理到主机 - 物理页表的第二阶段以及虚拟机管理程序的页表需要使用新的格式。当使用实现虚拟到物理或分别来宾虚拟到客体物理转换的正常MMU时,可以决定是使用新版本还是旧版页表格式。

当我们计划在所有不同权限级别运行核心流程,从而共享一个内存视图时,使用与所有权限级别相同的页面表格似乎是有益的。由于管理程序模式需要新的页表格式,我们决定先在现有的内核中实现。这种方法的好处是我们可以使用内核作为测试用来验证新格式。此外,它可以进行比较测量。旧页表格式的深度最多为2级,新的页面格式有3个级别。因此,预期会出现一些业绩差异。

在Genode基础平台上整合新格式后,我们可以测量出轻微的性能下降。作为运行时测试,我们使用Genode本身的核心流程编译。编译过程依次产生大量进程,一次又一次地填充其地址空间。这将强调MMB的TLB条目替换以及页表行走。虽然测试使用了MMU,但它代表了与人造微量基准相反的现实生活中的例子。因此,希望能更准确地显示有效的成本。如表1所示,使用新的3级页表格式时的运行时开销相对于编译测试小于1%。

为了始终在所有不同权限级别之间提供一致的内存视图,以前在用户地和内核之间共享的页表也必须由管理程序特定的MMU使用。因此,我们将上一节Bootstrap中描述的管理程序模式准备扩展到Genode的“Dom0”。我们将页表地址设置在管理程序的“hyp translation table base register”(HTTBR)中。此外,我们将页面属性设置在“低位翻译控制寄存器”(HTCR)和“低内存属性间接寄存器”(HMAIR)中。最后,我们通过“hyp系统控制寄存器”(HSCTLR)启用了管理程序的MMU和缓存。

最后,我们通过创建1:1客体物理到主机 - 物理转换表来测试嵌套分页,并启用了普通Genode系统的第二阶段翻译。使用“虚拟化翻译表基址寄存器”(VTTBR),“虚拟化翻译控制寄存器”(VTCR)和“配置寄存器”(HCR)设置第二级表的地址,设置页面属性,并启用嵌套分页。他们自然可以在管理程序模式下进行访问。因此,在之前描述的引导过程中执行这些寄存器的设置。在启用第二阶段翻译之后,我们再次测试了编译测试,并发现了3的运行时间开销。与使用旧页表格式的版本相比,没有嵌套分页的7%。表1包括我们的测量。

页表格式    持续时间(ms)    高架2级,1级   214 3级,1级   216 0.9%3级,2级   222 3.7%表1:使用不同翻译方案的编译测试测量。

考虑到在使用嵌套分页时测量的性能下降,我们决定在切换到客户机操作系统时启用虚拟化MMU,只要Genode软件堆栈正在运行,它就会被禁用。使用这种方法,我们在后来的发展阶段观察到一个有趣的副作用。

为了减少在不同进程之间切换时的TLB维护操作的必要性,ARM在过去引入了“地址空间标识符”(ASID),用于标记TLB内的条目。因此,只有当前活动的ASID标记的那些TLB条目才被认为是有效的。现在,MMU不仅在地址空间之间而且在虚拟机之间共享,还添加了一个额外的“虚拟机标识符”(VMID)。可以在VTTBR系统寄存器中进行设置,负责设置第二级页表。对我们而言,重要的学习效果是使用VMID来标记TLB,无论是否启用嵌套分页。因此,

CPU虚拟化

在内存虚拟化成功实验之后,下一步是为CPU实现世界切换。我们从一个非常简单的VMM开始,使用“ 总体架构”一节中描述的VM会话接口,将VM的初始寄存器集提供给管理程序。以下表示为VM状态的寄存器集首先仅包括通用寄存器(r0-r15),“当前程序状态寄存器”(CPSR)和与这些寄存器有关的这些寄存器中的一些寄存器的“存储”副本不同的执行模式。

首先,VMM通过其核心的VM会话请求包含VM状态的数据空间。它通过提供相应的重置值来准备状态。使用VM会话,它注册一个信号处理程序并启动VM的执行。之后,VMM等待传递给其信号处理程序的虚拟化事件。

当VM会话被打开时,核心进程创建一个新的VM对象。该对象包括用于VM状态的数据空间,VMID以及用于客体物理到主机 - 物理转换的最初空的第二阶段页表。当会话的客户端发出运行调用时,核心将VM对象添加到调度程序。如果调度程序选择要运行的VM,则会重新加载所有模式特定的存储寄存器,并使用“hvc”指令触发管理程序调用。虚拟机管理程序通过首先检查虚拟化MMU是否启用来响应此调用。如果禁用,则会推断该呼叫是由Genode主机系统触发的,而不是由VM触发。在这种情况下,虚拟机管理程序假定主机到VM交换机。

对于实际的交换,启用虚拟化MMU,将VM的第二级表和VMID加载到VTTBR中,并配置陷阱行为。无论何时访问协处理器,包括访问包含所有系统寄存器的系统协处理器(CP15),VM将进入管理程序。该陷阱控制通过“低协处理器陷阱寄存器”(HCPTR),“低系统陷阱寄存器”(HSTR)和HCR进行管理。最后,虚拟机管理程序加载所有VM的通用寄存器和CPSR,并丢弃其特权级别以实际执行虚拟机。VM创建和执行的详细过程如下图所示。

当虚拟机被捕获时,硬件更新“hyp syndrome register”(HSR)。HSR包含异常的类型和异常特有的附加信息。在这些综合征中,尝试访问系统的协处理器,与第二阶段页表相关的页面错误和管理程序调用。在HSR中编码的额外特殊异常信息例如是访问系统寄存器时的源寄存器和目标寄存器。除了HSR之外,还有其他系统寄存器包含引起页面错误的客户虚拟地址(HDFAR和HIFAR)以及产生故障的页面对齐客机物理地址(HPFAR)的信息。

通过HSR,HDFAR,HIFAR,HPFAR和通用寄存器提供的信息主要适用于虚拟设备的仿真。没有必要走访客操作系统的页表,访问其内存,并解码指令以获取相关信息。ARM的一个明智的决定是将硬件已知的异常相关信息编码到这些管理程序特定的陷阱寄存器中。它不仅简化了VMM软件,而且减轻了VMM访问VM专用存储器的需要。这种访问可能会受到ARM的弱缓存一致性的影响。

这里写图片描述

创建和运行虚拟机的示例顺序

鉴于上述见解,我们决定通过HSR,HDFAR,HIFAR和HPFAR来扩展VM状态。当从虚拟机切换到主机系统时,虚拟机状态的这些字段在管理程序模式下被更新。核心发信号通知VM被捕获后,VMM可以使用更新的状态信息来处理异常。

在我们的实验中,到目前为止,VMM刚刚启动了一个带有一组给定寄存器的虚拟机。没有为虚拟机保留物理内存,更不要说客户机操作系统二进制文件的内存丢失了。由于我们不想信任VMM比任何其他用户陆地应用程序,我们不能让VMM直接通过第二级表控制VM的内存。如果VMM可以直接访问嵌套页面,则可能会向其虚拟机提供访问任何内存区域,包括内含管理程序本身的内存。因此,我们必须使用该工具扩展VM会话界面来填充guest虚拟机的物理内存。要使VMM能够将主机物理内存分配给客户物理地址空间,首先需要证明它具有访问主机物理内存的适当权限。此证明以数据空间功能的形式提供。内核将由数据空间引用的主机物理内存以VMM指定的客户物理地址添加到第二级页表。

为了测试初步的世界切换例程以及VM的物理内存控制功能,我们实现了一个非常简化的客户机操作系统内核。除了访问其二进制数据之外,此内核不会执行任何操作,从而引发管理程序调用。改变后的VMM首先将简单的内核映像复制到先前通过自己的RAM会话请求的数据空间中。然后,它使用VM会话接口将相同的数据空间附加到测试内核链接到的地址上的客户物理内存。虚拟机的第二阶段页表的实际结合由核心流程完成。之后,VMM准备VM状态并启动虚拟机。每当虚拟化事件发出信号给VMM时,

VM进入的第一个陷阱是第一个指令。但与我们的假设相反,原因不是与第二阶段页表相关的错误配置。管理程序特定故障寄存器获取的信息有助于我们识别问题。MMU提出的异常尝试解决第一个指令的地址。由于我们以前没有考虑到世界上的“系统控制寄存器”(SCTRL),所以它的所有属性也应用于VM执行。这包括启用MMU和缓存。通过将SCTRL寄存器添加到VM状态,在VMM中设置正确的复位值(无MMU),并在VM和Genode主机系统之间切换时重新加载SCTRL寄存器,

在这一点上,我们已经开始虚拟化一个真正的客户端操作系统的基础。当然,我们选择一个开源系统作为候选人,以便在提高虚拟化事件时能够轻松地确定操作系统正在尝试的操作。Linux是我们的第一个候选人,因为它支持目标硬件最好,我们最有经验的。我们选择ARM的Versatile Express Cortex A15开发板作为虚拟硬件平台,将其提供给Linux客户端。作为ARM的参考平台,主要记录在案,并且QEMU支持它,该平台似乎有希望。

我们使用Versatile Express平台的标准配置来编译最新的vanilla Linux内核。现在在嵌入式世界中,通过使用所谓的“设备树二进制文件”(DTB)将硬件描述与Linux源代码分开。为了避免需要一次虚拟化整个硬件,我们为只能包含一个CPU内核,中断控制器,定时器,一个UART设备以及必要的时钟和总线配置的Versatile Express板创建了一个最小的设备树。在对最小DTB以及QEMU上未修改的Linux内核进行成功测试后,我们使用VMM将内核和DTB加载到内存中,并开始执行。

从那时起,我们从一个虚拟化事件逐步走向下一个。由于虚拟机管理程序在访问系统注册或不存在的物理地址时准备机器来捕获VM,因此我们可以清楚地看到Linux客户端以什么顺序进行操作。通过逐步模拟系统寄存器,可以根据需要选择将它们添加到VM状态,并在适当的情况下禁用陷阱行为,我们逐步完成CPU虚拟化。例如,仿真某些识别寄存器所需的全部是返回有意义的值。但是对于其他寄存器,访问必须转发到真正的硬件。例如,MMU属性(如当前页表)的更改不能被仿真,但需要传播到硬件。这些系统寄存器需要由管理程序保存和恢复。因此,它们被连续添加到VM状态。当系统寄存器必须重新加载时,通常它们也可以直接访问客户机而不需要陷阱。此外,还有用于维护TLB,指令和数据缓存的系统寄存器,它们使用当前活动的VMID和ASID来分别例如与当前进程相关联的缓存行。这些操作是不关键的,应该由VM直接完成。存在用于维护TLB,指令和数据缓存的系统寄存器,其使用当前活动的VMID和ASID来分别例如与当前进程相关联的缓存行。这些操作是不关键的,应该由VM直接完成。存在用于维护TLB,指令和数据缓存的系统寄存器,其使用当前活动的VMID和ASID来分别例如与当前进程相关联的缓存行。这些操作是不关键的,应该由VM直接完成。

幸运的是,ARM对系统协处理器寄存器的陷阱行为进行了细粒度控制。对于其他协处理器,它是一个二进制决定,无论VM何时访问它们都将被捕获。关于系统协处理器的陷阱行为分为14个不同的部分,它们通过“系统陷阱寄存器”(HSTR)进行控制。其中一个部分例如包括所有与TLB维护相关的系统寄存器。主要开发过程是首先使所有部分在访问其中一个寄存器时立即捕获VM。如果管理程序中的所有系统寄存器都由管理程序重新加载,则可以禁用该部分的陷阱行为,并可以向VM授予直接访问权限。

没有优化,只需直接访问TLB和缓存维护寄存器,我们确定了需要由管理程序重新加载的最小系统寄存器集。表2显示了最小寄存器集。

部分  缩写  名称1   SCTRL   系统控制寄存器1   CPACR   协处理器访问控制寄存器2   TTBCR   翻译台基控制寄存器2   TTBR0   翻译台基地址寄存器02   TTBR1   翻译台基地登记册13   DACR    域访问控制寄存器5   DFSR    数据故障状态寄存器5   IFSR    指令故障状态寄存器5   ADFSR   辅助DFSR5   AIFSR   辅助IFSR6   DFAR    数据故障地址寄存器6   IFAR    指令故障地址寄存器10  PRRR    主区域重映射寄存器10  NMRR    正常内存重映射寄存器13  CIDR    上下文ID注册13  TPIDR 0-2   软件线程ID寄存器表2:由管理程序重新加载的最小系统寄存器集。

除了37个核心寄存器之外,至少还有16个系统寄存器必须由每个交换上的管理程序从主机重新加载到客户系统,反之亦然。

虚拟化中断

在开发过程中,我们认识到如果虚拟机管理程序没有相应地配置陷阱行为,则在虚拟机内接收的Genode主机系统的控制下,设备中断。为此,管理程序必须在切换到VM时将所有中断引导到自身。为了最小化开销,当切换回主机系统时,此陷阱行为将被再次禁用。相应地,当正常Genode应用程序执行时发生中断时,根本不涉及管理程序代码。

在上一节描述的CPU模型完成之后,逐步执行VM的执行,我们越过Linux的中断控制器的初始化程序。除了CPU之外,ARM还为其中断控制器引入了虚拟化扩展。如 引导到Genode的“Dom0”一节所述,ARM的通用中断控制器(GIC)分为核心本地CPU接口和一个全局分配器接口。

通过硬件虚拟化,对于每个核心,可以使用“虚拟CPU接口”(GICV)。每个GICV由专用的“虚拟CPU控制接口”(GICH)容纳。GICV意味着可以替代普通的CPU接口,并由VM直接使用。控制接口分别保存到管理程序VMM中以管理出现在GICV上的中断。

与CPU接口相比,在硬件上实现的全球分销商并没有特殊的虚拟化支持。因此,VMM必须为虚拟机模拟此设备。虽然客户经常访问CPU接口,但分发商据说很少被触摸,主要是在初始化期间。因此,通过CPU接口的特殊硬件支持来提供优化表明了自身。

一开始,我们实现了几乎空虚的GIC形状。在不支持Distributor接口所在的物理地址与内存的情况下,VM在尝试访问时总是触发虚拟化事件。对于由Linux访问的所有GIC寄存器,VMM记录其状态,但不实现任何逻辑。因此,Linux首先成功地经过了分销商的初始化。

为了利用中断控制器的新的虚拟化功能,我们决定为虚拟CPU提供虚拟CPU接口,而不是模拟CPU接口。因此,我们必须使用GICV的内存映射I / O(MMIO)寄存器来恢复虚拟机通常期望CPU接口的物理地址范围。虽然,我们已经扩展了VM会话接口,以使VMM能够向VM提供任意数据空间,但这不适合附加GICV的MMIO寄存器。考虑到多个VMM可能并行运行以控制不同的VM,并且只有一个物理GICV,我们不能授予VMM直接访问它。否则,不同的VMM会干扰。另一方面,只有VMM知道CPU接口应放在客体物理内存中,取决于它仿效的平台。为了解决这个问题,VM会话进一步扩展了一个特定的功能来附加中断控制器的CPU接口。

将核心局部中断控制器接口添加到VM的内存布局后,Linux内核成功完成了GIC初始化,并将其启动过程继续到发出“等待中断”(WFI)指令。这通常会使CPU进入浅睡眠状态,直到下一个中​​断发生。相应地配置(通过HCR),执行WFI指令的VM被捕获。VMM识别出VM正在等待中断并停止执行,直到发生下一个中断。为了进一步跟随Linux系统的初始化,下一个逻辑步骤是填充GIC模型,并将虚拟机注入中断。

第一个结果是一个非常简单的中断控制器模型。它忽略了多处理器支持,优先级以及不同安全级别(TrustZone)对中断的分配。而前者肯定是为VM支持多个CPU的必备功能,无论如何,我们的Linux访客内核不会使用优先级和安全扩展。由于全面的优先支持将使GIC的国家机构更加复杂,所以尽可能省略它。当前的GIC模式保持各个中断的状态,无论它们是否使能,以及中断控制器本身是否负责。

为了向VM发出中断信号,可以使用GICV的控制接口。它由核心进程(管理程序)独占控制,而不是VMM。此设计可防止不同虚拟机和显示器之间的干扰。我们遵循微内核范例的第一个直观方法是通过在不同VM之间切换时重新加载GICH寄存器来最小化虚拟机管理程序中的复杂性。我们将GICH寄存器的影子副本引入VM状态。VMM仅在影子副本上运行。当管理程序执行世界切换时,它将阴影GICH状态加载到真正的GICH中。

在处理第一个中断时,出现了这种方法的限制。实现的第一个中断源是Linux访客操作系统用于调度的虚拟计时器。由于下节介绍的原因,虚拟定时器的中断需要由不同的VM共享,因此可以在内核中直接处理。因此,该定时器中断任一必须由内核直接注射或它的发生必须用信号发送到VMM,这反过来会经由阴影GICH寄存器注入中断。第一种方法需要内核和VMM之间的关于VM的GICH寄存器集的同步,这是不希望的,因为内核不能依赖于用户 - 土地行为。另一方面,如下所示,

每当Genode主机内核接收到器件中断时,内核将在分配器屏蔽中断,直到相应的器件驱动程序处理它。所以中断不会在同一时间重新出现。在单片内核中,可以省略这种掩码。内核调用中断的上半部分处理程序,这是一个小程序,它直接在相应的器件上确认中断,使得中断控制器上的中断信号消失。在像Genode这样的基于微内核的系统中,设备驱动程序像用户地无特权的普通应用程序一样运行,情况并非如此。一般来说,当设备驱动程序将在设备处理相关的中断时通常是未知的。因此,内核暂时在控制器屏蔽中断,直到相关的设备驱动程序发出中断处理的信号。对于虚拟化情况,这意味着如果VM自身直接使用设备,例如虚拟定时器,则需要识别中断处理的完成。否则主机内核无法再次取消屏蔽相应的中断。幸运的是,ARM已经通过虚拟化扩展为这种情况做了规定。如果配置相应,只要客户端操作系统将该中断标记为处理中断,注入的中断可以触发特殊的维护中断。例如虚拟计时器,需要识别中断处理的完成。否则主机内核无法再次取消屏蔽相应的中断。幸运的是,ARM已经通过虚拟化扩展为这种情况做了规定。如果配置相应,只要客户端操作系统将该中断标记为处理中断,注入的中断可以触发特殊的维护中断。例如虚拟计时器,需要识别中断处理的完成。否则主机内核无法再次取消屏蔽相应的中断。幸运的是,ARM已经通过虚拟化扩展为这种情况做了规定。如果配置相应,只要客户端操作系统将该中断标记为处理中断,注入的中断可以触发特殊的维护中断。

这里写图片描述

中断专用于虚拟机的设备的传递。

总而言之,VMM实现了VM的中断处理。无论何时在VM控制下的物理设备的中断发生,内核都会发信号通知VMM。VMM通过VM状态下的GICH寄存器注入中断,并恢复VM。在切换到VM时,内核又重新加载GICH寄存器。在VM内发生中断。客户操作系统内核处理相关设备的中断,并在中断控制器的虚拟CPU接口处确认中断。这将在管理程序中触发维护中断,将其转发给VMM。VMM通过确认内核中的原始中断来响应维护中断。内核再次屏蔽中断,整个过程完成。上图中的序列图说明了专用于一个VM的设备中断的传递。假设这个过程在VM运行时发生,它将需要四个世界交换机和至少四个内核VMM交换机,直到VM能够继续。这表示高中断延迟。

尽管处理直接分配给VM的中断的开销似乎相当重要,但是我们决定从此开始,并在必要时进行优化。这种纯粹的体系结构的优点是,虚拟机管理程序只能从VM状态保存和恢复虚拟CPU控制接口的寄存器。因此,我们严格遵循最小化TCB的原则。

VMM控制GICH寄存器,并在必要时注入中断。如果直接与VM关联中断,则VMM可以像Genode中的任何其他设备驱动程序一样访问它:它将在核心进程中打开一个IRQ会话。与此相反,无法通过IRQ会话获得中断控制器的虚拟计时器和维护中断。它们在所有VMM之间共享。当它们发生时,只有当前活动的VM的VMM才能接收它。这种中断由虚拟机管理程序通过VM状态隐含地发送给VMM。当VMM识别到VM由于中断而停止时,它将从VM状态读取中断信息。

与VM直接控制的设备相反,VMM可能会提供仿真设备。这些设备通常使用主机系统的服务作为后端。部分与访客操作系统的交互描述了使用Genode终端服务的UART设备的一个示例。如果这样的仿真设备需要向VM注入中断,则该过程不那么复杂。与物理虚拟中断相比,成功处理这些虚拟中断不需要明确地向VMM发出信号。

虚拟时间

如上一节所述,Linux内核在初始化期间使用的第一个设备之一是定时器。虚拟化时间是一个关键的操作。当通过陷阱模拟方法实现定时器时,由于频繁访问引起的重大性能损失是不可避免的。为了规避这些可预见的问题,ARM为其核心本地通用计时器增加了虚拟化支持,通过协处理器十四(CP14)访问。

当启动ARM的通用计时器时,我们遇到了严重的问题。当初始支持ARNDALE板时,我们未能使用ARM的定时器,而是使用三星自己的多核心计时器。三星的Exynos 5 SoC(ARNDALE板中使用的)的文档甚至没有提到通用计时器。通过关于涉及三星开发商的ARM Linux内核邮件列表的有趣讨论,事实证明,三星的多核定时器和ARM的通用计时器实际上在这个SoC上使用相同的时钟,并且三星定时器中的某些位需要启用ARM定时器成功运行。另一个小问题是定时器运行的频率。它需要在安全的世界中配置。

ARM的通用计时器由虚拟计时器进行扩展,虚拟计时器与正常物理计时器相结合。它以相同的频率计数,但相对于可设置的物理参考具有偏移。在不同的虚拟机之间进行切换时,需要保存并恢复包含实际计数器值的寄存器和虚拟计时器的控制寄存器。除此之外,VM可以直接访问计数器,并始终获取实际值。不需要陷阱。

当虚拟计时器的计数器为零时,如果在控制寄存器中使能了该中断,则会发生中断。然而,与可以与正好一个设备驱动程序或VM相关联的其他设备相反,虚拟计时器可能在多个虚拟机之间共享。因此,虚拟定时器中断扮演着特殊的角色。只能使用当前活动的虚拟机将其传递给VMM。该过程在上一节中进行了深入的描述。

如果虚拟机被中断,则虚拟时间计数器被存储并且虚拟时间停止。这在相对较短的时间段内是可接受的,例如在处理虚拟化事件期间。但是,如果VM被永久抢占,例如在客户机操作系统发出信号等待中断之后,虚拟时间需要继续。在这一点上,ARM的虚拟计时器硬件几乎没有用。我们无法为非活动虚拟机编程定时器,并在同一时间将其用于另一个活动的虚拟机。相反,需要使用另一个时间资源来监视非活动VM的时间进度。因此,无论何时VMM都认识到VM将停止执行较长时间,它将使用VM状态的最后一个虚拟计时器计数器值来编程Genode的定时器服务。

与客户操作系统进行交互

UART的第一步需要一个基本的设备模型。起初,我们在VMM中实现了一个非常简单的设备模型,没有中断支持,没有为VM(RX方向)提供任何字符,只是VM(TX方向)传输的打印字符。通过在Linux内核中启用早期调试消息,可以一次又一次地将这些消息与在QEMU中运行的Linux内核的输出进行比较。这通过识别输出中的差异来帮助大量检测问题。

引导过程完成后,我们喜欢使用UART设备来首先与客户操作系统进行交互交互。因此,扩展了设备型号,而后者的打印后端被Genode的终端服务所取代。每次从终端接收到一个字符并且每次VM尝试从UART寄存器读取或写入一个字符时,通过注入中断,我们获得了一个功能非常慢的串行控制台。然而,这种性能限制对于我们的虚拟化方法来说并不是特别的,而是由于UART设备的工作,这对于一般的虚拟化是不满意的。

这里写图片描述

在Genode之上并行运行的三个Linux串行控制台

用于设备虚拟化的I / O MMU

当托管虚拟机时,将虚拟机中运行的客户OS直接分配诸如USB控制器,GPU或专用网卡的物理设备可以以两种方式使用。首先,如果客户操作系统是设备的唯一用户,则设备的直接分配将使用该设备最大化访客操作系统的I / O性能。其次,客户操作系统可能配备了主机操作系统中不存在的专用设备驱动程序。在这种情况下,客户操作系统可以用作执行设备驱动程序并提供到主机OS的驱动程序接口的运行时间。在这两种情况下,客户操作系统不应被视为可信赖的。相反,它有可能颠覆组件和虚拟机之间的隔离。

伴随着虚拟化的支持,ARM推出了一种称为“系统MMU”(SMMU)的“I / O内存管理单元”(IOMMU)。通常,IOMMU将设备可见的虚拟地址转换为物理地址,类似于CPU使用的传统MMU。它有助于防止恶意或有故障的设备以及设备驱动程序。而且,它简化了设备虚拟化。当专用于特定客户操作系统的设备启动DMA传输时,它将潜在地访问错误的地址范围。因此,客户机操作系统的驱动程序对DMA能力的设备进行编程,以便使用某些物理地址。但客体物理内存视图与实际主机实体视图不同。这里的IOMMU发挥作用。

在探索ARM虚拟化支持的同时,出现了以下问题:如何提供对虚拟机(VM)的直接设备访问?根据我们以前的经验,我们有一个概念,如何通过x86平台的IOMMU将设备驱动程序与系统的其余部分分开。但是在ARM上,我们没有配备这样的技术的平台。因此,另一个问题是:ARM的IOMMU如何集成到基于微内核的操作系统,如Genode?

事实证明,ARNDALE董事会的三星Exynos5 SoC并没有使用ARM的SMMU,而是使用自己的SysMMU衍生产品。然而,鉴于参考手册的描述,两个IOMMU都非常相似。虽然三星的SysMMU似乎比ARM变体不那么复杂。

客户端操作系统DMA攻击

在我们开发平台上探索SysMMU之前,我们决定实施用作测试车辆的攻击。一个使用DMA功能的设备读取或覆盖Genode内核二进制文件的简单驱动程序似乎是一个合适的例子。当寻找一个合适的设备时,我们自然就会在三星SoC上使用DMA引擎。这种DMA引擎由一些没有专用DMA引擎的外设使用。此外,它还包括执行存储器到存储器复制事务的多个DMA通道。因此,它似乎完全符合我们的要求。然而,在从表面上研究DMA引擎的功能描述之后,结果比以前更复杂。由于我们不想实施过于复杂的测试驱动程序,选择了一种替代方法。鉴于我们最小的虚拟化环境,我们能够在虚拟机内运行一个简单的Linux客户机操作系统。如果我们通过直接访问DMA引擎扩展虚拟环境,则Linux内核可能会执行攻击,因为它已经包含相应设备的驱动程序。

第一步是将DMA引擎添加到Linux访客的“设备树二进制”(DTB)中。如“ CPU虚拟化”部分所述,VMM提供的虚拟环境与底层硬件平台不同。我们使用内核和为ARM的Versatile Express Cortex A15开发板构建的最小DTB而不是提供虚拟ARNDALE板。幸运的是,原始的DTB硬件描述板已经包含与ARNDALE板上相同的DMA引擎。因此,我们只需要将原始的DMA引擎描述添加到我们的最小DTB中。

在启动Linux客户机操作系统后,内核无法初始化DMA引擎。事实证明,负责时钟和电源领域的Genode主机操作系统的平台驱动程序在初始化时钟和电源控制器时会禁用该设备。在解决了这个小问题之后,内核终于可以识别和初始化DMA引擎。

为了执行实际的攻击,我们调整了一个已经存在于Linux内核中的DMA引擎调试测试。它使用内核的通用DMA API在相应的引擎上运行多个测试。我们修改了测试模块,方法是将Genode OS主机内核复制到一些空的Linux guest虚拟机内存。在这个阶段,我们没有提供IOMMU的支持。因此,我们计算了客体物理到主机物理内存的偏移量与移交给DMA API的地址。然而,预期的效果并没有发生。目标记忆总是保持不变。但DMA引擎报告说,它已经成功完成了交易。在进一步检查DMA引擎的Linux内核的设备驱动程序后,原来,引擎从内存中获取微码来执行其操作。微码由驱动程序写入,其物理地址也由驱动程序提供给设备。换句话说:为了能够执行DMA事务,引擎之前已经启动了一个DMA事务。

将客户主机物理内存偏移量应用于DMA引擎设备驱动程序的指令指针的地址后,最终会如预期的那样工作。我们能够从客户操作系统读取并覆盖主机操作系统内核。

限制DMA交易

为了防止从客户操作系统驱动的DMA攻击,下一步是将SysMMU投入运行。事实证明,SoC中没有单一的SysMMU,而是在不同设备之前的一些不同的SysMMU。由于SysMMU不能区分来自其源总线的请求,所以在每种情况下,SoC需要使用专用的SysMMU,设备应与其他设备分离。另一方面,与Intel的IOMMU相比,每个设备使用一个SysMMU可以大大简化SysMMU。

在英特尔,IOMMU依靠PCI设备的总线设备功能三元组作为DMA事务的唯一标识,它可以管理不同设备的不同转换表。没有实践经验,只有研究参考手册,ARM的SMMU工作类似,可以根据AXI总线ID区分不同的设备。不同的方法如下图所示。这里写图片描述

IOMMU方法:Intel VT-d(以上)和Samsung Exynos5(下)

我们用于实验的三星Exynos5 SoC包括30多个SysMMU。我们确定了位于负责内存到内存事务的DMA引擎的那部分和存储器控制器本身之间的那个。

为了测试我们的目标是正确的SysMMU,我们将其配置为阻止任何请求。三星的SysMMU有三种不同的模式:“禁用”,“启用”和“阻止”。在禁用模式下,它只允许所有事务通过而不进行任何翻译。这是系统重置时的默认值。在块模式下,SysMMU只阻止源总线的任何请求。在启用模式下,它使用转换表查找设备虚拟到物理转换,以及TLB来缓存映射。将DMA引擎的SysMMU置于阻止模式导致Linux客户机操作系统的所有DMA事务超时,从而达到预期的行为。

下一步是将SysMMU置于启用模式。因此,我们首先没有定义一个有效的转换表。要了解翻译故障或其他故障,每个SysMMU提供专用中断。当尝试确定DMA引擎的SysMMU的中断号码时,出现了新的问题。与大多数其他普通设备中断相反,源自SysMMU的中断将被分组。这意味着它们部分共享相同的中断线。为了能够区分一个中断组中的不同源,每个组都有一个简化的中断控制器,即所谓的中断组合器。中断组合器连接到全局中断控制器。Genode的集成ARM内核没有使用这些组合器,因为它们不需要已经存在的设备驱动程序。不幸的是,仅使能中断组合器还不够。必须引入虚拟中断号的命名空间来考虑一个组的不同中断。我们选择了务实的路径,而不是通过设计来解决这个问题。此时,我们只对相应的SysMMU启用了中断,并忽略了同一组的所有其他中断。该中断主要用于错误检测,并不一定需要SysMMU的正确功能。因此,暂时使用临时解决方案似乎是可行的。必须引入虚拟中断号的命名空间来考虑一个组的不同中断。我们选择了务实的路径,而不是通过设计来解决这个问题。此时,我们只对相应的SysMMU启用了中断,并忽略了同一组的所有其他中断。该中断主要用于错误检测,并不一定需要SysMMU的正确功能。因此,暂时使用临时解决方案似乎是可行的。必须引入虚拟中断号的命名空间来考虑一个组的不同中断。我们选择了务实的路径,而不是通过设计来解决这个问题。此时,我们只对相应的SysMMU启用了中断,并忽略了同一组的所有其他中断。该中断主要用于错误检测,并不一定需要SysMMU的正确功能。因此,暂时使用临时解决方案似乎是可行的。该中断主要用于错误检测,并不一定需要SysMMU的正确功能。因此,暂时使用临时解决方案似乎是可行的。该中断主要用于错误检测,并不一定需要SysMMU的正确功能。因此,暂时使用临时解决方案似乎是可行的。

现在中断主要可以传递给SysMMU驱动程序,设备发出总线错误信号。这是预料之中。由于我们没有将地址设置为设备的转换表,因此使用无效的总线地址访问其表。在设置用零初始化并将其物理地址传播到SysMMU作为转换表的有效部分存储器之后,我们终于收到一个指示正常翻译的中断。最后,我们必须设置一个有效的转换表,其中包含Linux客户机操作系统内存的客体物理到主机物理翻译。三星SysMMU的翻译表格式是兼容但简化的ARM二级页表格式。与原始模型相反,SysMMU派生不包括一些内存属性,如缓存策略属性。在使填充的翻译表生效后,Linux客户机操作系统终于可以在自己的内存区域内进行DMA内存到内存的交易,但无法访问其外部的范围。此外,Linux内核的DMA驱动程序的所有修补程序,关于访客到主机的物理内存的偏移计算,自然变得越来越流畅。

在当前的示例实现中,DMA引擎的SysMMU的驱动程序被实现为VMM的一部分。这是为了方便实施。然而,在安全的虚拟化环境的最终设计中,我们不希望比任何其他应用程序或驱动程序信任VMM。由于我们将SysMMU转换表的设置确定为对系统的所有程序和设备都至关重要,所以SysMMU的编程必须由可靠的组件专门完成。因此,我们希望将来在Genode的值得信赖的平台驱动程序中将ARM的IOMMU整合到ARM中。由于Genode中的所有设备驱动程序都取决于该驱动程序,因此它是实现DMA保护的自然选择。

结果

本工作将检查ARM的虚拟化扩展以及I / O保护机制。它显示了如何与基于组件的操作系统架构(如Genode)结合使用。最终的实现是一个概念证明,尚未纳入Genode OS框架的主线。虽然它仍然是一个原型,我们可以得出一些结论。

在我们的工作中,我们接着执行几乎所有的虚拟化相关的功能整合到非特权VMM尽可能最小化共同TCB的范例。特权管理程序的复杂性几乎可以忽略不计。事实上,Genode的核心/内核已补充仅仅通过600行代码(LOC)支持这一虚拟化架构。

对VMM进一步添加功能,如复杂的设备型号,不会增加常见TCB的复杂性。当前版本的虚拟机管理程序的唯一省略是在VM和Genode之间切换时,缺乏FPU上下文的按需切换。然而,由于基础平台已经包括对正常Genode应用程序的FPU切换的支持,所以假设FPU虚拟化不会增加复杂性。

另一个开放的问题是支持VM中的对称多处理(SMP)。一般来说,Cortex A15 CPU的基本平台已经支持SMP。在虚拟机中拥有多个CPU大多数是相应增强VMM的问题。我们设想通过专用的VM会话来表示每个虚拟CPU。在内核中实现的VM对象与调度方面的正常线程几乎相同,可以分配给不同的内核。为此,不需要在内核中实现其他代码。除此之外,当处理几个VM会话时,VMM必须保证与常用设备模型的同步性。

本文中描述的大多数ARM虚拟化工作(除了I / O保护支持除外)已经集成到Genode OS框架的 版本15.02中。

阅读全文
0 0
原创粉丝点击