virtio 与vhost_net介绍

本文探讨了virtio的基本构建模块,重点讲解了vhost-net/virtio-net架构,以及在KVM中两者如何高效通信。对比了全虚拟化和virtio设备,介绍了vhost-net作为内核级后端的优势。同时涵盖了vhost-user在用户空间通信中的应用,以及QEMU配置实例。

1. virtio基本构建模块

 

virtio是一种I/O半虚拟化解决方案,是一套通用I/O设备虚拟化的程序,是对半虚拟化Hypervisior中的一组通用I/O设备的抽象。是标准化的的开放接口,以使得VM能够访问简化的设备,如块设备和网络设备等。

一个guest VM或者guest指的是在物理计算机上安装、执行和托管的VM。托管guest VM的计算机称之为host,它为guest VM提供资源。Guest VM通过hypervisor在host OS之上运行独立的OS。例如,host将为guest提供虚拟的NIC,guest感觉好像是在使用真实的NIC,而实际上使用的是虚拟的NIC

以下模块用于创建virtio环境:

  • KVM:kernel based virtual machine,允许Linux充当hypervisor,以便host可以运行多个隔离的虚拟环境(guest)。KVM基本上为Linux提供了hypervisor功能。这意味这hypervisor组件(例如内存管理、调度程序、网络堆栈等)作为Linux内核的一部分提供。VM是由标准Linux 调度程序通过专用虚拟硬件(例如网络适配器)调度的常规Linux进程。
  • QEMU:一个托管的虚拟机监视器,可以通过仿真为guest提供一组不同的硬件和设备模型。QEMU可以和KVM一起使用,以利用硬件扩展使得guest达到接近host的速度。Guest通过qemu命令行执行。CLI提供了为QEMU指定所有必须的配置选项和能力。
  • Libvirt:一个将xml格式的配置转化为qemu CLI调用的接口。它还提供了一个admin daemon来配置子进程(如qumu),因此,qemu将不需要root权限。例如,当Openstack Nova要启动VM时,它使用libvirt通过为每个VM启动qemu进程。

下图显示了这三个模块如何结合在一起

  1. Host和guest都包含kernel space 和 user space。
  2. 从图中可以看出,KVM在host kernel space中运行,而libvirt在host user space中运行。
  3. Guest VM在qumu中运行,该进程只是在host user space上运行的进程,并与libvirt和KVM进行通信。
  4. 将为每个guest VM创建一个qemu进程,因此,如果您创建N个guest VM,则将有N个qemu进程,而libvirt将与他们进行通信。

 2. vhost-net/virtio-net架构

virtio分为前端和后端,一个backend组件和一个frontend组件。backend组件是virtio接口的host端。frontend组件是virtio的guest端。
在vhost-net/virtio-net体系结构中,组件如下:vhost-net是在host kernel space中运行的backend,virtio-net是在guest kernel space中运行的frontend。

  1. 由于vhost-net和virtio-net都运行在host和guest的kernel space,因此,我们也将他们称之为驱动程序,因此,有些文章中写成vhost-net驱动程序。
  2. 我们在backend和frontend之间有一个单独的control plane和data plane。如前所述,control plane只是为vhost-net内核模块和qemu进程实现了virtio spec进行通信,然后将其传递给guest,并最终传递给virtio-net。Vhost-net使用vhost protocol建立框架,然后将其用于data plane,以使用共享内存区域在host和guest kernel space之间直接转发数据包。

