三、I/O 虚拟化

1. 核心目标
  • 隔离性:确保虚拟机只能访问分配给它的 I/O 设备。
  • 性能:减少虚拟化带来的 I/O 延迟和吞吐量损耗。
  • 兼容性:支持虚拟机无需修改即可使用常见设备。
2. 实现方式
(1) 全虚拟化(Full Virtualization)
  • 原理:Hypervisor 模拟真实硬件设备(如 Intel E1000 网卡),虚拟机使用标准驱动程序。
  • 缺点:频繁的 VM Exit(如每次数据包收发需陷入 Hypervisor),性能低下。
(2) 半虚拟化(Paravirtualization)
  • 原理:虚拟机使用优化后的前端驱动(如 virtio-net),与宿主机后端驱动(如 vhost-net)直接协作,通过共享内存和异步通知减少上下文切换。
  • 优点:性能显著优于全虚拟化。
  • 代表技术Virtio 协议
(3) 设备直通(PCI Passthrough)
  • 原理:将物理设备(如 GPU)直接分配给虚拟机,绕过 Hypervisor。
  • 技术依赖:IOMMU(如 Intel VT-d)确保 DMA 安全性。
  • 缺点:设备独占,无法共享。

四、Virtio 协议

1. 核心思想
  • 前后端分离
    • 前端驱动(Guest):运行在虚拟机内核中,管理虚拟设备(如 virtio-net)。
    • 后端驱动(Host):运行在宿主机用户态(如 QEMU)或内核态(如 vhost-net),处理真实 I/O。
  • 高效通信:通过共享内存队列(Virtqueues)传递数据,减少 VM Exit。
2. 关键组件
(1) Virtqueue
  • 环形缓冲区(Descriptor Ring):存储 I/O 请求的描述符(Descriptor),每个描述符指向一段物理内存(GPA)。
  • 结构
    • Descriptor Table:描述符数组,定义数据缓冲区的地址和长度。
    • Available Ring:前端驱动提交待处理的请求。
    • Used Ring:后端驱动返回已完成的请求。
  • 操作流程
    1. 前端驱动将请求写入 Available Ring。
    2. 后端驱动通过中断或轮询读取 Available Ring,处理请求。
    3. 后端驱动将完成状态写入 Used Ring,并通知前端。
(2) 设备发现与配置
  • PCI 设备模拟:Virtio 设备通过 PCI 或 MMIO 暴露给虚拟机,包含以下配置空间:
    • Device ID:标识设备类型(如 0x1041 表示网络设备)。
    • Feature Bits:协商前后端支持的功能(如多队列、校验和卸载)。
    • Virtqueue 数量与地址:配置 Virtqueue 的物理地址和大小。
3. 性能优化
  • Vhost:将后端驱动移到内核(如 vhost-net)或硬件(如 vDPA),减少用户态-内核态切换。
  • 批处理:合并多个 I/O 请求,减少中断次数。

五、Virtio 驱动开发示例

1. 环境准备
  • 目标设备:假设开发一个简单的 Virtio 块设备驱动。
  • 内核版本:Linux 5.10+(已内置 Virtio 框架支持)。
2. 驱动代码框架
#include <linux/virtio.h>
#include <linux/virtio_config.h>
#include <linux/virtio_blk.h>

/* 定义 Virtio 设备 ID */
#define VIRTIO_ID_BLOCK 2

/* 驱动私有数据结构 */
struct virtio_blk_device {
    struct virtio_device *vdev;
    struct virtqueue *vq;
    struct request_queue *rq;
};

/* 处理完成的中断 */
static void virtio_blk_done(struct virtqueue *vq) {
    struct virtio_blk_device *vbdev = vq->vdev->priv;
    struct scatterlist sg;
    unsigned int len;

    while (virtqueue_get_buf(vbdev->vq, &len)) {
        /* 处理已完成的 I/O 请求(如唤醒等待的进程) */
        printk(KERN_INFO "Virtio-blk: I/O completed, len=%u\n", len);
    }
}

/* 提交 I/O 请求到 Virtqueue */
static int virtio_blk_submit_request(struct virtio_blk_device *vbdev,
                                     struct scatterlist *sg, unsigned int num) {
    struct virtqueue *vq = vbdev->vq;
    int ret;

    ret = virtqueue_add_sgs(vq, sg, num, 0, vbdev, GFP_KERNEL);
    if (ret < 0) {
        printk(KERN_ERR "Virtio-blk: Failed to add request to virtqueue\n");
        return ret;
    }

    virtqueue_kick(vq); /* 通知后端驱动处理请求 */
    return 0;
}

/* 设备探测函数 */
static int virtio_blk_probe(struct virtio_device *vdev) {
    struct virtio_blk_device *vbdev;
    int ret;

    /* 分配设备私有数据 */
    vbdev = kzalloc(sizeof(*vbdev), GFP_KERNEL);
    if (!vbdev)
        return -ENOMEM;

    vdev->priv = vbdev;
    vbdev->vdev = vdev;

    /* 初始化 Virtqueue */
    vbdev->vq = virtio_find_single_vq(vdev, virtio_blk_done, "requests");
    if (IS_ERR(vbdev->vq)) {
        ret = PTR_ERR(vbdev->vq);
        goto free_vbdev;
    }

    /* 配置设备(如读取磁盘容量) */
    virtio_cread(vdev, struct virtio_blk_config, capacity, &vbdev->capacity);

    printk(KERN_INFO "Virtio-blk: Device probed, capacity=%llu sectors\n",
           vbdev->capacity);
    return 0;

free_vbdev:
    kfree(vbdev);
    return ret;
}

/* 设备移除函数 */
static void virtio_blk_remove(struct virtio_device *vdev) {
    struct virtio_blk_device *vbdev = vdev->priv;

    virtio_reset_device(vdev); /* 重置设备 */
    del_virtqueue(vbdev->vq);
    kfree(vbdev);
}

/* 定义 Virtio 驱动结构 */
static struct virtio_device_id id_table[] = {
    { VIRTIO_ID_BLOCK, VIRTIO_DEV_ANY_ID },
    { 0 },
};

static struct virtio_driver virtio_blk_driver = {
    .driver.name = KBUILD_MODNAME,
    .id_table = id_table,
    .probe = virtio_blk_probe,
    .remove = virtio_blk_remove,
};

module_virtio_driver(virtio_blk_driver);
MODULE_DEVICE_TABLE(virtio, id_table);
MODULE_LICENSE("GPL");
3. 关键步骤解析
  1. 设备探测virtio_blk_probe 在检测到 Virtio 块设备时调用,初始化 Virtqueue。
  2. 请求提交virtio_blk_submit_request 将 I/O 请求的散列表(scatterlist)添加到 Virtqueue,并通过 virtqueue_kick 通知后端。
  3. 中断处理virtio_blk_done 在收到后端完成通知时,从 Used Ring 中读取已完成的请求。

六、总结

技术核心机制性能优化
I/O 虚拟化全虚拟化、半虚拟化、设备直通Virtio 协议减少上下文切换
Virtio前后端驱动 + VirtqueuesVhost 将后端移入内核或硬件
应用场景
  • 云计算:KVM/QEMU 默认使用 Virtio 驱动提供网络和磁盘访问。
  • 容器virtio-fs 用于虚拟机与宿主机之间的文件共享。
  • DPU:智能网卡通过 vDPA 提供硬件加速的 Virtio 后端。
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