virtio 简介

什么是 virtio

virtio 是一种 I/O 半虚拟化解决方案,是一套通用 I/O 设备虚拟化的程序,是对半虚拟化 Hypervisor 中的一组通用 I/O 设备的抽象。提供了一套上层应用与各 Hypervisor 虚拟化设备(KVM,Xen,VMware等)之间的通信框架和编程接口,减少跨平台所带来的兼容性问题,大大提高驱动程序开发效率。

 

为什么是 virtio-----针对 Linux 的抽象

在完全虚拟化的解决方案中,guest VM 要使用底层 host 资源,需要 Hypervisor 来截获所有的请求指令,然后模拟出这些指令的行为,这样势必会带来很多性能上的开销。半虚拟化通过底层硬件辅助的方式,将部分没必要虚拟化的指令通过硬件来完成,Hypervisor 只负责完成部分指令的虚拟化,要做到这点,需要 guest 来配合,guest 完成不同设备的前端驱动程序,Hypervisor 配合 guest 完成相应的后端驱动程序,这样两者之间通过某种交互机制就可以实现高效的虚拟化过程。

由于不同 guest 前端设备其工作逻辑大同小异(如块设备、网络设备、PCI设备、balloon驱动等),单独为每个设备定义一套接口实属没有必要,而且还要考虑扩平台的兼容性问题,另外,不同后端 Hypervisor 的实现方式也大同小异(如KVM、Xen等),这个时候,就需要一套通用框架和标准接口(协议)来完成两者之间的交互过程,virtio 就是这样一套标准,它极大地解决了这些不通用的问题。

 

概念层次结构

从来宾操作系统的角度来看,对象层次结构 的定义如 图 4 所示。在顶级的是 virtio_driver,它在来宾操作系统中表示前端驱动程序。与该驱动程序匹配的设备由 virtio_device(设备在来宾操作系统中的表示)封装。这引用 virtio_config_ops结构(它定义配置 virtio 设备的操作)。virtio_device 由 virtqueue 引用(它包含一个到它服务的 virtio_device 的引用)。最后,每个 virtqueue 对象引用 virtqueue_ops 对象,后者定义处理 hypervisor 的驱动程序的底层队列操作。尽管队列操作是 virtio API 的核心,我还是先简单讨论一下新的发现,然后再详细探讨 virtqueue_ops 操作。

图 4. virtio 前端的对象层次结构

virtio 前端的对象层次结构

该流程以创建 virtio_driver 并通过 register_virtio_driver 进行注册开始。virtio_driver 结构定义上层设备驱动程序、驱动程序支持的设备 ID 的列表、一个特性表单(取决于设备类型)和一个回调函数列表。当 hypervisor 识别到与设备列表中的设备 ID 相匹配的新设备时,将调用 probe 函数(由 virtio_driver 对象提供)来传入 virtio_device 对象。将这个对象和设备的管理数据缓存起来(以独立于驱动程序的方式缓存)。可能要调用 virtio_config_ops 函数来获取或设置特定于设备的选项,例如,为 virtio_blk 设备获取磁盘的 Read/Write 状态或设置块设备的块大小,具体情况取决于启动器的类型。

注意,virtio_device 不包含到 virtqueue 的引用(但 virtqueue 确实引用了 virtio_device)。要识别与该 virtio_device 相关联的 virtqueue,您需要结合使用 virtio_config_ops 对象和 find_vq 函数。该对象返回与这个 virtio_device 实例相关联的虚拟队列。find_vq 函数还允许为 virtqueue 指定一个回调函数(查看 图 4 中的 virtqueue 结构)。

virtqueue 是一个简单的结构,它识别一个可选的回调函数(在 hypervisor 使用缓冲池时调用)、一个到 virtio_device 的引用、一个到 virtqueue 操作的引用,以及一个引用要使用的底层实现的特殊 priv 引用。虽然 callback 是可选的,但是它能够动态地启用或禁用回调。

该层次结构的核心是 virtqueue_ops,它定义在来宾操作系统和 hypervisor 之间移动命令和数据的方式。让我们首先探索添加到或从 virtqueue 移除的对象。

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 缓冲池

来宾操作系统(前端)驱动程序通过缓冲池与 hypervisor 交互。对于 I/O,来宾操作系统提供一个或多个表示请求的缓冲池。例如,您可以提供 3 个缓冲池,第一个表示 Read 请求,后面两个表示响应数据。该配置在内部被表示为一个散集列表(scatter-gather),列表中的每个条目表示一个地址和一个长度。

核心 API

通过 virtio_device 和 virtqueue(更常见)将来宾操作系统驱动程序与 hypervisor 的驱动程序链接起来。virtqueue 支持它自己的由 5 个函数组成的 API。您可以使用第一个函数 add_buf 来向 hypervisor 提供请求。如前面所述,该请求以散集列表的形式存在。对于 add_buf,来宾操作系统提供用于将请求添加到队列的 virtqueue、散集列表(地址和长度数组)、用作输出条目(目标是底层 hypervisor)的缓冲池数量,以及用作输入条目(hypervisor 将为它们储存数据并返回到来宾操作系统)的缓冲池数量。当通过 add_buf 向 hypervisor 发出请求时,来宾操作系统能够通过 kick 函数通知 hypervisor 新的请求。为了获得最佳的性能,来宾操作系统应该在通过 kick 发出通知之前将尽可能多的缓冲池装载到 virtqueue

通过 get_buf 函数触发来自 hypervisor 的响应。来宾操作系统仅需调用该函数或通过提供的 virtqueue callback 函数等待通知就可以实现轮询。当来宾操作系统知道缓冲区可用时,调用 get_buf 返回完成的缓冲区。

virtqueue API 的最后两个函数是 enable_cb 和 disable_cb。您可以使用这两个函数来启用或禁用回调进程(通过在 virtqueue 中由 virtqueue 初始化的 callback 函数)。注意,该回调函数和 hypervisor 位于独立的地址空间中,因此调用通过一个间接的 hypervisor 来触发(比如 kvm_hypercall)。

缓冲区的格式、顺序和内容仅对前端和后端驱动程序有意义。内部传输(当前实现中的连接点)仅移动缓冲区,并且不知道它们的内部表示。

总结:

virtio 是 guest 与 host 之间通信的润滑剂,提供了一套通用框架和标准接口或协议来完成两者之间的交互过程,极大地解决了各种驱动程序和不同虚拟化解决方案之间的适配问题。

virtio 抽象了一套 vring 接口来完成 guest 和 host 之间的数据收发过程,结构新颖,接口清晰。

 

posted @ 2017-05-30 15:48 super-sos 阅读(...) 评论(...) 编辑 收藏