1 简介 5
1.1 文档说明 5
1.1.1 背景 5
1.1.2 内容简介 5
1.1.3 适用范围 5
1.2 缩略语 5
1.3 参考资料 5
2 Virtio 5
2.1 Virtio 介绍 6
2.2 virtio简介 8
2.3 Virtio:一种Linux I/O虚拟化框架 13
2.4 linux virtiommu 18
3 SMMU 19
3.1 缩略语 19
3.2 DMA介绍 20
3.3 虚拟化技术 - I/O虚拟化 22
3.4 透传 - Device Passthrough 22
3.5 虚拟化技术 - 内存虚拟化 23
3.6 软件实现 - 影子页表 24
3.7 硬件辅助 - EPT/NPT 25
3.8 EPT/NPT MMU优化 27
4 SMMU和IOMMU技术 29
4.1 ARM的SMMU 29
4.2 ARM SMMU的原理与IOMMU 33
4.2.1 arm smmu的原理 33
4.2.2 smmu驱动与iommu框架 38
5 iommu-viommu(virtio-iommu) 45
5.1 virtio iommu - full emulate 45
5.2 virtio 详细介绍 46
5.3 IOMMU(四)-dma remapping 51
5.4 在网上搜索信息 51
5.5 virtio 从 virtio 论文开始 52
5.5.1 地址转换 53
5.5.2 Virtio-blk 后端 53
5.5.3 Virtio-blk 前端 54
5.5.4 总结 55
5.6 Qemu模拟IO和半虚拟化Virtio的区别以及I/O半虚拟驱动介绍 55
5.6.1 QEMU基本原理和优缺点 55
5.6.2 半虚拟化virtio基本原理和优缺点 56
6 virtio-iommu 57
6.1 iommu datastruct 57
6.2 virtio iommu patch 60
6.3 dpdk vhost-uesr对vIOMMU的支持 62
6.3.1 DMA remapping 62
6.3.2 Interrupt remapping 65
6.3.3 总结 66
6.4 driver_probe 67
6.5 [2016] An Introduction to PCI Device Assignment with VFIO by Alex Williamson 68
6.6 vfio 73
6.7 KVM详细,太详细太深入,经典 75
6.7.1 vhost-net (kernel-level virtio server) 78
7 pKVM 79
8 smmu_spec 80
9 vfio与iommu关系 81
9.1 vfio 直接介绍设备 82
9.2 VFIO(Virtual Function IO)研究 84
9.2.1 研究目的 84
9.2.2 IOMMU 84
9.3 IOMMU历史知识与和VFIO的联系 85
9.4 Virtual IOMMU(cloud) 88
9.4.1 基本原理 88
9.4.2 为什么是 virtio-iommu? 89
9.5 linux doc for vfio 90
表 1 缩略语
|
|
crosVM |
Rust轻量级虚拟化VMM |
KVM |
内核虚拟化模块 |
pKVM |
Protected KVM |
CROSVM官方文档: Introduction - Book of crosvm
- 本章节介绍了X86版本和ARM64版本crosVM的模拟环境.ARM64的稍复杂一点,需要先模拟一个ARM64 Host Server,再在其上运行crosVM.
为解决这些问题,Rusty Russell开发了virtio机制,其是一个在hypervisor之上的抽象API接口,让客户机知道自己运行在虚拟化环境中,从而与hypervisor根据virtio标准协作,从而在Guest中达到更好的性能(特别是I/O性能),关于virtio在其论文中如此定义:
virtio:a series of efficient,well-maintained Linux drivers which can be adapted for various different hypervisor implementations using a shim layer.This includes a simple extensible feature mechanism for each driver.
理解: 正如Linux提供了各种hypervisor解决方案,这些解决方案都有自己的特点和优点。这些解决方案包括 Kernel-based Virtual Machine (KVM)、lguest和User-mode Linux等。在这些解决方案中需要各自实现自身平台下的设备虚拟化。而
virtio 是一种前后端架构,包括前端驱动(Front-End Driver)和后端设备(Back-End Device)以及自身定义的传输协议。通过传输协议,virtio不仅可以用于QEMU/KVM方案,也可以使用其他的虚拟化方案。如虚拟机可以不必是QEMU,也可以是其他类型的虚拟机,后端不一定要在QEMU中实现,也可以在内核中实现(即vhost方案)
照传输协议的格式进行解析,对于网卡等需要实际物理设备交互的请求,后端驱动会对物理设备进行操作,从而完成请求,并且会通过中断机制通知前端驱动。
virtio前端和后端驱动的数据传输通过
图1 vring原理
virtio设备是由一个PCI的控制器添加的,其本质上是一个virtio设备,会挂到virtio总线上,所以PCI总线上只会显示其驱动为virtio-pci
eventfd 是一个用来通知事件的文件描述符,timerfd 是定时器时间的文件描述符。二者都是内核向用户空间的应用发送通知的机制,可以有效地被用来实现用户空间的事件/通知驱动的应用程序
简单来说,就是eventfd用来触发事件通知,timerfd用来触发将来的事件通知
eventfd不仅可以用于进程之间的通信,还能用于用户态和内核态的通信
eventfd本质上是一个系统调用,创建一个事件通知fd,在内核内部创建有一个eventfd对象,可以用来实现进程之间的等待/通知机制,内核也可以利用eventfd来通知用户态进程事件
百度百科 virtio
除了前端驱动程序(在来宾操作系统中实现)和后端驱动程序(在 hypervisor 中实现)之外,virtio 还定义了两个层来支持来宾操作系统到 hypervisor 的通信。
- kvm虚拟化基础
virtio 标准,是一个与Hypervisor 独立的、构建设备驱动的接口,允许多种HyperVisor使用一组相同的设备驱动程序,能够实现更好的对客户机的互操作性
virtio是一个沟通客户机前端设备与宿主机上设备后端模拟的比较高性能的协议
参考链接:virtio 简介 - bakari - 博客园
什么是 virtio
virtio 是一种 I/O 半虚拟化解决方案,是一套通用 I/O 设备虚拟化的程序,是对半虚拟化 Hypervisor 中的一组通用 I/O 设备的抽象。提供了一套上层应用与各 Hypervisor 虚拟化设备(KVM,Xen,VMware等)之间的通信框架和编程接口,减少跨平台所带来的兼容性问题,大大提高驱动程序开发效率。
#为什么是 virtio
在完全虚拟化的解决方案中,guest VM 要使用底层 host 资源,需要 Hypervisor 来截获所有的请求指令,然后模拟出这些指令的行为,这样势必会带来很多性能上的开销。半虚拟化通过底层硬件辅助的方式,将部分没必要虚拟化的指令通过硬件来完成,Hypervisor 只负责完成部分指令的虚拟化,要做到这点,需要 guest 来配合,guest 完成不同设备的前端驱动程序,Hypervisor 配合 guest 完成相应的后端驱动程序,这样两者之间
由于不同 guest 前端设备其工作逻辑大同小异(如块设备、网络设备、PCI设备、balloon驱动等),单独为每个设备定义一套接口实属没有必要,而且还要考虑扩平台的兼容性问题,另外,不同后端 Hypervisor 的实现方式也大同小异(如KVM、Xen等),这个时候,就需要一套通用框架和标准接口(协议)来完成两者之间的交互过程,virtio 就是这样一套标准,它极大地解决了这些不通用的问题。
virtio 的架构#
从总体上看,virtio 可以分为四层,包括前端 guest 中各种驱动程序模块,后端 Hypervisor (实现在Qemu上)上的处理程序模块,中间用于前后端通信的 virtio 层和 virtio-ring 层,virtio 这一层实现的是虚拟队列接口,算是前后端通信的桥梁,而 virtio-ring 则是该桥梁的具体实现,它实现了两个环形缓冲区,分别用于保存前端驱动程序和后端处理程序执行的信息。
严格来说,virtio 和 virtio-ring 可以看做是一层,virtio-ring 实现了 virtio 的具体通信机制和数据流程。或者这么理解可能更好,virtio 层属于控制层,负责前后端之间的通知机制(kick,notify)和控制流程,而 virtio-vring 则负责具体数据流转发。
virtio 数据流交互机制#
vring 主要通过两个环形缓冲区来完成数据流的转发,如下图所示。
vring 包含三个部分,描述符数组 desc,可用的 available ring 和使用过的 used ring。
desc 用于存储一些关联的描述符,每个描述符记录一个对 buffer 的描述,available ring 则用于 guest 端表示当前有哪些描述符是可用的,而 used ring 则表示 host 端哪些描述符已经被使用。
Virtio 使用 virtqueue 来实现 I/O 机制,每个 virtqueue 就是一个承载大量数据的队列,具体使用多少个队列取决于需求,例如,virtio 网络驱动程序(virtio-net)使用两个队列(一个用于接受,另一个用于发送),而 virtio 块驱动程序(virtio-blk)仅使用一个队列。
具体的,假设 guest 要向 host 发送数据,首先,guest 通过函数 virtqueue_add_buf 将存有数据的 buffer 添加到 virtqueue 中,然后调用 virtqueue_kick 函数,virtqueue_kick 调用 virtqueue_notify 函数,通过写入寄存器的方式来通知到 host。host 调用 virtqueue_get_buf 来获取 virtqueue 中收到的数据。
存放数据的 buffer 是一种分散-聚集的数组,由 desc 结构来承载,如下是一种常用的 desc 的结构:
当 guest 向 virtqueue 中写数据时,实际上是向 desc 结构指向的 buffer 中填充数据,完了会更新 available ring,然后再通知 host。
当 host 收到接收数据的通知时,首先从 desc 指向的 buffer 中找到 available ring 中添加的 buffer,映射内存,同时更新 used ring,并通知 guest 接收数据完毕。
总结:#
virtio 是 guest 与 host 之间通信的润滑剂,
virtio 抽象了一套 vring 接口来完成 guest 和 host 之间的数据收发过程,结构新颖,接口清晰。
参考:Virtio:一种Linux I/O虚拟化框架 - 安全客,安全资讯平台
简言之,virtio是设备和半虚拟化管理程序(paravirtualized hypervisor)之间的一个抽象层。virtio是Rusty Russell为了支持他自己的虚拟化方案lguest而开发的。这篇文章以对半虚拟化和设备仿真的介绍开始,然后探寻virtio中的一些细节。采用kernel 2.6.30版本的virtio框架进行讲解。
Linux是hypervisor的“游乐场”。正如我在文章使用Linux作为hypervisor中所展现的,Linux提供了许多具有不同特性和优势的虚拟化解决方案。例如KVM(Kernel-based Virtual Machine),lguest、和用户态的Linux。在Linux使用这些虚拟化解决方案给操作系统造成了重担,因为它们各自都有独立的需求。其中一个问题就是设备的虚拟化。virtio为各种各样设备(如:网络设备、块设备等等)的虚拟提供了通用的标准化前端接口,增加了各个虚拟化平台的代码复用。而不是各自为政般的使用繁杂的设备虚拟机制。
在Linux使用这些虚拟化解决方案给操作系统造成了重担,因为它们各自都有独立的需求。其中一个问题就是
全虚拟化 vs. 半虚拟化
我们先来讨论两种完全不同的虚拟化方案:全虚拟化和半虚拟化。在全虚拟化中,客户操作系统在hypervisor上运行,相当于运行于裸机一般。客户机不知道它在虚拟机还是物理机中运行,不需要修改操作系统就可以直接运行。与此相反的是,
译注:客户机与hypervisor的切换举例:客户机请求I/O,需要hypervisor中所虚拟的设备来响应请求,此时就会发生切换。
硬件也随着虚拟化不断地发展着。新处理器加入了高级指令,使客户机操作系统和hypervisor的切换更加高效。硬件也随着I/O虚拟化不断地发生改变(参照Resource了解PCI passthrough和单/多根I/O虚拟化)。
在传统的全虚拟化环境中,hypervisor必须陷入(trap)请求,然后模仿真实硬件的行为。尽管这样提供了很大的灵活性(指可以运行不必修改的操作系统),但却造成了低效率(图1左侧)。图1右侧展示了
作者注:virtio并不是半虚拟化领域的唯一存在。Xen提供了半虚拟化的设备驱动,VMware也提供了名为Guest Tools的半虚拟化支持。
Linux客户机中的一种抽象
如前节所述,
注意在实际中,使用用户空间的QEMU程序来进行设备仿真,所以后端驱动通过QEMU的I/O来与用户空间的hypervisor通信。QEMU是系统模拟器,包括提供客户机操作系统虚拟化平台,提供整个系统的仿真(PCI host controller, disk, network, video hardware, USB controller等)。
Virtio 架构
除了前端驱动(在客户机操作系统中实现)和后端驱动(在hypervisor中实现)之外,
如图3,包含了五种前端驱动:块设备(如硬盘)、网络设备、PCI仿真、balloon驱动(用于动态的管理客户机内存使用)和一个终端驱动。每一个前端驱动,在hypervisor中都有一个相匹配的后端驱动。
概念层级
在客户机的视角来看,对象层级如图4所示。顶端是virtio_driver,表示客户机中的前端驱动。
图4. virtio前端的对象层级
这一过程起始于virtio_driver的创建和后续的使用register_virtio_driver将驱动进行注册。virtio_driver结构定义了设备驱动的上层结构,包含了它所支持的设备的设备ID,特性表(根据设备的类型有所不同),和一系列回调函数。当hypervisor发现新设备,并且匹配到了设备ID,就会以virtio_device为参数调用probe函数(于virtio_driver中提供)。这一结构与管理数据一起被缓存(以独立于驱动的方式)。根据设备的类型,virtio_config_ops中的可能会被调用,以获取或设置设备相关的选项(例如,获取硬盘块设备的读/写状态或者设置块设备的块大小)。
注意,
virtqueue结构包含可选的回调函数(用于在hypervisor填充缓冲后,通知客户机)、一个指向virtio_device、一个指向virtqueue操作和一个特别的priv用于底层实现使用。callback是可选的,也可以动态的启用或禁用。
这个层级的核心是virtqueue_ops,其中定义了如何在客户机和hypervisor之间传输命令与数据。我们先来探索virtqueue中对象的添加和删除操作。
Virtio缓冲
客户机驱动(前端)与hypervisor(后端)通过缓冲区进行通信。对于一次I/O,客户机提供一个或多个缓冲区表示请求。例如,你可以使用三个缓冲区,其中一个用来存储读请求,其他两个用来存储回复数据。内部这个配置被表示为分散/聚集(scatter-gather)列表(列表中的每个元素存储有缓冲区地址与长度)。
核心API
将客户机驱动与hypervisor驱动链接起来,偶尔是通过virtio_device,大多数情况下都是通过virtqueue。virtqueue支持五个API函数。使用第一个函数add_buf向hypervisor添加请求,这种请求以分散/聚集列表的形式,正如先前讨论的。为了提交请求,客户机必须提供请求命令,分散/聚集列表(以缓冲区地址和长度为元素的数组),向外提供请求的缓冲区的数量(也就是发送请求信息给hypervisor),向内传递数据的缓冲区的数量(hypervisor用来填充数据,返回给客户机)。当客户机通过add_buf向hypervisor提交一条请求后,客户机就可以使用kick通知hypervisor新请求已递送。但为了更好地性能,客户机应该在kick通知hypervisor之前,提交尽可能多的请求。
客户机使用get_buf接收从hypervisor中返回的数据。客户机可以简单地使用get_buf轮询或者等待由virtqueue callback函数的通知。当客户机知道了缓冲区数据可用,就会使用get_buf获取数据。
最后两个virtqueue的API是enable_cb和disable_cb,可用使用这两个函数启用和禁用回调函数(callback函数使用find_vq初始化设置)。注意回调函数与hypervisor在不同的地址空间,所以调用需要间接调用(indirect hypervisor call)(例如:kvm_hypercall)。
缓冲区的格式、顺序与内容仅对前端和后端驱动有意义。内部传送(现在使用环形缓冲区实现)只传输缓冲区,并不知道内部表达的意义。
Virtio驱动例子
对于各种各样前端驱动,可以在Linux内核源码的./drivers子目录下找到。virtio网络驱动在./driver/net/virtio_net.c,virtio块驱动在./driver/block/virtio_blk.c。./driver/virtio子目录下提供了virtio接口的实现(virtio设备、驱动、virtqueue和环形缓冲区)。
你可以在Linux内核中练习半虚拟化基础工作。你所需要的就是一个作为hypervisor的内核,客户机内核和用来仿真设备的QEMU。你可以使用KVM(一个存在于宿主机内核中的模块)或者Rusty Russell的lguest(一个修改过的Linux内核)。两种方案都支持virtio(配合以QEMU进行系统模拟和libvirt进行虚拟化管理)。
Rusty的成果是简化了半虚拟化驱动的开发,并且设备仿真性能更高。最重要的还是,virtio能够提供更好地性能(两三倍的网络I/O)比现有的商业解决方案。虽说有一定的代价,但如果你的hypervisor和客户机系统是Linux,还是非常值得的。
进一步
尽管你可以永远不会为virtio开发前端或者后端驱动,但它实现了一个有趣的架构,值得更加细致的理解它。与先前的Xen相比,virtio为了半虚拟化提高性能提供了新的可能。在作为投入使用的hypervisor和新虚拟技术的实验平台中,Linux不断地证明了它自己。virtio再一次证明了Linux作为hypervisor的优势和开放性。
参考链接:[RFC 0/3] virtio-iommu: a paravirtualized IOMMU
The description itself seemed too long for a single email, so I split it
into three documents, and will attach Linux and kvmtool patches to this
email.
1. Firmware note,
2. device operations (draft for the virtio specification),
3. future work/possible improvements.
Just to be clear on the terms I'm using:
pIOMMU physical IOMMU, controlling DMA accesses from physical devices
GVA, GPA, HVA, HPA
Guest/Host Virtual/Physical Address
IOVA I/O Virtual Address, the address accessed by a device doing DMA through an IOMMU.
Note: kvmtool is GPLv2. Linux patches are GPLv2, except for UAPI
virtio-iommu.h header, which is BSD 3-clause. For the time being, the
specification draft in RFC 2/3 is also BSD 3-clause.
参考链接:ARM SMMU原理与IOMMU技术(“VT-d” DMA、I/O虚拟化、内存虚拟化)_rtoax的博客-CSDN博客_smmu 虚拟化
表 1 virtio iommu缩略语
|
|
KVM |
Kernel-based Virtual Machine,一种基于Linux内核的Type II虚拟机技术 |
ASID |
Address Space ID 地址空间标识符 |
CD |
Context Descriptor; 上下文描述符 |
CTP |
Context-table pointer 上下文表指针 |
EPT |
Extended Page Table 扩展页表 |
GPA |
Guest Phyical Address guest 物理地址 |
GVA |
Guest Virtual Address |
HPA |
Host Phyical Address |
IOVA |
IO Virtual Address space IO虚拟地址空间 |
IPA |
Intermediate Phyical Address |
NPT |
Nested Page Table 嵌套页表 |
PCID |
Process context identifier 进程上下文标识符 |
PMCG |
Performance Monitor Counter Groups 性能监控计数器组 |
S2TTB |
Stage 2 Translate Table Base |
SMMU |
System MMU 系统MMU |
VT-d |
Virtualization Technology for Direct I/O 直接I/O虚拟化技术 |
关键:
MMU地址翻译是将进程的虚拟地址(HVA)翻译成物理地址(HPA);
IOMMU地址翻译则是将虚拟机物理地址空间内的GPA翻译成HPA;
IOMMU页表和MMU页表一样,都采用了多级页表的方式来进行翻译;
专门转换I/O地址的MMU在x86的阵营里就是IOMMU;
Intel把IOMMU技术叫做VT-d(Virtualization Technology for Direct I/O);
EPT/NPT MMU作为传统MMU的扩展,也是有TLB;
ARM公司主要是依靠出售core的license来赚钱的;
能够有能力完成设备iova 到 pa转换的有很多,例如有intel iommu, amd的iommu
DMA是指在不经过CPU干预的情况下外设直接访问(Read/Write)主存(System Memroy)的能力。
在设备直通(Device Passthough)的虚拟化场景下,直通设备在工作的时候同样要使用DMA技术来访问虚拟机的主存以提升IO性能。那么问题来了,直接分配给某个特定的虚拟机的,我们必须要保证直通设备DMA的安全性,一个VM的直通设备不能通过DMA访问到其他VM的内存,同时也不能直接访问Host的内存,否则会造成极其严重的后果。因此,必须对直通设备进行“DMA隔离”和“DMA地址翻译”,
为什么直通设备会存在DMA访问的安全性问题呢?
原因也很简单:由于直通设备进行DMA操作的时候guest驱动直接使用gpa来访问内存的,这就导致如果不加以隔离和地址翻译必然会访问到其他VM的物理内存或者破坏Host内存,因此必须有一套机制能够将gpa转换为对应的hpa这样直通设备的DMA操作才能够顺利完成。
VT-d DMA Remapping的引入就是为了解决直通设备DMA隔离和DMA地址翻译的问题
地址空间标志(address-space-identifier)
VT-d中引入root-table和context-table的目的比较明显,这些额外的table的存在就是为了记录每个直通设备和其被分配的Domain之间的映射关系。
在虚拟化系统中,I/O外设只有一套,需要被多个guest VMs共享。VMM/hypervisor提供了两种机制来实现对I/O设备的访问,一种是
所谓passthrough,就是指guest VM可以透过VMM,直接访问I/O硬件,这样guest VM的I/O操作路径几乎和无虚拟化环境下的I/O路径相同,性能自然是非常高的。
在虚拟化环境下,guest VM使用的物理地址是GPA,如果直接用guest OS中的驱动程序去操作I/O设备的话(这里的I/O限定于和内存统一编址的MMIO),那么设备使用的地址也是GPA。这倒不难办,使用CPU的EPT/NPT MMU查询对应guest VM的nPT页表,进行一下GPA->HPA的转换就可以了。
可是别忘了,有一些I/O设备是具备DMA(Direct Memory Access)功能的。由于DMA是直接在设备和物理内存之间传输数据,必须使用实际的物理地址(也就是HPA),但DMA本身是为了减轻CPU的处理负担而存在的,其传输过程并不经过CPU。对于一个支持DMA传输的设备,当它拿着GPA去发起DMA操作时,由于没有真实的物理内存地址,传输势必会失败。
然而,不和AMD使用相同的名字是Intel一贯的路数,所以Intel通常更愿意把这种硬件辅助的I/O虚拟化技术叫做VT-d(Virtualization Technology for Direct I/O)。作为后起之秀的ARM自然也不甘示弱,推出了对应的SMMU(System MMU)。
这里为了支持device passthrough(透传)模式下的DMA传输,
Device passthrough(透传)机制要求VMM为guest VM分配好设备,并提供隔离。假设系统中现在有三个guest VMs,编号分别是0, 1, 2,如果VM 0分配到了网卡A,就要阻止VM 1和VM 2对网卡A的访问。
可以采用的方法是在拥有设备的guest VM加载驱动程序前,先给要分配出去的设备加载一个伪驱动作为占位符,由于没有真正的驱动程序,这个设备对于其他的guest VM来说就相当于是“隐藏”了。
这同时也暴露了使用device passthrough存在的一个问题,
大型操作系统(比如Linux)的内存管理的内容是很丰富的,而内存的虚拟化技术在OS内存管理的基础上又叠加了一层复杂性,比如我们常说的虚拟内存(virtual memory),如果使用虚拟内存的OS是运行在虚拟机中的,
在Linux这种使用虚拟地址的OS中,虚拟地址经过page table转换可得到物理地址
如果这个操作系统是运行在虚拟机上的,那么这只是一个中间的物理地址(Intermediate Phyical Address - IPA),需要经过VMM/hypervisor的转换,才能得到最终的物理地址(Host Phyical Address - HPA)。从VMM的角度,guest VM中的虚拟地址就成了GVA(Guest Virtual Address),IPA就成了GPA(Guest Phyical Address)。
可见,如果使用VMM,并且guest VM中的程序使用虚拟地址(如果guest VM中运行的是不支持虚拟地址的RTOS,则在虚拟机层面不需要地址转换),那么就需要两次地址转换。