Linux Kernel Vhost 架构
Vhost 回顾
Linux中的vhost驱动程序提供了内核virtio设备仿真。 通常,QEMU用户空间进程模拟guest的I / O访问。 Vhost将virtio仿真代码放到内核中,使QEMU用户空间脱离。 这允许设备仿真代码直接调用到内核子系统,而不是从用户空间执行系统调用。
vhost-net驱动程序模拟主机内核中的virtio-net网卡。 Vhost-net是最早的vhost设备,也是主流Linux中唯一可用的设备。 也已经开发了实验vhost-blk和vhost-scsi装置。
在Linux 3.0中,vhost代码存放在drivers / vhost /中。 所有设备使用的通用代码在drivers / vhost / vhost.c中。 这包括virtio vring访问功能,所有virtio设备需要为了与客户进行通信。 vhost-net代码存放在drivers / vhost / net.c中。
Vhost 驱动模型
vhost-net驱动程序在主机上创建一个/ dev / vhost-net字符设备。 此字符设备作为配置vhost-net实例的接口。
当使用-netdev tap启动QEMU时,vhost = on将打开/ dev / vhost-net并使用几个ioctl调用初始化vhost-net实例。 这些必须将QEMU进程与vhost-net实例关联,准备virtio功能协商,并将guest虚拟机物理内存映射传递到vhost-net驱动程序。
在初始化期间,vhost驱动程序创建一个名为vhost- $ pid的内核线程,其中$ pid是QEMU进程pid。 这个线程被称为“vhost工作线程”。 工作线程的任务是处理I / O事件并执行设备仿真。
内核Virtio仿真
Vhost不会模拟一个完整的virtio PCI适配器。 相反,它只将自己限制为virtqueue操作。 QEMU仍然用于执行virtio特性协商和实时迁移。 这意味着vhost驱动程序不是完整的virtio设备实现,它依赖于用户空间来处理控制平面,而数据平面在内核中完成。
vhost工作线程等待virtqueue kick,然后处理放在virtqueue上的缓冲区。 在vhost-net这意味着从tx virtque的数据包并且通过tap文件描述符发送它们。
文件描述符轮询也由vhost工作线程完成。 在vhost-net中,当数据包进入tap文件描述符时,工作线程被唤醒,并将其放置到rx virtqueue中,以便客户端可以接收它们。
Vhost作为用户空间接口
vhost架构的一个令人惊讶的方面是它不以任何方式绑定到KVM。 Vhost是一个用户空间接口,不依赖于KVM内核模块。这意味着其他用户空间代码(如libpcap)在理论上可以使用vhost设备,如果他们发现它们方便的高性能I / O接口。
当客户端kick主机,因为它已经将缓冲区放在一个virtqueue,需要一种方式来通知vhost工作线程有工作要做。由于vhost不依赖于KVM内核模块,它们不能直接通信。相反,vhost实例使用vhost工作线程监视活动的eventfd文件描述符进行设置。 KVM内核模块具有称为ioeventfd的功能,用于获取eventfd并将其挂接到特定的guest虚拟机I / O出口。 QEMU用户空间注册一个ioeventfd用于VIRTIO_PCI_QUEUE_NOTIFY硬件寄存器访问,它能kick virtqueue。这是当guest虚拟机kick virtqueue,vhost工作线程被KVM内核模块通知的方式。
在从vhost工作线程返回到中断客户端时,使用了类似的方法。 Vhost需要一个“call”文件描述符,写这个文件描述符是为了去kick guest。 KVM内核模块有一个称为irqfd的功能,它允许eventfd触发客户机中断。 QEMU用户空间注册一个irqfd为virtio PCI设备中断并将其交给vhost实例。这是vhost工作线程如何中断客户端。
因此,vhost实例只知道guest虚拟机内存映射,kick eventfd和call eventfd。
More detail
drivers / vhost / vhost.c - 常见的vhost驱动程序代码
drivers / vhost / net.c - vhost-net驱动
virt / kvm / eventfd.c - ioeventfd和irqfd
QEMU用户空间代码显示如何初始化vhost实例:
hw / vhost.c - 常见的vhost初始化代码
hw / vhost_net.c - vhost-net初始化