SPDK vhost target

主流的I/O设备虚拟化的方案

  • 纯软件模拟:完全利用软件模拟出一些设备给虚拟机使用,主要的工作可以在Simics、Bochs、纯QEMU解决方案中看到。
  • 半虚拟(Para-Virtualization):主要是一种frontend-backend的模型,在虚拟机中的Guest OS中使用frontend的驱动,Hypervisor中暴露出backend接口。这种解决方案需要修改Guest OS,或者提供半虚拟化的前端驱动。
  • 硬件虚拟化:主流的方案有SR-IOV、VT-D等,可以把整个设备直接分配给一个虚拟机,或者如果设备支持SR-IOV,就可以把设备的VF(Virtual Function)分配给虚拟机。

1.virtio

在这里插入图片描述
virtio在QEMU中的总体实现可以分成3层:

  • 前端是设备层,位于Guest操作系统内部;
  • 中间是虚拟队列传输层,Guest和QEMU都包含该层,数据传输及命令下发完成都是通过该层实现的;
  • 第3层是virtio后端设备,用于具体落实来自Guest端发送的请求。

2.vhost加速

virtio后端设备用于具体响应Guest的命令请求。
例如,对virtio-scsi设备来讲,该virtio后端负责SCSI命令的响应,QEMU负责模拟该PCI设备,把该SCSI命令响应的模块在QEMU进程之外实现的方案称为vhost。这里同样分为两种实现方式,在Linux内核中实现的叫作vhost-kernel,而在用户态实现的叫作vhost-user。

1)QEMU virtio-scsi

Qemu 架构

Qemu 是纯软件实现的虚拟化模拟器,几乎可以模拟任何硬件设备,我们最熟悉的就是能够模拟一台能够独立运行操作系统的虚拟机,虚拟机认为自己和硬件打交道,但其实是和 Qemu 模拟出来的硬件打交道,Qemu 将这些指令转译给真正的硬件

正因为 Qemu 是纯软件实现的,所有的指令都要经 Qemu 过一手,性能非常低,所以,在生产环境中,大多数的做法都是配合 KVM 来完成虚拟化工作,因为 KVM 是硬件辅助的虚拟化技术,主要负责 比较繁琐的 CPU 和内存虚拟化,而 Qemu 则负责 I/O 虚拟化,两者合作各自发挥自身的优势,相得益彰。

在这里插入图片描述
Guest和QEMU之间通过virtqueue进行数据交换,当Guest提交新的SCSI命令到virtqueue时,根据virtio PCI设备定义,Guest会把该队列的ID写入PCI配置空间中,通知PCI设备有新的SCSI请求已经就绪;

之后QEMU会得到通知,基于Guest填写的队列ID到指定的virtqueue获取最新的SCSI请求;

最后发送到该模拟PCI设备的后端,这里后端可以是宿主机系统上的一个文件或块设备分区。

当SCSI命令在后端的文件或块设备执行完成并返回给virtio-scsi backend模块后QEMU会向该P
CI设备发送中断通知,从而Guest基于该中断完成整个SCSI命令流程。

这个方案存在如下两个严重影响性能的因素。

  • 当Guest提交新的SCSI请求到virtqueue队列时,需要告知QEMU哪个队列含有最新的SCSI命令
  • 在实际处理具体的SCSI读/写命令时(在hostOS 中),存在用户态到内核态的数据副本

数据副本影响性能,我们比较好理解,因为存储设备中的数据块相对于网络来说都是大包,但是为什么说Guest提交新的SCSI请求时也严重影响性能呢?
根据virtio协议,Guest提交请求到virtqueue时需要把该队列的ID写入PCI配置空间,所以每个新的命令请求都会写入一次PCI的配置空间。在X86虚拟化环境下,Guest中对PCI空间的读/写是特权指令,需要更高级别的权限,因此会触发VMM的Trap,从而导致VM_EXIT事件,CPU需要切换上下文到QEMU进程去处理该事件,在虚拟化环境下,VM_EXIT对性能有重大影响,而且对系统能够支持VM的密度等方面也有影响,所以下面介绍的方案都是基于对这两点的优化来进行的。