对于每个guest,我们可以关联多个虚拟CPU(vCPU),并给每个vCPU创建RX/TX队列,因此,带有3个vCPU的详细示例如下所示(为了简单,删除control plane)

 3. KVM中virtio-net与vhost-net通信

  • vhost模块需要提前加载,注册一个misc的设备,供虚拟机启动时候使用。
  • 虚拟机创建的时候,会初始化一个tap设备,然后启动一个vhost _$(qemu-kvm_pid)的线程,配置vring等承载数据的高度。
  • guest和host进行网络数据IO的时候,只负责数据IO的中断,中断消息等由kvm模块负责。
  • guest发包的时候,virtio模块负责发送数据包加入链接,然后通知kvm模块,kvm模块通过ioeventfd通知vhost模块,此时可以将有包的堆栈挂在work_list上,然后激活线程vhost进行收包操作,收到包之后传递给tap设备,再往内核协议栈中上
  • guest收包的时候,首先是vhost的往tap设备发包,然后将包加入到其中一个,然后将挂在work_list,激活线程vhost,vhost进行收包的操作,然后传递到其中,然后vhost通过irqfd通知kvm模块,kvm模块给guest发送中断,guest会通过中断,到NAPI,执行轮询,接收数据包,然后上传到协议栈。

å¨è¿éæå¥å¾çæè¿°

4.vhost-net 与 virtio-net 的比较

在 QEMU/KVM 中,客户机可以使用的设备大致可分为三类:

1. 模拟设备:完全由 QEMU 纯软件模拟的设备。

2. Virtio 设备:实现 VIRTIO API 的半虚拟化设备。

3. PCI 设备直接分配 (PCI device assignment) 。

4.1 全虚拟化 I/O 设备

KVM 在 IO 虚拟化方面,传统或者默认的方式是使用 QEMU 纯软件的方式来模拟 I/O 设备,包括键盘、鼠标、显示器,硬盘 和 网卡 等。模拟设备可能会使用物理的设备,或者使用纯软件来模拟。模拟设备只存在于软件中。 

过程:

  1. 客户机的设备驱动程序发起 I/O 请求操作请求
  2. KVM 模块中的 I/O 操作捕获代码拦截这次 I/O 请求
  3. 经过处理后将本次 I/O 请求的信息放到 I/O 共享页 (sharing page),并通知用户空间的 QEMU 程序。
  4. QEMU 程序获得 I/O 操作的具体信息之后,交由硬件模拟代码来模拟出本次 I/O 操作。
  5. 完成之后,QEMU 将结果放回 I/O 共享页,并通知 KMV 模块中的 I/O 操作捕获代码。
  6. KVM 模块的捕获代码读取 I/O 共享页中的操作结果,并把结果放回客户机。

这种方式的优点是可以模拟出各种各样的硬件设备;其缺点是每次 I/O 操作的路径比较长,需要多次上下文切换,也需要多次数据复制,所以性能较差。 

4.2 virtio架构

目前 KVM 采用的的是 virtio 这个 Linux 上的设备驱动标准框架,它提供了一种 Host 与 Guest 交互的 IO 框架。

 KVM/QEMU 的 vitio 实现采用在 Guest OS 内核中安装前端驱动 (Front-end driver)和在 QEMU 中实现后端驱动(Back-end)的方式。前后端驱动通过 vring 直接通信,这就绕过了经过 KVM 内核模块的过程,达到提高 I/O 性能的目的。

纯软件模拟的设备和 Virtio 设备的区别:virtio 省去了纯模拟模式下的异常捕获环节,Guest OS 可以和 QEMU 的 I/O 模块直接通信。

使用 Virtio 的完整虚机 I/O流程:

Host 数据发到 Guest:

1. KVM 通过中断的方式通知 QEMU 去获取数据,放到 virtio queue 中

2. KVM 再通知 Guest 去 virtio queue 中取数据。

vhost-net 的要求:

  • qemu-kvm-0.13.0 或者以上
  • 主机内核中设置 CONFIG_VHOST_NET=y 和在虚机操作系统内核中设置 CONFIG_PCI_MSI=y (Red Hat Enterprise Linux 6.1 开始支持该特性)
  • 在客户机内使用 virtion-net 前段驱动
  • 在主机内使用网桥模式,并且启动 vhost_net

