3. vhost-net 与 vhost-scsi:两种后端类型对比

在 DPDK vhost 用户态协议栈中,后端类型的选择直接影响虚拟化 I/O 路径的性能与功能边界。当前主流后端包括 vhost-net(网络设备后端)与 vhost-scsi(存储设备后端),分别对应 virtio-net 与 virtio-blk 两种前端设备模拟。本章从架构差异、数据路径、零拷贝实现方式及应用场景三个维度进行系统性对比。

核心观点:vhost-net 面向网络包处理,强调高吞吐与低延迟;vhost-scsi 面向块存储,强调数据完整性、SCSI 命令集支持与持久化语义。两者在共享内存通信机制上同源,但在 virtio 队列组织、I/O 语义与后端卸载能力上存在本质差异。

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 校验
选型提示:如果虚拟化场景以网络 I/O 为主(如 NFV、云网关),优先选择 vhost-net;如果场景涉及虚拟磁盘、数据库或分布式存储,vhost-scsi 能提供更完整的 SCSI 语义支持。

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 之间的数据拷贝。

关键差异:virtio-blk 的请求头/状态分离设计使得 vhost-scsi 必须处理更复杂的命令生命周期(如 SCSI 任务管理、重置),而 virtio-net 仅需处理简单的包收发。这导致 vhost-scsi 的代码路径更长,延迟通常比 vhost-net 高 20%-40%。

3.3 数据路径与零拷贝实现对比

下图展示了两种后端在共享内存通信中的数据路径差异:

vhost-net 与 vhost-scsi 数据路径对比 vhost-net 数据路径 Guest virtio-net 前端 共享内存 virtqueue vhost-net 后端 DPDK mbuf 池 物理 NIC / TAP vhost-scsi 数据路径 Guest virtio-blk 前端 共享内存 virtqueue vhost-scsi 后端 SCSI 命令解析 SPDK / 块设备 零拷贝 共享内存 GPA 映射

从图中可以看出,两种后端在共享内存 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 简化设备模型、减少内存开销
性能参考:在同等硬件条件下(Intel Xeon 6258R, 256GB, MLX5 CX-6),vhost-net 单队列可达 10Mpps 以上,而 vhost-scsi 单队列 4K 随机读写约 500K IOPS。两者在共享内存通信上的延迟差异约为 2-5μs,主要来自 SCSI 命令解析与块设备调度。

3.5 选型决策流程

以下流程帮助开发者在实际项目中快速选择后端类型:

  1. 判断 I/O 类型:如果是网络包(以太网帧),走 vhost-net;如果是块请求(扇区读写),走 vhost-scsi。
  2. 评估语义需求:是否需要 SCSI 持久化预留、任务管理、DIF/DIX?需要则选 vhost-scsi。
  3. 检查卸载能力:物理 NIC 是否支持 TSO/GSO/checksum offload?支持则 vhost-net 能获得额外性能增益。
  4. 考虑生态集成:如果后端存储已经基于 SPDK 或 NVMe-oF,vhost-scsi 能直接复用;如果网络已经使用 DPDK 轮询模式,vhost-net 集成更自然。
  5. 性能兜底:在不确定时,优先使用 vhost-net 进行原型验证,因为其代码路径更短、调试工具更丰富。
常见误区:不要试图在 vhost-net 上模拟块设备语义(如通过 NBD 或 iSCSI 封装),这会引入额外的协议转换开销,性能通常比原生 vhost-scsi 低 30%-50%。同样,也不要在 vhost-scsi 上处理网络包,SCSI 命令解析会严重拖慢网络吞吐。