X86平台上的I/O虚拟化
什么是虚拟化?
抽象来说,虚拟化是资源的逻辑表示,它不受物理设备的约束。
具体来说,虚拟化技术的实现形式是在系统中加入一个虚拟化层,虚拟化层将下层的资源抽象成另一种形式资源,提供给上层使用。通过空间上的分割,时间上的分时以及模拟,虚拟化可以将一份资源抽象成多份。反过来说,虚拟化也可以将多份资源抽象成一份。总的来说,虚拟化抽象了硬件层,允许多种不同的负载能共享一组资源。
虚拟化的好处是什么?各大企业和商业机构从虚拟化中可以得到非常大的效率提升,因为虚拟化可以显著提高服务器的使用率,能够进行动态分配、管理资源和负载的相互隔离,并提高安全性和自动化。虚拟化还可以提供按需的服务配置和软件定义的资源编排,可以根据实际业务需求在云平台上扩展某类服务。
I/O虚拟化是一个需要重要关注的地方,其接口选择、性能高低决定了该方案的成败。DPDK同样可以对I/O虚拟化助一臂之力。
X86平台虚拟化概述
虚拟化实现主要有三个部分实现:CPU虚拟化、内存虚拟化和I/O虚拟化。
Intel为了简化软件复杂度、提高性能和增强安全性,对其IA架构进行扩展,提出了硬件辅助虚拟化技术VT-x和VT-d。
CPU虚拟化
INTEL VT-x对处理器进行了扩展,引入了两个新的模式:VMX根模式和VMX非根模式。宿主机运行所在的模式是根模式,客户机运行所在的模式是非根模式。
引入这两种模式可以很好地解决虚拟化漏洞的问题。通常指令的虚拟化是通过陷入再模拟的方式实现的,而IA架构有多条敏感指令不能通过这种方法处理,否则会导致虚拟化漏洞。最直观的解决办法就是让这些敏感指令能够出发异常。非根模式下所有敏感指令的行为都被重新定义,使得他们能不经过虚拟化就直接运行或通过陷入在模拟的方式来处理,在根模式下,所有指令的行为和传统的IA一样,没有改变。
非根模式下敏感指令引起的陷入被称为VM-EXIT,当VM-Exit发生时,CPU自动从非根模式切换成根模式,相应地,VT-x也定义了 VM-Entry,该操作有宿主机发起,通常是调度某个客户机运行,此时CPU从根模式切换成为非根模式。
其次,为了更好地支持CPU虚拟化,VT-x引入了VMCS(Virtual-machine Control structure,虚拟机控制结构)。VMCS保存虚拟CPU需 要的相关状态,例如CPU在根模式和非根模式下的特权寄存器的值。 VMCS主要供CPU使用,CPU在发生VM-Exit和VM-Entry时都会自动查 询和更新VMCS,以加速客户机状态切换时间。宿主机可以通过指令来 配置VMCS,进而影响CPU的行为。
内存虚拟化
内存虚拟化的主要任务是实现地址空间的虚拟化,他引入了一层新的地址空间,即客户机物理地址空间。
内存虚拟化通过两次地址转换来支持地址空间的虚拟化,即客户机虚拟地址GVA(Guest Virtual Address)到客户机物理地址GPA(Guest Physical Address)再到宿主机物理地址HPA(Host Physical Address)的转换。
GVA到GPA由客户机操作系统决定。GPA到HPA由宿主机决定。
传统的IA架构只支持一次地址转换,即通过CR3指定的页表来实现虚拟地址到物理地址的转换,这和内存虚拟化所要求的两次地址转换产生了矛盾。为了解决这个问题,Intel VT-x提供了扩展页表(EPT)技术,直接在硬件上支持了GVA—>GPA—>HPA 的两次地址转换,大大降低了内存虚拟化软件的实现难度,也提高了内存虚拟化的性能。
I/O虚拟化
I/O虚拟化包括管理虚拟设备和共享物理硬件之间的I/O请求的路由选择。
目前实现I/O虚拟化有三种方式:I/O全虚拟化、I/O半虚拟化和I/O透传。他们在处理客户机和宿主机通信以及宿主机架构上分别采用了不同的处理方式。
I/O全虚拟化
这种方式可以模拟一些真实设备。一个设备的所有功能或总线结构(如设备枚举、识别、中断和DMA)都可以在宿主机中模拟。客户机所能看到的就是一组统一的I/O设备。宿主机截获客户 机对I/O设备的访问请求,然后通过软件模拟真实的硬件。这种方式对 客户机而言非常透明,无需考虑底层硬件的情况,不需要修改操作系 统。宿主机必须从设备硬件的最底层开始模拟,尽管这样可以模拟得很 彻底,以至于客户机操作系统完全不会感知到是运行在一个模拟环境 中,但它的效率比较低。
I/O半虚拟化
半虚拟化的意思就是说客户机操作系统能够感知到自己是虚拟机。
对于I/O系统来说,通过前端驱动/后端驱动模拟实现I/O虚拟化。客户机中的驱动程序为前端,宿主机提供的与客户机通 信的驱动程序为后端。
前端驱动将客户机的请求通过与宿主机间的特殊 通信机制发送给后端驱动,后端驱动在处理完请求后再发送给物理驱动。
不同的宿主机使用不同的技术来实现半虚拟化。比如说XEN,就是通过事件通道、授权表以及共享内存的机制来使得虚拟机中的前端驱动 和宿主机中的后端驱动来通信。
对于KVM使用virtio,和xen的半虚拟化 网络驱动原理差不多。
那么半虚拟化相对全虚 拟化有什么好处?
半虚拟化虽然和全虚拟化一样,都是使用软件完成虚 拟化工作,但是机制不一样。在全虚拟化中是所有对模拟I/O设备的访 问都会造成VM-Exit,而在半虚拟化中是通过前后端驱动的协商,使数 据传输中对共享内存的读写操作不会VM-Exit,这种方式由于不像模拟 器那么复杂,软件处理起来不至于那么慢,可以有更高的带宽和更好的 性能。但是,这种方式与I/O透传相比还是存在性能问题,仍然达不到物理硬件的速度。
I/O透传
直接把物理设备分配给虚拟机使用,例如直接分配一个硬盘或网卡给虚拟机。这种方式需要硬件平台具备I/O透传技术, 例如Intel VT-d技术。它能获得近乎本地的性能,并且CPU开销不高。 DPDK支持半虚拟化的前端virtio和后端vhost,并且对前后端都有性 能加速的设计,这些将分别在后面两章介绍。而对于I/O透传,DPDK可 以直接在客户机里使用,就像在宿主机里,直接接管物理设备进行操作。
I/O透传虚拟化
I/O透传带来的好处是高性能,几乎可以获得本机的性能,这个主要是因为Intel VT-d的技术支持,在执行IO操作时大量减少甚至避免VM-Exit陷入到宿主机中。目前只有PCI和PCI-e设备支持Intel VT-d技 术。
它的不足有以下两点:
-
x86平台上的PCI和PCI-e设备是有限的,大量使用VT-d独立分配 设备给客户机,会增加硬件成本。
-
PCI/PCI-e透传的设备,其动态迁移功能受限。动态迁移是指将一个客户机的运行状态完整保存下来,从一台物理服务器迁移到另一台服务器上,很快地恢复运行,用户不会察觉到任何差异。原因在于宿主 机无法感知该透传设备的内部状态,因此也无法在另一台服务器恢复其 状态。
针对上述问题的解决方案:
-
在物理主机上,仅少数对IO性能要求高的客户机使用VT-d直接 分配设备,其他的客户机可以使用纯模拟或者virtio以达到多个客户机共 享一个设备的目的。
-
在客户机里,分配两个设备,一个是PCI/PCI-e透传设备,一个 是模拟设备。DPDK通过bonding技术把这两个设备设成主备模式。当需要动态迁移时,通过DPDK PCI/PCI-e热插拔技术把透传设备从系统中拔出,切换到模拟设备工作,动态迁移结束后,再通过PCI/PCI-e热插拔技 术把透传设备插入系统中,切换到透传设备工作。
-
可以选择SR-IOV,让一个网卡生成多个独立的虚拟网卡,把这些虚拟网卡分配给每一个客户机,可以获得相对好的性能,但是这种方 案也受限于PCI/PCIe带宽或者是SR-IOV扩展性的性能。
Intel VT-d简介
在I/O透传虚拟化中,一个难点是设备DMA操作如何直接访问到宿主机的物理地址。客户机操作系统看到的地址空间和宿主机的物理地址空间并不是一样的。当一个虚拟机直接和IO设备对话时,它提供给这个设备的地址是虚拟机物理地址GPA,那么设备拿着这个虚拟机物理地 址GPA去发起DMA操作势必会失败。
如何解决这个问题?
办法是进行一个地址转换,将GPA转换成 HPA主机物理地址,那么设备发起DMA操作时用的是HPA,这样就能拿到正确的地址。而Intel VT-d就是完成这样的一个工作,在芯片组里引入了DMA重映射硬件,以提供设备重映射和设备直接分配的功能。
VT-d技术还引入了域的概念,抽象地被定义为一个隔离的环境,宿主机物理内存的一部分是分配给域的。对于分配给这个域的I/O设备, 那么它只可以访问这个域的物理内存。在虚拟化应用中,宿主机把每一个虚拟机当作是一个独立的域。
VT-d主要给宿主机提供了一下的功能:
-
I/O设备的分配:可以灵活地把I/O设备分配给虚拟机,把对虚拟机的保护和隔离的特性扩展到IO的操作上来。
-
DMA重映射:可以支持来自设备DMA的地址翻译转换。
-
中断重映射:可以支持来自设备或者外部中断控制器的中断的隔离和路由到对应的虚拟机。
-
可靠性:记录并报告DMA和中断的错误给系统软件,否则的话可能会破坏内存或影响虚拟机的隔离。
PCIe SR- IOV概述
有了PCI/PCI-e透传技术,将物理网卡直接透传到虚拟机,虽然大大提高了虚拟机的吞吐量,但是一台服务器可用的物理网卡有限,如何才 能实现水平拓展?因此,SR-IOV技术应运而生。
SR-IOV技术是由PCI-SIG制定的一套硬件虚拟化规范,全称是 Single Root IO Virtualization(单根IO虚拟化)。SR-IOV规范主要用于网卡(NIC)、磁盘阵列控制器(RAID controller)和光纤通道主机总线适配器(Fibre Channel Host Bus Adapter,FC HBA),使数据中心达 到更高的效率。SR-IOV架构中,一个I/O设备支持最多256个虚拟功能, 同时将每个功能的硬件成本降至最低。
SR-IOV引入了两个功能类型:
-
PF(Physical Function,物理功能):这是支持SR-IOV扩展功能的 PCIe功能,主要用于配置和管理SR-IOV,拥有所有的PCIe设备资源。PF在系统中不能被动态地创建和销毁(PCI Hotplug除外)。
-
VF(Virtual Function,虚拟功能):“精简”的PCIe功能,包括数据 迁移必需的资源,以及经过谨慎精简的配置资源集,可以通过PF创建和销毁。
PCIe网卡透传下的收发包流程
在虚拟化VT-x和VT-d打开的x86平台上,如果把一个网卡透传到客户机中,其收发包的流程与在宿主机上直接使用的一样,主要的不同在 于地址访问多了一次地址转换。下图是网卡转发操作交互示意图:
1.CPU填充缓冲地址到接收侧描述符 | 7.CPU填充更改包内容,做发送准备 |
---|---|
2.网卡读取接收侧描述符获取缓冲区地址 | 8.CPU读发送侧描述符,检查是否有发送完成标志 |
3.网卡将包的内容写到缓冲区地址处 | 9.CPU将准备发送的缓冲区地址填充到发送侧描述符中 |
4.网卡回写接收侧描述符更新状态(确认包内容已写完) | 10.网卡读取发送侧描述符中地址 |
5.CPU读取接收侧描述符确定包接收完毕 | 11.网卡根据描述符中的地址,读取缓冲区中数据内容 |
6.CPU读取包内容做转发判断 | 12网卡写发送侧描述符,更新发送已完成标志 |
其中步骤1、5、6、7、8、和9是需要CPU对内存的操作。在虚拟化下,在对该内存地址的第一次访问,需要进行两次地址转换:客户机的 虚拟地址转换成客户机的物理地址,客户机的物理地址转换成宿主机的物理地址。这个过程和网卡直接在宿主机上使用相比,多了一次客户机的物理地址到宿主机的物理地址的转换,这个转换是由Intel VT-x技术中的EPT技术来完成。这两次的地址转换结果会被缓存在CPU的Cache 和TLB中。对该内存地址的再次访问,如果命中CPU的cache或TLB,则无需进行两次地址转换,其开销就很小了;如果不命中,则还需重新进 行两次地址转换。 剩下的步骤都是网卡侧发起的操作,也需要对内存操作。在非虚拟 化下,宿主机里的网卡进行操作时,无论DMA还是对描述符的读写, 直接用的就是物理地址,不需要地址转换。在虚拟化下,在对该内存地 址的第一次访问,需要进行一次地址转换,客户机的物理地址转换成宿 主机的物理地址。同直接在宿主机上使用相比,多了这一次地址转换, 这个转换是由Intel VT-d技术的DMA重映射来完成。这个地址转换结果 会被缓存在VT-d的IOTLB中。对该内存地址的再次访问,如果命中 IOTLB,则无需进行地址转换,其开销就小;如果不命中,则还需再次 做地址转换。为了增加IOTLB命中的概率,建议采用大页。
I/O透传虚拟化配置的常见问题
问题1:Intel®的服务器,BIOS打开VT-x和VT-d,想把一个PCIe网卡透传到虚拟机中,发现虚拟机启动失败,提示IOMMU没有找到。
解答:不仅要在BIOS设置上打开VT-d,还要在Linux内核启动选项中设置intel_iommu=on。有些Linux内核版本可能缺省没有使能
CONFIG_INTEL_IOMMU。
问题2:Intel的服务器,BIOS打开VT-x和VT-d,Linux内核启动参数有intel_iommu=on,但想在宿主机上运行DPDK程序。把主机上网卡
端口绑定到igb_uio驱动,然后运行DPDK程序,会遇到错误,dmesg会报DMAR错误,如“dmar:DMAR:[DMA Read]Request device[07:00.1]fault addr 1f73220000”。
解答:两个解决办法。如果还需要使用VT-d透传技术把另一个PCI/PCI-e设备分配给虚拟机,则在内核启动参数中设置iommu=pt。“iommu=pt”会让主机内核跳过地址的翻译(绕过VT-d),直接使用程序给的地址来做DMA,不需要查询IO页表做地址翻译,这
样的设置不会影响虚拟机的DMA重映射。
如果系统不需要使用VT-d透传技术分配一个PCI/PCI-e设备给虚拟机,则可以在BIOS中关闭VT-d,或把内核启动参数intel_iommu=on改为intel_iommu=off。
问题3:在与问题2同样的配置环境下,不过在主机上把网卡端口绑定到vfio-pci驱动,而不是igb_uio驱动,然后运行DPDK程序,结果程序能够正常运行。这里也没有设置iommu=pt,为什么又可以工作了呢?
解答:VFIO是一个IOMMU的用户态驱动,它可以在用户态配置IOMMU。当使用vfio驱动时,DPDK会设置DMA映射,直接把物理地
址作为DMA的地址,这样就不需要查询页表做地址翻译。而igb_uio如果不直接配置IOMMU,就绕不过去IOMMU的地址翻译。