qemu-kvm 命令的 -net tap 有几个选项和 vhost-net 相关的: -net tap,[,vnet_hdr=on|off][,vhost=on|off][,vhostfd=h][,vhostforce=on|off]

  • vnet_hdr =on|off:设置是否打开TAP设备的“IFF_VNET_HDR”标识。“vnet_hdr=off”表示关闭这个标识;“vnet_hdr=on”则强制开启这个标识,如果没有这个标识的支持,则会触发错误。IFF_VNET_HDR是tun/tap的一个标识,打开它则允许发送或接受大数据包时仅仅做部分的校验和检查。打开这个标识,可以提高virtio_net驱动的吞吐量。
  • vhost=on|off:设置是否开启vhost-net这个内核空间的后端处理驱动,它只对使用MIS-X中断方式的virtio客户机有效。
  • vhostforce=on|off:设置是否强制使用 vhost 作为非MSI-X中断方式的Virtio客户机的后端处理程序。
  • vhostfs=h:设置为去连接一个已经打开的vhost网络设备。

4.3 vhost-net

  前面提到 virtio 在宿主机中的后端处理程序(backend)一般是由用户空间的QEMU提供的,然而如果对于网络 I/O 请求的后端处理能够在在内核空间来完成,则效率会更高,会提高网络吞吐量和减少网络延迟。在比较新的内核中有一个叫做 “vhost-net” 的驱动模块,它是作为一个内核级别的后端处理程序,将virtio-net的后端处理任务放到内核空间中执行,减少内核空间到用户空间的切换,从而提高效率。

 4.4 vhost_user

4.4.1 什么是 vhost-user

在 vhost_net 的方案中,由于 vhost_net 实现在内核中,guest 与 vhost_net 的通信,相较于原生的 virtio 方式性能上有了一定程度的提升,从 guest 到 kvm.ko 的交互只有一次用户态的切换以及数据拷贝。这个方案对于不同 host 之间的通信,或者 guest 到 host nic 之间的通信是比较好的,但是对于某些用户态进程间的通信,比如数据面的通信方案,openvswitch 和与之类似的 SDN 的解决方案,guest 需要和 host 用户态的 vswitch 进行数据交换,如果采用 vhost_net 的方案,guest 和 host 之间又存在多次的上下文切换和数据拷贝,为了避免这种情况,业界就想出将 vhost_net从内核态移到用户态。这就是 vhost-user 的实现。

4.4.2 vhost-user 的实现

vhost-user 和 vhost_net 的实现原理是一样,都是采用 vring 完成共享内存,eventfd 机制完成事件通知。不同在于 vhost_net 实现在内核中,而 vhost-user 实现在用户空间中,用于用户空间中两个进程之间的通信,其采用共享内存的通信方式。

vhost-user 基于 C/S 的模式,采用 UNIX 域套接字(UNIX domain socket)来完成进程间的事件通知和数据交互,相比 vhost_net 中采用 ioctl 的方式,vhost-user 采用 socket 的方式大大简化了操作。

vhost-user 基于 vring 这套通用的共享内存通信方案,只要 client 和 server 按照 vring 提供的接口实现所需功能即可,常见的实现方案是 client 实现在 guest OS 中,一般是集成在 virtio 驱动上,server 端实现在 qemu 中,也可以实现在各种数据面中,如 OVS,Snabbswitch 等虚拟交换机。

如果使用 qemu 作为 vhost-user 的 server 端实现,在启动 qemu 时,我们需要指定 -mem-path 和 -netdev 参数,如:

$ qemu -m 1024 -mem-path /hugetlbfs,prealloc=on,share=on \
-netdev type=vhost-user,id=net0,file=/path/to/socket \
-device virtio-net-pci,netdev=net0

指定 -mem-path 意味着 qemu 会在 guest OS 的内存中创建一个文件,share=on 选项允许其他进程访问这个文件,也就意味着能访问 guest OS 内存,达到共享内存的目的。

