我们已经成功地将我们的工作集成到Linux内核中,KVM/ARM现在是Linux平台上的标准ARM hypervisor,因为从39内核开始,每个内核都包含了它。它相对简单且能快速完成虚拟化的需求,这得益于特定的设计,这些设计选择允许它利用Linux内核现有的基础,尽管底层硬件存在一些差异化。我们分享了一些从我们的经验中学到的教训,希望他们可以帮助其他人获得开源社区广泛采用的研究思路。
代码可维护性是关键:一个常见的误解是,提供潜在改进或有趣的新特性的研究软件实现可以简单地开源,从而被开源社区快速集成。经常没有考虑到的一个重要问题就是是必须对任何实现进行维护。如果一个实现需要很多人或很多工作量来进行维护,那么它就不太课鞥被集成到现有的开源代码库中。因为可维护性非常重要,所以重用代码和接口非常重要。例如,KVM/ARM构建在KVM和QEMU等现有基础设施上,从一开始我们就优先处理代码评审注释,使我们的代码适合于集成到现有系统中。这个决策的一个意想不到但重要的好处是,我们可以利用社区来帮助解决困难的bug或理解ARM架构的复杂部分。
成为一个知名的贡献者:说服维护者集成代码不仅关乎代码本身,还关乎提交代码的人。对于研究人员来说,抱怨内核维护者不接受他们的代码进入Linux并不稀奇,只是因为一些已知的内核开发人员提交了相同的想法并已经被接受。原因是信任的问题,建立信任是一个难题,提交代码必须是众所周知的,但是不提交代码就不能被人知道。一种方法是从小处着手,作为我们工作的一部分,我们还对KVM进行了各种小的更改,以准备对ARM进行更好的支持,其中包括清理现有代码,使其更通用,以及改进跨平台支持。KVM维护者很高兴接受这些小的改进,它们产生了良好的信誉,并帮助我们为KVM社区所熟知。
结交朋友,融入社区:开源开发实际上是一种社会企业。与社区建立联系非常有帮助,不仅在网上,而且在会议和其他场合亲自参与。例如,在KVM/ARM开发的早期阶段,我们前往英国剑桥的ARM总部,与ARM管理和ARM内核工程团队这两家公司建立了联系,他们都为我们的工作做出了贡献。再举一个例子,在将KVM/ARM集成到Linux内核期间遇见一个问题就是将各种虚拟化接口的集成,例如控制寄存器的读写操作。由于这是一项既定的策略,即永远不要破坏已发布的接口以及与用户空间应用程序的兼容性,所以不能更改现有的接口,并且社区将大量精力放在设计可扩展和可重用的接口上。决定接口的适当性是一种判断,而不是一门精确的科学。我们很幸运地得到了著名内核开发人员的帮助,比如Rusty Russell,他们帮助我们驱动关于接口的实现和通信,特别是用户空间保存和寄存器恢复,这一特性对于调试和虚拟机的迁移都很有用。与Rusty这样的成熟开发人员合作是一个巨大的帮助,因为我们受益于他的经验和在内核社区中的强大声音。
尽早让社区参与:在开发KVM/ARM时,一个重要的问题是如何跨Linux支持的大量可用ARM SoC平台访问HYP模式,一种方法是在初始化KVM时初始化和配置HYP模式,这将把代码更改隔离到KVM子系统中。但是,因为从内核进入HYP模式涉及到一个trap,所以早期引导装载程序必须已经在Hyp模式中安装了代码来处理trap并允许KVM运行。如果没有安装这样的trap处理程序,当ARM core捕获到HYP事件时可能会导致内核崩溃。我们与内核社区合作,在KVM和引导加载程序之间定义正确的ABI,但是很快就发现,与SoC供应商就ABIs达成一致是很困难的。
通过与ARM和开源社区的合作,我们得出了这样的结论:如果我们只是要求内核以HYP模式启动,那么我们就不必依赖于脆弱的ABIs。然后,内核在启动时简单地测试它是否处于HYP模式,在这种情况下,它安装一个trap处理程序来提供一个hook,以便在稍后阶段重新进入HYP模式。必须向内核引导过程中添加少量代码,但其结果是一个更干净、更健壮的机制。如果引导加载程序不知道HYP模式,并且内核没有在HYP模式下启动,
KVM/ARM将检测到这一点,并将保持禁用状态。这个解决方案避免了设计新ABI,而且原来的内核仍然可以工作,因为它们总是将显式切换到内核模式作为它们的第一个指令。这些更改被合并到主线Linux 3.6内核中,并修改了官方的ARM内核引导建议,建议所有引导加载程序以HYP模式引导内核。
了解指挥系统:KVM/ARM有多个可能的上游路径。在历史上,KVM支持的其他架构(如x86和PowerPC)通过KVM树直接合并到Linus Torvalds的树中,并得到相应架构维护人员的适当批准。然而,KVM/ARM需要对其进行一些小的更改,ARM特定的头文件和idmap子系统,因此不清楚代码是通过KVM树进行集成,获得ARM内核维护者的批准,还是通过ARM内核树进行集成。Russell King是ARM内核维护人员,Linus直接从他的ARM内核树中获取ARM相关的代码。这种情况特别有趣,因为Russell King不想在主线内核中合并虚拟化支持,他也没有检查我们的代码。与此同时,KVM社区对集成我们的代码非常感兴趣,但是如果没有ARM维护者的批准,就不能这样做,Russell King拒绝参与关于这个过程的讨论。
持之以恒:当我们尝试合并我们的代码到Linux中时,Linux ARM其实已经发生了很多变化。对于维护人员来说,SoC支持代码中的流失量正成为一个越来越大的问题,许多工作都是为了减少板特定的代码,并支持单个ARM内核镜像可通过多SoC进行引导。鉴于这些正在发生的变化,获得足够的时间让ARM内核维护人员来评审代码是很有挑战性的,而且对于任何合并到ARM树中的新代码,维护人员都有额外的压力,要求他们严格批评。我们别无选择,只能继续维护和改进代码,并定期发布随上游内核更改而更新的补丁系列。最终,ARM维护者之一Will Deacon抽出时间进行了几次全面而有益的评审,在解决了他的问题之后,他批准将我们的代码合入到Linux内核中。经过这一切,当我们认为我们完成了,我们终于收到了Russel King的一些反馈。
当MMIO操作陷入到hypervisor时,虚拟化扩展将填充一个寄存器,其中包含用于模拟指令的有用信息(无论是装载还是存储、源/目标寄存器,以及MMIO访问的长度)。一个老的Linux内核使用的某些指令类型则不是这样填充该寄存器的。因此,KVM/ARM会从内存中加载指令并在软件中对其进行解码。尽管解码实现经过了很多人的测试和审查,但Russell King反对包含这个特性。他已经在其他子系统中实现了多种形式的指令解码,并要求我们要么重写ARM内核的重要部分,以统一所有的指令解码,以提高代码重用,要么从我们的实现中放弃对MMIO指令解码的支持。我们没有追求可能会拖上几个月的重写工作,而是放弃了其他方面都很受欢迎和有用的代码库。我们只能推测这一决定背后的真正动机,因为ARM维护者不会参与有关这个问题的讨论。
经过15个主要补丁的修订和18个多月的时间,KVM/ARM代码在2013年2月被成功通过Russell King的ARM分支合并到Linus的代码树中。在Linux 3.9合并窗口关闭之前让所有这些东西最终走到一起,关键是与内核开发人员保持一个良好的关系,同时也从他们那里得到了很多的帮助,并将持之以恒的推动代码合入所面临的各种挑战。
我们提出了一些实验结果,量化了KVM/ARM在多核ARM硬件上的性能。我们通过在虚拟机中直接在硬件上运行微基准测试和实际应用程序工作负载来评估KVM/ARM与本地执行相比的虚拟化开销。我们测量并比较KVM/ARM与KVM x86的性能、能耗和开销成本,以便证明KVM/ARM在更成熟的硬件虚拟化平台上的有效性。这些结果提供了对ARM硬件虚拟化支持性能的第一个真实硬件度量,以及ARM和x86之间的比较。
ARM的测量结果是使用一个Insignal Arndale board获得的,该开发板是具有一个1.7GHz的双核Cortex -15的三星5250 SoC。这是第一个基于CortexA-15具有硬件虚拟化支持的ARM CPU。USB总线提供100Mb以太网,外部120GB三星840系列SSD驱动器通过eSATA连接到Arndale板。x86测量是使用低功耗移动笔记本电脑平台和行业标准服务器平台获得的。笔记本电脑的平台是一台2011年的MacBook Air,双核1.8GHz内核i7-677M中央处理器,一个三星SM256C 256GB SSD驱动器,和一个苹果100Mb USB以太网适配器。服务器平台是一台专用的OVH SP 3服务器,双核3.4GHz Intel XeonE3 1245v2 CPU,两个物理SSD驱动器,其中只有一个被使用,1GB以太网连接到100Mb的网络。在我们进行实验时,x86硬件还不支持虚拟APIC。
考虑到硬件平台的差异,我们的重点不是测量绝对性能,而是测量每个平台上虚拟化和本机执行之间的相对性能差异。由于我们的目标是评估hypervisor,而不是原始硬件性能,因此这个相对测量是为比较KVM/ARM与KVM x86的虚拟化的性能和功耗提供了一个有用的跨平台基础。
为了提供可比较的测量结果,我们尽可能保持所有硬件平台上的软件环境相同。所有平台上的主机和虚拟机都是Ubuntu 12.10。我们在实验中使用了Linux 3.10内核,并在源代码树的顶部集成了大量支持页面操作的补丁。由于实验是在许多不同的平台上进行的,所以内核配置必须略有不同,但是所有的公共特性在所有平台上都是类似地配置。特别是,在ARM和x86上的来guest虚拟机中都使用了Virtio驱动程序。我们使用QEMU v1.5.0用于我们的测量。所有系统都配置了最多1.5GB RAM,以供测试的guest虚拟机或主机使用。此外,所有多核测量都是使用两个物理内核和带有两个虚拟cpu的guest虚拟机以及单核测量完成的,在客户端和主机系统的内核配置中禁用SMP。x86平台上禁用了超线程,禁用CPU频率缩放,以确保在每个平台上以相同的时钟速率测量本机和虚拟化的性能。
对于涉及网络和另一台服务器的测量,所有系统都使用100Mb以太网。ARM和x86笔记本电脑平台使用Netgear GS608v3交换机连接,2010年iMac使用3.2GHz Core i3 CPU连接,12GB运行Mac OS X Mountain Lion的RAM被用作服务器。x86服务器平台连接到一个100Mb端口,使用OVH网络基础架构和同一数据中心中的另一台相同服务器作为服务器。虽然x86服务器平台所使用的网络基础设施有一些不同,因为它是由其他人控制的,但是我们不认为这些不同会对虚拟化和本机执行之间的相对性能产生任何重大影响。
我们给出了四组实验的结果。首先,我们使用定制的小客户端在多核硬件上测量了hypervisor的各种微观体系结构特征的成本.我们进一步安装了KVM/ARM和KVM x86上的代码,以便在关键路径上的特定点上读取循环计数器,以便更精确地确定开销时间花在哪里。其次,我们使用lmbench v3.0在单核和多核硬件上测量了一些常见的低级操作系统操作的成本。在多核配置上运行lmbench时,我们将每个基准测试过程固定在一个单独CPU的进程上,用于测量多核系统上虚拟机中处理器间通信的真实开销。第三,我们使用单核和多核硬件上的各种工作负载来测量实际应用程序的性能。表2描述了我们使用的8个应用程序的工作负载
第四,我们使用与测量应用程序性能相同的8个应用程序的工作负载来测量功耗结果。ARM功率的测量是用ARM能耗探针来进行的,用于测量连接到Arndale板电源上的分流器的功耗。外部SSD的电源是通过将USB电源线连接到Arndale板上的USB端口来提供的,从而将存储电源纳入在电源处测量的总SoC电源中。x86电源测量使用powerstat工具执行,该工具会读取ACPI信息。powerstat会从电池中获得总的系统电量,所以x86系统上的电量测量来自电池电量,只能在x86笔记本电脑平台上运行。虽然我们没有测量x86服务器平台的功耗,但是x86笔记本的效率预期要低得多,所以使用x86笔记本可以对ARM的能源效率进行保守的比较。x86笔记本电脑的显示和无线功能被关闭,以确保公平的比较。这两种工具都报告了10Hz间隔的瞬时功率消耗,单位是瓦特。测量值取平均值,再乘以测试时间得到能量测量值。