Virtio块设备驱动开发
·
三、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。
- 前端驱动(Guest):运行在虚拟机内核中,管理虚拟设备(如
- 高效通信:通过共享内存队列(Virtqueues)传递数据,减少 VM Exit。
2. 关键组件
(1) Virtqueue
- 环形缓冲区(Descriptor Ring):存储 I/O 请求的描述符(Descriptor),每个描述符指向一段物理内存(GPA)。
- 结构:
- Descriptor Table:描述符数组,定义数据缓冲区的地址和长度。
- Available Ring:前端驱动提交待处理的请求。
- Used Ring:后端驱动返回已完成的请求。
- 操作流程:
- 前端驱动将请求写入 Available Ring。
- 后端驱动通过中断或轮询读取 Available Ring,处理请求。
- 后端驱动将完成状态写入 Used Ring,并通知前端。
(2) 设备发现与配置
- PCI 设备模拟:Virtio 设备通过 PCI 或 MMIO 暴露给虚拟机,包含以下配置空间:
- Device ID:标识设备类型(如
0x1041表示网络设备)。 - Feature Bits:协商前后端支持的功能(如多队列、校验和卸载)。
- Virtqueue 数量与地址:配置 Virtqueue 的物理地址和大小。
- Device ID:标识设备类型(如
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. 关键步骤解析
- 设备探测:
virtio_blk_probe在检测到 Virtio 块设备时调用,初始化 Virtqueue。 - 请求提交:
virtio_blk_submit_request将 I/O 请求的散列表(scatterlist)添加到 Virtqueue,并通过virtqueue_kick通知后端。 - 中断处理:
virtio_blk_done在收到后端完成通知时,从 Used Ring 中读取已完成的请求。
六、总结
| 技术 | 核心机制 | 性能优化 |
|---|---|---|
| I/O 虚拟化 | 全虚拟化、半虚拟化、设备直通 | Virtio 协议减少上下文切换 |
| Virtio | 前后端驱动 + Virtqueues | Vhost 将后端移入内核或硬件 |
应用场景
- 云计算:KVM/QEMU 默认使用 Virtio 驱动提供网络和磁盘访问。
- 容器:
virtio-fs用于虚拟机与宿主机之间的文件共享。 - DPU:智能网卡通过
vDPA提供硬件加速的 Virtio 后端。
更多推荐
所有评论(0)