2)Kernel vhost-scsi

这个方案是QEMU virtio-scsi的后续演进,基于LIO在内核空间实现为虚拟机服务的SCSI设备。实际上vhost-kernel方案并没有完全模拟一个PCI设备,QEMU仍然负责对该PCI设备的模拟,只是把来自virtqueue的数据处理逻辑拿到内核空间了

为了实现在内核空间处理virtqueue上的数据QEMU需要告知内核vhost-scsi模块关于virtqueue的内存信息及Guest的内存映射,这样其实省去了Guest到QEMU用户态空间,再到宿主机内核空间多次数据复制
但是由于内核的vhost-scsi模块并不知道什么时候在哪个队列存在新的请求,所以当Guest生成新的请求到virtqueue队列,再更新完PCI配置空间后,由QEMU负责通知vhost-kernel启动内核线程去处理新的队列请求

这里我们可以看到Kernel vhost-scsi方案相比QEMU virtio-scsi方案在具体的SCSI命令处理时减少了数据的内存复制过程,从而提高了性能。

3)SPDK vhost-user-scsi

在这里插入图片描述

虽然Kernel vhost-scsi方案在数据处理时已经没有数据的复制过程,但是当Guest有新的请求时,仍然需要QEMU通过系统调用通知内核工作线程
这里存在两方面的开销:Guest内核需要更新PCI配置空间,QEMU需要捕
获Guest的VMM自陷,然后通知Kernel vhost-scsi工作线程

SPDK vhost-user-scsi方案消除了这两方面的影响,后端的I/O处理线程在轮询所有的virtqueue,因此不需要Guest在添加新的请求到virtqueue后更新PCI的配置空间。SPDK vhost-user-scsi的后端I/O处理模块轮询机制加上零拷贝技术基本解决了前面我们提到的阻碍QEMU virtio-scsi性能提升的两个关键点。

3.SPDK vhost-scsi加速

核心的实现就是队列在Guest和SPDK vhost target之间是共享的,那么接下来我们就看一下vhost是如何实现这个内存共享的,以及Guest物理地址到主机的虚拟地址是如何转换的。

在vhost-kernel方案中,QEMU使用ioctl系统调用和内核的vhost-scsi模块建立联系,从而把QEMU中模拟的SCSI设备部分传递到了内核态,即内核态对该SCSI设备不是完全模拟的,仅仅负责对virtqueue进行处理,因此这个ioctl的消息主要负责3部分的内容传递:Guest内存映射;Guest Kick Event、vhost-kernel驱动用来接收Guest的消息,当接收到该消息后即可启动工作线程;IRQ Event用于通知Guest的I/O完成情况。同样地,当把内核对virtqueue处理的这个模块迁移到用户态时,以上3个主要部分的内容传递就变成了UNIX Domain socket文件了,消息格式及内容和Kernel的ioctl相比有许多相似和重复的地方。

4.SPDK vhost-NVMe加速

virtio和NVMe协议在设计时都采用了相同的环型结构,virtio使用avaiable和used ring作
为请求和响应,而NVMe使用提交队列和完成队列作为请求和响应。

QEMU NVMe存在相同的性能瓶颈,Guest都要写NVMe PCI配置空间寄存器,因此会存在VMM
Trap自陷问题,由于后端主机使用文件来承载I/O命令,同样存在用户态到内核态数据副本的问题

NVMe驱动会首先更新shadow doorbell,基于从后端模拟设备获取到的反馈,来决定是否更新PCI的doorbell,也就是说Guest是否更新PCI doorbell是由模拟设备后端来决定的

内存副本,于物理的NVMe设备需要使用控制器内部的DMA引擎搬移数据,要求所有的I/O命令对应的数据区域都是物理内存连续的

推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我也要当昏君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值