-netdev type=vhost-user 指定通信方案,file=/path/to/socket 指定 socket 文件。

当 qemu 启动之后,首先会进行 vring 的初始化,并通过 socket 建立 C/S 的共享内存区域和事件机制,然后 client 通过 eventfd 将 virtio kick 事件通知到 server 端,server 端同样通过 eventfd 进行响应,完成整个数据交互。

 参考链接:

Linux kernel Vhost-net 和 Virtio-net代码详解

Linux kernel Vhost-net 和 Virtio-net代码详解 - allcloud - 博客园

static void vhost_user_rdma_device_realize(DeviceState *dev, Error **errp) { VirtIODevice *vdev = VIRTIO_DEVICE(dev); VHostUserRdma *r = VHOST_USER_RDMA(vdev); Error *err = NULL; int i, ret; if (!r->chardev.chr) { error_setg(errp, "vhost-user-rdma: chardev is mandatory"); return; } r->num_queues = VHOST_USER_RDMA_NUM_QUEUES; if (r->num_queues > VIRTIO_QUEUE_MAX) { error_setg(errp, "vhost-user-rdma: invalid number of IO queues"); return; } if (!vhost_user_init(&r->vhost_user, &r->chardev, errp)) { return; } virtio_init(vdev, VIRTIO_ID_RDMA, sizeof(struct virtio_rdma_config)); r->virtqs = g_new(VirtQueue *, r->num_queues); for (i = 0; i < r->num_queues; i++) { r->virtqs[i] = virtio_add_queue(vdev, VHOST_USER_RDMA_QUEUE_SIZE, vhost_user_rdma_handle_output); } r->vhost_vqs = g_new0(struct vhost_virtqueue, r->num_queues); r->connected = false; qemu_chr_fe_set_handlers(&r->chardev, NULL, NULL, vhost_user_rdma_event, NULL, (void *)dev, NULL, true); reconnect: if (qemu_chr_fe_wait_connected(&r->chardev, &err) < 0) { error_report_err(err); goto virtio_err; } /* check whether vhost_user_rdma_connect() failed or not */ if (!r->connected) { goto reconnect; } ret = vhost_dev_get_config(&r->dev, (uint8_t *)&r->rdmacfg, sizeof(struct virtio_rdma_config), &err); if (ret < 0) { error_report("vhost-user-rdma: get rdma config failed"); goto reconnect; } return; virtio_err: g_free(r->vhost_vqs); r->vhost_vqs = NULL; for (i = 0; i < r->num_queues; i++) { virtio_delete_queue(r->virtqs[i]); } g_free(r->virtqs); virtio_cleanup(vdev); vhost_user_cleanup(&r->vhost_user); }
03-25
/* * Vhost user rdma PCI Bindings * * Copyright(C) 2021 Bytedance Inc. All rights reserved. * * Authors: * Junji Wei <weijunji@bytedance.com> * * This work is licensed under the terms of the GNU LGPL, version 2 or later. * See the COPYING.LIB file in the top-level directory. * */ #include "qemu/osdep.h" #include "standard-headers/rdma/virtio_rdma.h" #include "hw/virtio/virtio.h" #include "hw/virtio/vhost-user-rdma.h" #include "hw/pci/pci.h" #include "hw/qdev-properties.h" #include "qapi/error.h" #include "qemu/error-report.h" #include "qemu/module.h" #include "hw/virtio/virtio-pci.h" #include "qom/object.h" typedef struct VHostUserRdmaPCI VHostUserRdmaPCI; #define TYPE_VHOST_USER_RDMA_PCI "vhost-user-rdma-pci-base" DECLARE_INSTANCE_CHECKER(VHostUserRdmaPCI, VHOST_USER_RDMA_PCI, TYPE_VHOST_USER_RDMA_PCI) struct VHostUserRdmaPCI { VirtIOPCIProxy parent_obj; VHostUserRdma vdev; }; static Property vhost_user_rdma_pci_properties[] = { DEFINE_PROP_UINT32("class", VirtIOPCIProxy, class_code, 0), DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, DEV_NVECTORS_UNSPECIFIED), DEFINE_PROP_END_OF_LIST(), }; static void vhost_user_rdma_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp) { VHostUserRdmaPCI *dev = VHOST_USER_RDMA_PCI(vpci_dev); DeviceState *vdev = DEVICE(&dev->vdev); if (vpci_dev->nvectors == DEV_NVECTORS_UNSPECIFIED) { vpci_dev->nvectors = dev->vdev.num_queues + 1; } // vhost-user not support pci legacy virtio_pci_force_virtio_1(vpci_dev); qdev_realize(vdev, BUS(&vpci_dev->bus), errp); } static void vhost_user_rdma_pci_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass); PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass); set_bit(DEVICE_CATEGORY_NETWORK, dc->categories); device_class_set_props(dc, vhost_user_rdma_pci_properties); k->realize = vhost_user_rdma_pci_realize; pcidev_k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET; pcidev_k->device_id = PCI_DEVICE_ID_VIRTIO_RDMA; pcidev_k->revision = VIRTIO_PCI_ABI_VERSION; pcidev_k->class_id = PCI_CLASS_NETWORK_OTHER; } static void vhost_user_rdma_pci_instance_init(Object *obj) { VHostUserRdmaPCI *dev = VHOST_USER_RDMA_PCI(obj); virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev), TYPE_VHOST_USER_RDMA); object_property_add_alias(obj, "bootindex", OBJECT(&dev->vdev), "bootindex"); } static const VirtioPCIDeviceTypeInfo vhost_user_rdma_pci_info = { .base_name = TYPE_VHOST_USER_RDMA_PCI, .generic_name = "vhost-user-rdma-pci", .transitional_name = "vhost-user-rdma-pci-transitional", .non_transitional_name = "vhost-user-rdma-pci-non-transitional", .instance_size = sizeof(VHostUserRdmaPCI), .instance_init = vhost_user_rdma_pci_instance_init, .class_init = vhost_user_rdma_pci_class_init, }; static void vhost_user_rdma_pci_register(void) { virtio_pci_types_register(&vhost_user_rdma_pci_info); } type_init(vhost_user_rdma_pci_register) #include “qemu/osdep.h” #include “qapi/error.h” #include “qemu/error-report.h” #include “qemu/cutils.h” #include “hw/qdev-core.h” #include “hw/qdev-properties.h” #include “hw/virtio/vhost.h” #include “hw/virtio/vhost-user-rdma.h” #include “hw/virtio/virtio.h” #include “hw/virtio/virtio-bus.h” #include “hw/virtio/virtio-access.h” #include “sysemu/sysemu.h” #include “sysemu/runstate.h” // FIXME: should read from slave #define VHOST_USER_RDMA_NUM_QUEUES 256 #define VHOST_USER_RDMA_QUEUE_SIZE 512 static const int user_feature_bits[] = { VIRTIO_F_VERSION_1, VIRTIO_RING_F_INDIRECT_DESC, VIRTIO_RING_F_EVENT_IDX, VIRTIO_F_NOTIFY_ON_EMPTY, VHOST_INVALID_FEATURE_BIT }; static int vhost_user_rdma_start(VirtIODevice *vdev) { VHostUserRdma *r = VHOST_USER_RDMA(vdev); BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev))); VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); int i, ret; if (!k->set_guest_notifiers) { error_report("binding does not support guest notifiers"); return -ENOSYS; } ret = vhost_dev_enable_notifiers(&r->dev, vdev); if (ret < 0) { error_report("Error enabling host notifiers: %d", -ret); return ret; } ret = k->set_guest_notifiers(qbus->parent, r->dev.nvqs, true); if (ret < 0) { error_report("Error binding guest notifier: %d", -ret); goto err_host_notifiers; } r->dev.acked_features = vdev->guest_features; ret = vhost_dev_start(&r->dev, vdev, true); if (ret < 0) { error_report("Error starting vhost: %d", -ret); goto err_guest_notifiers; } r->started_vu = true; for (i = 0; i < r->dev.nvqs; i++) { vhost_virtqueue_mask(&r->dev, vdev, i, false); } return ret; err_guest_notifiers: k->set_guest_notifiers(qbus->parent, r->dev.nvqs, false); err_host_notifiers: vhost_dev_disable_notifiers(&r->dev, vdev); return ret; } static void vhost_user_rdma_stop(VirtIODevice *vdev) { VHostUserRdma *r = VHOST_USER_RDMA(vdev); BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev))); VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); int ret; if (!r->started_vu) { return; } r->started_vu = false; if (!k->set_guest_notifiers) { return; } vhost_dev_stop(&r->dev, vdev, true); ret = k->set_guest_notifiers(qbus->parent, r->dev.nvqs, false); if (ret < 0) { error_report("vhost guest notifier cleanup failed: %d", ret); return; } vhost_dev_disable_notifiers(&r->dev, vdev); } static int vhost_user_rdma_handle_config_change(struct vhost_dev *dev) { int ret; VHostUserRdma *r = VHOST_USER_RDMA(dev->vdev); Error *local_err = NULL; ret = vhost_dev_get_config(dev, (uint8_t *)&r->rdmacfg, sizeof(struct virtio_rdma_config), &local_err); if (ret < 0) { error_report("get config space failed"); return -1; } virtio_notify_config(dev->vdev); return 0; } // slave’s config changed notify const VhostDevConfigOps rdma_ops = { .vhost_dev_config_notifier = vhost_user_rdma_handle_config_change, }; static int vhost_user_rdma_connect(DeviceState *dev) { VirtIODevice *vdev = VIRTIO_DEVICE(dev); VHostUserRdma *r = VHOST_USER_RDMA(vdev); int ret = 0; Error *local_err = NULL; info_report("vhost_user_rdma: vhost connect"); if (r->connected) { return 0; } r->connected = true; r->dev.nvqs = r->num_queues; r->dev.vqs = r->vhost_vqs; r->dev.vq_index = 0; r->dev.backend_features = 0; vhost_dev_set_config_notifier(&r->dev, &rdma_ops); ret = vhost_dev_init(&r->dev, &r->vhost_user, VHOST_BACKEND_TYPE_USER, 0, &local_err); if (ret < 0) { error_report("vhost-user-rdma: vhost initialization failed: %s", strerror(-ret)); return ret; } /* restore vhost state */ if (virtio_device_started(vdev, vdev->status)) { info_report("vhost_user_rdma: vhost ss?"); ret = vhost_user_rdma_start(vdev); if (ret < 0) { error_report("vhost-user-rdma: vhost start failed: %s", strerror(-ret)); return ret; } } info_report("vhost_user_rdma: vhost connect success"); return 0; } static void vhost_user_rdma_disconnect(DeviceState *dev) { VirtIODevice *vdev = VIRTIO_DEVICE(dev); VHostUserRdma *s = VHOST_USER_RDMA(vdev); if (!s->connected) { return; } s->connected = false; vhost_user_rdma_stop(vdev); vhost_dev_cleanup(&s->dev); } static void vhost_user_rdma_event(void *opaque, QEMUChrEvent event); static void vhost_user_rdma_chr_closed_bh(void *opaque) { DeviceState *dev = opaque; VirtIODevice *vdev = VIRTIO_DEVICE(dev); VHostUserRdma *r = VHOST_USER_RDMA(vdev); vhost_user_rdma_disconnect(dev); qemu_chr_fe_set_handlers(&r->chardev, NULL, NULL, vhost_user_rdma_event, NULL, opaque, NULL, true); } static void vhost_user_rdma_event(void *opaque, QEMUChrEvent event) { DeviceState *dev = opaque; VirtIODevice *vdev = VIRTIO_DEVICE(dev); VHostUserRdma *r = VHOST_USER_RDMA(vdev); switch (event) { case CHR_EVENT_OPENED: if (vhost_user_rdma_connect(dev) < 0) { qemu_chr_fe_disconnect(&r->chardev); return; } break; case CHR_EVENT_CLOSED: if (runstate_is_running()) { AioContext *ctx = qemu_get_current_aio_context(); qemu_chr_fe_set_handlers(&r->chardev, NULL, NULL, NULL, NULL, NULL, NULL, false); aio_bh_schedule_oneshot(ctx, vhost_user_rdma_chr_closed_bh, opaque); } r->dev.started = false; break; case CHR_EVENT_BREAK: case CHR_EVENT_MUX_IN: case CHR_EVENT_MUX_OUT: /* Ignore */ break; } } static void vhost_user_rdma_handle_output(VirtIODevice *vdev, VirtQueue *vq) { VHostUserRdma *r = VHOST_USER_RDMA(vdev); int i, ret; if (!vdev->start_on_kick) { return; } if (!r->connected) { return; } if (r->dev.started) { return; } ret = vhost_user_rdma_start(vdev); if (ret < 0) { // error_report("vhost-user-rdma: vhost start failed: %s", // strerror(-ret)); qemu_chr_fe_disconnect(&r->chardev); return; } for (i = 0; i < r->dev.nvqs; i++) { VirtQueue *kick_vq = virtio_get_queue(vdev, i); if (!virtio_queue_get_desc_addr(vdev, i)) { continue; } event_notifier_set(virtio_queue_get_host_notifier(kick_vq)); } } static void vhost_user_rdma_update_config(VirtIODevice *vdev, uint8_t *config) { VHostUserRdma *r = VHOST_USER_RDMA(vdev); memcpy(config, &r->rdmacfg, sizeof(struct virtio_rdma_config)); } static void vhost_user_rdma_set_config(VirtIODevice *vdev, const uint8_t *config) { // nothing to do? } static uint64_t vhost_user_rdma_get_features(VirtIODevice *vdev, uint64_t features, Error **errp) { VHostUserRdma *s = VHOST_USER_RDMA(vdev); return vhost_get_features(&s->dev, user_feature_bits, features); } static void vhost_user_rdma_set_status(VirtIODevice *vdev, uint8_t status) { VHostUserRdma *r = VHOST_USER_RDMA(vdev); bool should_start = virtio_device_started(vdev, status); int ret; if (!vdev->vm_running) { should_start = false; } if (!r->connected) { return; } if (r->dev.started == should_start) { return; } if (should_start) { ret = vhost_user_rdma_start(vdev); if (ret < 0) { error_report("vhost-user-rdma: vhost start failed: %s", strerror(-ret)); qemu_chr_fe_disconnect(&r->chardev); } } else { vhost_user_rdma_stop(vdev); } } static void vhost_user_rdma_device_realize(DeviceState *dev, Error **errp) { VirtIODevice *vdev = VIRTIO_DEVICE(dev); VHostUserRdma *r = VHOST_USER_RDMA(vdev); Error *err = NULL; int i, ret; if (!r->chardev.chr) { error_setg(errp, "vhost-user-rdma: chardev is mandatory"); return; } r->num_queues = VHOST_USER_RDMA_NUM_QUEUES; if (r->num_queues > VIRTIO_QUEUE_MAX) { error_setg(errp, "vhost-user-rdma: invalid number of IO queues"); return; } if (!vhost_user_init(&r->vhost_user, &r->chardev, errp)) { return; } virtio_init(vdev, VIRTIO_ID_RDMA, sizeof(struct virtio_rdma_config)); r->virtqs = g_new(VirtQueue *, r->num_queues); for (i = 0; i < r->num_queues; i++) { r->virtqs[i] = virtio_add_queue(vdev, VHOST_USER_RDMA_QUEUE_SIZE, vhost_user_rdma_handle_output); } r->vhost_vqs = g_new0(struct vhost_virtqueue, r->num_queues); r->connected = false; qemu_chr_fe_set_handlers(&r->chardev, NULL, NULL, vhost_user_rdma_event, NULL, (void *)dev, NULL, true); reconnect: if (qemu_chr_fe_wait_connected(&r->chardev, &err) < 0) { error_report_err(err); goto virtio_err; } /* check whether vhost_user_rdma_connect() failed or not */ if (!r->connected) { goto reconnect; } ret = vhost_dev_get_config(&r->dev, (uint8_t *)&r->rdmacfg, sizeof(struct virtio_rdma_config), &err); if (ret < 0) { error_report("vhost-user-rdma: get rdma config failed"); goto reconnect; } return; virtio_err: g_free(r->vhost_vqs); r->vhost_vqs = NULL; for (i = 0; i < r->num_queues; i++) { virtio_delete_queue(r->virtqs[i]); } g_free(r->virtqs); virtio_cleanup(vdev); vhost_user_cleanup(&r->vhost_user); } static void vhost_user_rdma_device_unrealize(DeviceState *dev) { VirtIODevice *vdev = VIRTIO_DEVICE(dev); VHostUserRdma *r = VHOST_USER_RDMA(dev); int i; virtio_set_status(vdev, 0); qemu_chr_fe_set_handlers(&r->chardev, NULL, NULL, NULL, NULL, NULL, NULL, false); vhost_dev_cleanup(&r->dev); g_free(r->vhost_vqs); r->vhost_vqs = NULL; for (i = 0; i < r->num_queues; i++) { virtio_delete_queue(r->virtqs[i]); } g_free(r->virtqs); virtio_cleanup(vdev); vhost_user_cleanup(&r->vhost_user); } static void vhost_user_rdma_instance_init(Object *obj) { VHostUserRdma *r = VHOST_USER_RDMA(obj); device_add_bootindex_property(obj, &r->bootindex, "bootindex", "bootindex", DEVICE(obj)); } static const VMStateDescription vmstate_vhost_user_rdma = { .name = “vhost-user-rdma”, .minimum_version_id = 1, .version_id = 1, .fields = (VMStateField[]) { VMSTATE_VIRTIO_DEVICE, VMSTATE_END_OF_LIST() }, }; static Property vhost_user_rdma_properties[] = { DEFINE_PROP_CHR(“chardev”, VHostUserRdma, chardev), DEFINE_PROP_END_OF_LIST(), }; static void vhost_user_rdma_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); device_class_set_props(dc, vhost_user_rdma_properties); dc->vmsd = &vmstate_vhost_user_rdma; set_bit(DEVICE_CATEGORY_NETWORK, dc->categories); vdc->realize = vhost_user_rdma_device_realize; vdc->unrealize = vhost_user_rdma_device_unrealize; vdc->get_config = vhost_user_rdma_update_config; vdc->set_config = vhost_user_rdma_set_config; vdc->get_features = vhost_user_rdma_get_features; vdc->set_status = vhost_user_rdma_set_status; } static const TypeInfo vhost_user_rdma_info = { .name = TYPE_VHOST_USER_RDMA, .parent = TYPE_VIRTIO_DEVICE, .instance_size = sizeof(VHostUserRdma), .instance_init = vhost_user_rdma_instance_init, .class_init = vhost_user_rdma_class_init, }; static void virtio_register_types(void) { type_register_static(&vhost_user_rdma_info); } type_init(virtio_register_types) 为这段qemu代码 生成一个 符合qemu代码提交规则的 英文commit信息
最新发布
12-16
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值