3. vhost-net 与 vhost-scsi:两种后端类型对比
在 DPDK vhost 用户态协议栈中,后端类型的选择直接影响虚拟化 I/O 路径的性能与功能边界。当前主流后端包括 vhost-net(网络设备后端)与 vhost-scsi(存储设备后端),分别对应 virtio-net 与 virtio-blk 两种前端设备模拟。本章从架构差异、数据路径、零拷贝实现方式及应用场景三个维度进行系统性对比。
3.1 架构对比:vhost-net vs vhost-scsi
下表从协议栈层次、队列模型、数据平面与零拷贝支持四个维度进行对比:
| 对比维度 | vhost-net | vhost-scsi |
|---|---|---|
| 协议栈层次 | 网络层(L2/L3),处理以太网帧 | SCSI 目标层,处理 SCSI 命令与块数据 |
| virtio 设备类型 | virtio-net | virtio-blk |
| 队列模型 | 多队列(virtqueue per vCPU),支持 RSS | 单队列或多队列,通常按磁盘或 LUN 分配 |
| 数据单元 | 网络包(mbuf),可变长度 | 块请求(bio/SCSI CDB),固定扇区大小 |
| 零拷贝路径 | mbuf 直接映射到 Guest GPA | SCSI 数据缓冲区直接映射到 Guest GPA |
| 后端卸载 | TSO/GSO、checksum offload、多队列分流 | SCSI 持久化预留、任务管理、DIF/DIX 校验 |
3.2 virtio-net 与 virtio-blk 设备模拟
两种后端分别对应不同的 virtio 设备模拟模型,其 virtqueue 布局与描述符使用方式存在显著差异。
3.2.1 virtio-net 设备模拟
virtio-net 使用 三个 virtqueue(接收、发送、控制),每个队列由描述符表、可用环与已用环组成。数据路径以网络包为单位,描述符链支持 scatter-gather,允许将 mbuf 的多个数据段映射到 Guest 物理地址空间。
// virtio-net 描述符链示例(发送路径)
struct vring_desc desc[2];
desc[0].addr = gpa_of_header; // 网络包头(GPA)
desc[0].len = sizeof(struct virtio_net_hdr);
desc[0].flags = VRING_DESC_F_NEXT;
desc[0].next = 1;
desc[1].addr = gpa_of_payload; // 网络负载(GPA)
desc[1].len = pkt_len;
desc[1].flags = 0; // 链结束
vhost-net 后端通过 DPDK 的 rte_vhost_enqueue_burst 将 mbuf 直接写入 Guest 的接收队列,实现零拷贝。关键优化点在于 mbuf 物理地址与 Guest GPA 的映射表,该表在 vhost 设备启动时通过 VHOST_SET_MEM_TABLE 建立。
3.2.2 virtio-blk 设备模拟
virtio-blk 使用 一个请求队列(request virtqueue),每个请求对应一个 SCSI 命令描述块(CDB)。描述符链包含三个部分:请求头(包含 SCSI 命令)、数据缓冲区(读/写)、状态字节。
// virtio-blk 描述符链示例(读请求)
struct vring_desc desc[3];
desc[0].addr = gpa_of_req_header; // struct virtio_blk_outhdr
desc[0].len = sizeof(struct virtio_blk_outhdr);
desc[0].flags = VRING_DESC_F_NEXT;
desc[0].next = 1;
desc[1].addr = gpa_of_data_buf; // 数据缓冲区(读入)
desc[1].len = block_size * num_sectors;
desc[1].flags = VRING_DESC_F_WRITE | VRING_DESC_F_NEXT;
desc[1].next = 2;
desc[2].addr = gpa_of_status; // 状态字节
desc[2].len = 1;
desc[2].flags = VRING_DESC_F_WRITE;
vhost-scsi 后端需要解析 SCSI CDB,执行实际的块设备 I/O(如通过 SPDK 或内核块层),然后将数据直接写入 Guest 的数据缓冲区。零拷贝体现在 数据缓冲区 GPA 直接映射到后端存储的 DMA 地址,避免 Guest 与 Host 之间的数据拷贝。
3.3 数据路径与零拷贝实现对比
下图展示了两种后端在共享内存通信中的数据路径差异:
从图中可以看出,两种后端在共享内存 virtqueue 层共享相同的零拷贝机制,但下游处理路径完全不同:vhost-net 直接操作 mbuf 并转发到物理网卡,而 vhost-scsi 需要经过 SCSI 命令解析层才能到达块设备。
3.4 应用场景分析
根据实际生产环境经验,以下场景矩阵可帮助决策:
| 应用场景 | 推荐后端 | 关键理由 |
|---|---|---|
| NFV / vCPE / 虚拟路由器 | vhost-net | 高吞吐包处理、多队列 RSS、checksum 卸载 |
| 云网关 / 负载均衡器 | vhost-net | 低延迟转发、连接跟踪卸载 |
| 虚拟磁盘 / 云硬盘 | vhost-scsi | SCSI 持久化预留、快照、精简配置 |
| 数据库虚拟机 | vhost-scsi | 块级 I/O 语义、DIF/DIX 数据完整性 |
| 容器存储(如 OpenEBS) | vhost-scsi | 支持 iSCSI 目标、多路径、故障切换 |
| 轻量级虚拟化(Firecracker) | vhost-net | 简化设备模型、减少内存开销 |
3.5 选型决策流程
以下流程帮助开发者在实际项目中快速选择后端类型:
- 判断 I/O 类型:如果是网络包(以太网帧),走 vhost-net;如果是块请求(扇区读写),走 vhost-scsi。
- 评估语义需求:是否需要 SCSI 持久化预留、任务管理、DIF/DIX?需要则选 vhost-scsi。
- 检查卸载能力:物理 NIC 是否支持 TSO/GSO/checksum offload?支持则 vhost-net 能获得额外性能增益。
- 考虑生态集成:如果后端存储已经基于 SPDK 或 NVMe-oF,vhost-scsi 能直接复用;如果网络已经使用 DPDK 轮询模式,vhost-net 集成更自然。
- 性能兜底:在不确定时,优先使用 vhost-net 进行原型验证,因为其代码路径更短、调试工具更丰富。