Skip to content

VirtIO

VirtIO 是一套实现虚拟化设备的通用协议,它是 hypervisor 和操作系统驱动之间沟通的桥梁。只要遵循 VirtIO 协议进行设备模拟,就能坐享海量生态资源,实现虚拟设备自由。

架构概述

VirtIO 采用前后端分离、数据面和管控面分离的架构。

前后端分离架构

VirtIO 采用前后端分离的设计模式,将设备功能分为两个独立的部分:

  • 前端(Frontend)运行在 Guest OS 中的驱动程序,负责与应用程序交互,处理 I/O 请求,并管理 virtqueue 和内存映射。
  • 后端(Backend)运行在 Hypervisor 中的设备模拟实现,负责实际的硬件操作和设备模拟,处理来自前端的请求并返回结果。
  • 前后端通过 virtqueue 实现数据交换,采用共享内存和环形缓冲区机制,支持异步通知和批量处理。

生态优势:Linux 默认自带 VirtIO 协议族驱动,QEMU 支持大量 VirtIO 设备,使用 VirtIO 模型可以复用大量现有代码。

数据面与控制面分离

数据面(Data Plane)负责实际的数据传输和处理,通过 virtqueue 进行批量数据传输,采用零拷贝技术减少内存拷贝开销,使用内存映射提供高效的内存访问机制,并通过批量处理、异步通知和多队列并行等优化手段提升性能。

控制面(Control Plane)负责设备的管理和控制,包括状态监控、配置更新、错误处理等设备管理功能,前后端特性协商和协议版本管理等特性协商功能,以及支持协议扩展和新功能的扩展支持。

Virtqueue 和 Vring

VirtIO 通过 virtqueue 数据结构进行前后端的交互,其核心机制是内部的 vring 数据结构。一个 virtqueue 对应一个 vring,一个 vring 是一个数组,被分成三个独立部分:

  1. Descriptor Table(描述符表)

    • 数据准备区
    • 只能由前端写入交互信息
    • 后端只读
    • 包含数据缓冲区的地址和长度信息
  2. Available Ring(可用环)

    • 前端通知区
    • 前端可写,后端只读
    • 用于前端通知后端有新的请求待处理
  3. Used Ring(已用环)

    • 后端通知区
    • 后端可写,前端只读
    • 用于后端通知前端请求处理完成

环形缓冲区机制

环形缓冲区通过索引管理确保前后端数据安全。Available Ring 和 Used Ring 各自维护一个 idx 索引,这些索引像指针一样标记环形缓冲区的写入位置,分别由前端和后端独立维护,确保双方不会覆盖彼此的数据。

环形设计使得前后端可以在各自的环中独立推进。Available Ring 和 Used Ring 都采用环形结构,前端可以在 Available Ring 中填入新请求,后端在 Used Ring 中标记已完成的任务,双方互不干扰地并行工作。

通知机制采用异步方式提升效率。前端通过"kick"操作通知后端有新请求待处理,后端通过中断机制通知前端任务完成,这种异步通知避免了忙等待,显著提升了并发处理效率。

多队列支持

一个设备可以创建多个队列,每个队列彼此独立,由前后端在通信时并行使用,从而提高设备的吞吐效率。

VirtIO 设备支持多种队列类型以满足不同性能需求:单队列适用于简单设备的基本需求,一个 virtqueue 是半双工的;双队列通过分别设置 tx_queue(发送队列)和 rx_queue(接收队列)实现双向全双工数据传输,提高传输效率;多队列采用负载均衡分发策略,显著提升设备和驱动间的吞吐能力,特别适合高性能网络设备场景。

注意:对于网络设备,传输速度取决于整条链路上的瓶颈点,单一一处的吞吐量大不能保证网速的提升。

交互流程

设备初始化

  1. 设备发现
    • Guest OS 通过 PCI 或 MMIO 发现 VirtIO 设备
    • 读取设备配置空间获取设备信息
  2. 设备配置
    • 协商特性(Feature bits)
    • 设置设备参数
    • 分配 virtqueue
  3. 驱动加载
    • 加载对应的 VirtIO 驱动
    • 初始化驱动数据结构
    • 建立与设备的通信通道
  4. 设备就绪
    • 完成所有初始化步骤
    • 设备进入工作状态
    • 可以开始处理 I/O 请求

前后端通信流程

  1. 数据发送流程
    • 前端准备数据缓冲区
    • 将缓冲区信息写入 Descriptor Table
    • 更新 Available Ring 的索引
    • 发送 kick 通知后端
  2. 数据接收流程
    • 后端处理请求
    • 将结果写入 Used Ring
    • 发送中断通知前端
    • 前端处理完成通知

VirtIO 设备类型

  1. 网络设备(VirtIO-net)
    • 虚拟网卡
    • 支持多队列
    • 支持 TSO/GSO
  2. 块设备(VirtIO-blk)
    • 虚拟磁盘
    • 支持多队列
    • 支持 DISCARD/WRITE_ZEROES
  3. 控制台设备(VirtIO-console)
    • 虚拟串口
    • 支持多端口
    • 支持流控制
  4. 输入设备(VirtIO-input)
    • 虚拟键盘/鼠标
    • 支持事件上报
    • 支持多点触控
  5. GPU 设备(VirtIO-gpu)
    • 虚拟显卡
    • 支持 2D/3D 加速
    • 支持显示输出

性能优化

  • 批处理技术通过合并多个 I/O 请求来减少前后端交互次数,从而显著提高系统吞吐量。当多个小请求同时到达时,系统可以将它们打包成一个批量请求进行处理,避免了频繁的上下文切换和通信开销。
  • 零拷贝技术通过避免数据在内存中的复制操作来降低 CPU 开销。VirtIO 利用共享内存机制,让前后端直接访问同一块内存区域,数据无需在用户空间和内核空间之间来回拷贝,大大提升了数据传输效率。
  • 中断合并机制将多个中断事件合并为一个中断进行处理,有效减少了中断处理的开销。当短时间内有多个 I/O 完成事件时,系统可以等待一段时间后将它们合并发送,避免频繁的中断处理,提高系统的整体响应性。
  • 轮询模式在特定场景下替代中断机制,通过主动查询设备状态来减少中断开销。对于低延迟要求的场景,轮询模式可以避免中断处理的延迟,提供更可预测的响应时间,特别适合对实时性要求较高的应用场景。

安全考虑

  • 内存隔离:前后端内存空间隔离,防止越界访问,保护敏感数据
  • 权限控制:设备访问权限管理,资源配额限制,防止资源耗尽
  • 数据加密:敏感数据传输加密,密钥管理,安全协议支持