vhost-net-原理-初始化流程-数据传输流程-vhost-net后端

news2024/9/20 16:35:16

文章目录

  • 1.vhost net
  • 2.vhost-net的初始化流程
    • vhost net设置
    • vhost dev设置
    • vhost vring设置
  • 3.数据收发流程分析
    • 3.1 数据发送
    • 3.2 数据接收
  • 4ioventfd和irqfd的通知机制
    • 4.1ioeventfd
      • qemu侧
      • kvm侧
      • 总体效果
    • 4.2irqfd
      • qemu侧
      • kvm侧
      • 总体效果
    • 参考:

1.vhost net

传统的virtio网卡是通过虚拟机内部的virtio驱动作为前端,负责将虚拟机内部的IO请求封装到vring descriptor中,然后通过写MMIO或PIO的方式通知QEMU中的virtio后端设备,QEMU将这些IO请求设备发送到tap设备,然后通过网桥发送到真实的网卡上
vhost方案也是通过虚拟机中的virtio驱动将IO请求封装在vring descriptor中,但vhsot是由宿主内核中的vhost模块作为virtio后端,vhost在收到虚拟机的通知后会直接在宿主机内核中与tap设备通信,从而完成数据的收发

vhost-net框架图
在这里插入图片描述
在这里插入图片描述
vhost-net 是一个内核驱动程序,它实现了 vhost 协议的处理程序端,以实现高效的数据平面,即数据包转发。
qemu 和 vhost-net 内核驱动程序(处理程序)使用 ioctl 来交换 vhost 消息,并使用几个名为 irqfd 和 ioeventfd 的类似 eventfd 的文件描述符来与guest交换通知。
当加载 vhost-net 内核驱动程序时,它会在/dev/vhost-net 上公开一个字符设备。
当 qemu 在 vhost-net 支持下启动时,它会打开它并使用多个 ioctl(2) 调用来初始化 vhost-net 实例。
这些是将虚拟机管理程序进程与 vhost-net 实例关联、准备 virtio 功能协商并将guest物理内存映射传递给 vhost-net 驱动程序所必需的。
在初始化期间,vhost-net 内核驱动程序创建一个名为 vhost-$pid 的内核线程,其中 p i d 是管理程序进程 p i d 。该线程称为“ v h o s t 工作线程”。 T a p 设备仍然用于 V M 与主机的通信,但现在工作线程处理 I / O 事件,即轮询驱动程序通知或 T a p 事件,并转发数据。 Q e m u 分配一个 e v e n t f d 并将其注册到 v h o s t 和 K V M 以实现通知绕过。 v h o s t − pid 是管理程序进程 pid。该线程称为“vhost 工作线程”。 Tap 设备仍然用于 VM 与主机的通信,但现在工作线程处理 I/O 事件,即轮询驱动程序通知或 Tap 事件,并转发数据。 Qemu分配一个eventfd并将其注册到vhost和KVM以实现通知绕过。vhost- pid是管理程序进程pid。该线程称为vhost工作线程Tap设备仍然用于VM与主机的通信,但现在工作线程处理I/O事件,即轮询驱动程序通知或Tap事件,并转发数据。Qemu分配一个eventfd并将其注册到vhostKVM以实现通知绕过。vhostpid 内核线程轮询它,当 guest 写入特定地址时,KVM 写入它。这个机制被命名为ioeventfd。
这样,对特定客户内存地址的简单读/写操作不需要经过昂贵的 QEMU 进程唤醒,可以直接路由到 vhost 工作线程。这还有一个优点是异步的,不需要 vCPU 停止(因此不需要立即进行上下文切换)。
另一方面,qemu分配另一个eventfd并再次将其注册到KVM和vhost,以进行直接vCPU中断注入。这种机制称为irqfd,它允许主机中的任何进程通过写入来向客户机注入 vCPU 中断,具有相同的优点(异步、不需要立即上下文切换等)。
请注意,virtio 数据包处理后端中的此类更改对于仍然使用标准 virtio 接口的guest来说是完全透明的。
vhost-net内核模块层次图
在这里插入图片描述

struct vhost_net:用于描述Vhost-Net设备。它包含几个关键字段:
1)struct vhost_dev,通用的vhost设备,可以类比struct device结构体内嵌在其他特定设备的结构体中;
2)struct vhost_net_virtqueue,实际上对struct vhost_virtqueue进行了封装,用于网络包的数据传输;
3)struct vhost_poll,用于socket的poll,以便在数据包接收与发送时进行任务调度;
struct vhost_dev:描述通用的vhost设备,可内嵌在基于vhost机制的其他设备结构体中,比如struct vhost_net,struct vhost_scsi等。关键字段如下:
1)vqs指针,指向已经分配好的struct vhost_virtqueue,对应数据传输;
2)work_list,任务链表,用于放置需要在vhost_worker内核线程上执行的任务;
3)worker,用于指向创建的内核线程,执行任务列表中的任务;
struct vhost_virtqueue:用于描述设备对应的virtqueue,这部分内容可以参考之前virtqueue机制分析,本质上是将Qemu中virtqueue处理机制下沉到了Kernel中。关键字段下:
1)struct vhost_poll,用于poll eventfd对应的文件,当不满足处理请求时会添加到eventfd对应的等待队列中,
而一旦被唤醒,该结构体中的struct vhost_work(执行函数被初始化为handle_tx_kick,以发送为例)将被放置到内核线程中去执行; 结构体的核心围绕着数据和通知机制,其中数据在vhost_virtqueue中体现,而通知主要是通过vhost_poll来实现,具体的细节下文将进一步描述。

2.vhost-net的初始化流程

net_init_tap        //qemu-8.0.2\net\tap.c
    net_init_tap_one
        vhost_net_init    //qemu-8.0.2\net\vhost-net.c
            vhost_dev_init    //qemu-8.0.2\net\vhost.c

在这里插入图片描述

Qemu中tap设备初始化在net_init_tap中完成,其中net_init_tap_one打开vhost-net设备文件,通过ioctl与内核的vhost-net交互;
vhost_set_backend_type设置vhost的后端类型,以及vhost的操作函数集。目前有两种vhost后端,一种是在内核态实现的virtio后端,一种是在用户态中实现的virtio后端;
kernel_ops:vhost的内核操作函数集,都是一些回调函数的实现,
最终会通过vhost_kernel_call–>ioctl–>vhost-net.ko路径,进行配置;
ioctl系统调用,与驱动交互简单来说可以分为三大类,下边分别介绍几个关键的设置:
在这里插入图片描述

vhost net设置

VHOST_SET_OWNER:底层会为调用者创建一个内核线程,对应到前文中数据结构中的vhost_worker,同时在vhost_dev结构体中还会保存调用者线程的内存空间数据结构;
VHOST_NET_SET_BACKEND设置vhost-net的后端设备,比如Qemu往内核态传递的tap设备对应的fd,从而让vhost-net直接与tap设备进行通信;

vhost dev设置

从Guest OS中的虚拟地址到最终的Host上的物理地址映射关系如上图所示,如果在Guest OS中要将数据发送出去,实际上只需要将Qemu中关于Guest OS的物理地址布局信息传递下去,此外再结合VHOST_SET_OWNER时传递的内存空间信息,就可以根据映射关系找到Guest OS中的数据对应到Host之上的物理地址,完成最后搬运即可;
VHOST_SET_MEM_TABLE将Qemu中的虚拟机物理地址布局信息传递给内核:
在这里插入图片描述

vhost vring设置

VHOST_SET_VRING_KICK设置vhost-net模块前端virtio驱动发送通知时触发的eventfd,通知机制,最终触发handle_kick函数的执行;
VHOST_SET_VRING_CALL设置vhost-net后端到虚拟机virtio前端的中断通知,参考之前文章中的irqfd机制;
此外关于vring的设备还包括vring的大小,地址信息等;
上述的这些设置的流程路径如下,关键路径:
在这里插入图片描述

当Guest OS中的virtio-net驱动完成初始化后,会通过vp_set_status来设置状态,以通知后端驱动已经ready,此时会触发VM的退出并进入KVM进行异常处理,最终路由给Qemu;
Qemu中的vcpu线程监测异常,当检测到KVM_EXIT_MMIO时,去回调注册该IO区域的读写函数,比如virtio_pci_common_write函数,在该函数中逐级往下最终调用到vhost_net_start函数;
在vhost_net_start中最终去通过kernel_ops函数集去设置底层并交互;初始化完成后,接下来让我们看看数据的发送与接收,为了能将整个流程表达清楚,我会将完整的图拆分成几个步骤来讲述。

3.数据收发流程分析

3.1 数据发送

vhost-net发送缓冲区图流程
在这里插入图片描述

1)
发送前的框图如下:
在这里插入图片描述

Guest OS中的virtio-net驱动中维护两个virtqueue,分别用于发送和接收;
图中的datagram表示的是需要发送的数据;
KVM模块提供了ioeventfd和irqfd用于通知机制;
vhost-net模块中创建好了vhost_worker内核线程,用于处理任务;
2)

在这里插入图片描述

当数据包准备好之后,通过往kick fd上触发信号,从而唤醒vhost_worker内核线程来调用handle_tx_kick进行数据的发送;
当Tap/Tun不具备发送条件时,vhost_worker会poll在socket上,等待Tap/Tun的唤醒,一旦被唤醒后可以调用handle_tx_net发送;
最终的handle_tx完成具体的发送;
3)
在这里插入图片描述

vhost_get_vq_desc函数在vritqueue中查找可用的buffer,并将信息存储到iov中,以便更好的访问;
sock->ops->sendmsg()函数,实际调用的是tun_sendmsg函数,在该函数中分配了skb结构体,并将iov[]中的信息传递过来,最终如图中所示完成数据的拷贝和发送,通过NIC发送出去;
4)
在这里插入图片描述

数据发送完毕后,通过irqfd机制通知vcpu;

3.2 数据接收

数据的接收是发送的逆过程,流程一致:
1)
在这里插入图片描述

初始化部分与发送过程一致;
Tap/Tun驱动从NIC接收到数据包,准备发送给vhost-net;
在这里插入图片描述

vhost-net中的vhost_worker线程也poll在两个fd之上,与发送端类似;
kick fd上触发信号时最终调用handle_rx_kick函数,Tap/Tun对应的socket上触发信号时,调用handle_rx_net函数;
最终通过handle_rx来完成实际的接收;
3)
在这里插入图片描述

接收过程中,vhost_get_vq_desc获取virtqueue中的可用buffer,并将信息存储到iov[]中;
sock->ops->recvmsg()函数实际指向tun_recvmsg函数,在该函数中最终完成数据的传递;
4)
在这里插入图片描述

数据接收完成后,通过irqfd机制通过vcpu,从而在Guest OS中进行处理;

4ioventfd和irqfd的通知机制

在这里插入图片描述

irqfd:提供一种机制,可以通过文件描述fd来向Guest注入中断,路径为紫色线条所示;
ioeventfd:提供一种机制,可以通过文件描述符fd来接收Guest的信号,路径为红色线条所示;
eventfd和irqfd这两种机制,都是基于eventfd来实现的;
irqfd是kvm\qemu通知虚拟机内部系统的一种快捷通道
ioeventfd是虚拟机内部操作系统通知kvm\qemu的一种快捷通道

4.1ioeventfd

qemu可以为虚拟机特定的地址关联一个eventfd,并对该eventfd进行事件监听,然后调用ioctl(KVM_IOEVENTFD)向KVM注册这段地址,当虚拟机内部因为IO发生VMExit时,KVM可以判断其地址是否有对应的eventfd,如果有就直接调用eventfd_signal发送信号到对应的fd,这样QEMU就能够从其事件监听循环返回,进而进行处理`

qemu侧

ioeventfd工作流程如下:
virtio_ioport_write
  virtio_pci_start_ioeventfd
    virtio_bus_start_ioeventfd              //qemu-8.0.2\hw\virtio\virtio-bus.c
      memory_region_add_eventfd            //qemu-8.0.2\softmmu\memory.c
        memory_region_transaction_commit
                    address_space_set_flatview
                        address_space_update_topology_pass
           address_space_update_ioeventfds
              address_space_add_del_ioeventfds
                kvm_init->  eventfd_add=kvm_mem_ioeventfd_add //qemu-8.0.2\accel\kvm\kvm-all.c
                  kvm_set_ioeventfd_mmio
                    kvm_vm_ioctl(kvm_state, KVM_IOEVENTFD, &iofd);//向KVM注册ioeventfd

在这里插入图片描述

内存区域MemoryRegion中的ioeventfds成员按照地址从小到大排序,memory_region_add_eventfd函数会选择合适的位置将ioeventfds插入,并提交更新;
提交更新过程中最终触发回调函数kvm_mem_ioeventfd_add的执行,这个函数指针的初始化是在Qemu进行kvm_init时,针对不同类型的内存区域注册了对应的memory_listener用于监听变化;
kvm_vm_ioctl:向KVM注册ioeventfd;Qemu中完成了初始化后,任务就转移到了KVM中。

kvm侧

在这里插入图片描述

KVM中注册ioeventfd的核心函数为kvm_assign_ioeventfd_idx,该函数中主要工作包括:
1)根据用户空间传递过来的fd获取到内核中对应的struct eventfd_ctx结构体上下文;
2)使用ioeventfd_ops操作函数集来初始化IO设备操作;
3)向KVM注册IO总线,比如KVM_MMIO_BUS,注册了一段IO地址区域,当操作这段区域的时候出发对应的操作函数回调;
当Guest OS中进行IO操作时,触发VM异常退出,KVM进行捕获处理,最终调用注册的ioevnetfd_write
在该函数中调用eventfd_signal唤醒阻塞在eventfd上的任务,Qemu和KVM完成了闭环;

总体效果

在这里插入图片描述

4.2irqfd

qemu侧

virtio_pci_set_guest_notifiers   //qemu-8.0.2\hw\virtio\virtio-pci.c
    kvm_virtio_pci_vector_vq_use
        kvm_virtio_pci_vector_use_one
            kvm_virtio_pci_irqfd_use
                kvm_irqchip_add_irqfd_notifier_gsi
                    kvm_irqchip_assign_irqfd    //qemu-8.0.2accel\kvm\kvm-all.c
                        kvm_vm_ioctl(s, KVM_IRQFD, &irqfd);  //向kvm发起ioctl请求

kvm侧

linux-5.16\virt\kvm\eventfd.c
kvm_irqfd
    kvm_irqfd_assign

在这里插入图片描述

Qemu中通过kvm_irqchip_assign_irqfd向KVM申请注册irqfd;
在KVM中,内核通过维护struct kvm_kernel_irqfd结构体来管理整个irqfd的流程;
kvm_irqfd_assign:
1)分配struct kvm_kernel_irqfd结构体,并进行各个字段的初始化;
2)初始化工作队列任务,设置成irqfd_inject,用于向Guest OS注入虚拟中断;
3)初始化等待队列的唤醒函数,设置成irqfd_wakeup,当任务被唤醒时执行,在该函数中会去调度工作任务irqfd_inject;
4)初始化pll_table pt字段的处理函数,设置成irqfd_ptable_queue_proc,该函数实际是调用add_wait_queue将任务添加至eventfd的等待队列中,这个过程是在vfs_poll中完成的;
当Qemu通过irqfd机制发送信号时,将唤醒睡眠在eventfd上的任务,唤醒后执行irqfd_wakeup函数,在该函数中调度任务,调用irqfd_inject来注入中断;

总体效果

在这里插入图片描述

qemu进行tap设备初始化
qemu8.0.2/net/tap.c

net_init_tap-->
    net_init_tap_one-->
        vhost_net_init(&options)-->
            vhost_net_get_fd(options->net_backend)
            vhost_dev_init-->
                vhost_set_backend_type-->
                    dev->vhost_ops = &kernel_ops;#在dev/host-net所在fd上调用ioctl,从而实现在host中的vhost-net模块进行交互
            vhost_net_ack_features

linux-5.16\drivers\vhost\net.c

vhost_net_init-->
    misc_register(&vhost_net_misc)-->
        .fops = &vhost_net_fops-->
            .open = vhost_net_open-->#用户态程序每次打开/dev/vhost-net都会调用该函数
            vhost_net_open-->        #初始化一个vhost_net结构体及其成员vhost_dev和收发包队列vhost_virtqueue;
                                     #同时将vhost_net设置为open打开的file结构体的private_data;f->private_data = n;
                vhost_dev_init-->
                        vhost_poll_init-->#对vhost_virqueue中的vhost_poll成员进行初始化
                            init_waitqueue_func_entry(vhost_poll_wakeup)-->
                                vhost_poll_wakeup-->                                
                vhost_poll_init(handle_tx_net)-->  
                    handle_tx_net  

参考:

https://www.redhat.com/en/blog/deep-dive-virtio-networking-and-vhost-net
https://www.redhat.com/en/blog/introduction-virtio-networking-and-vhost-net
https://www.cnblogs.com/LoyenWang/p/14399642.html
https://cloud.tencent.com/developer/article/1075600
https://blog.csdn.net/huang987246510/article/details/121046398
https://blog.csdn.net/qq_41596356/article/details/128538073

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/709099.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ChatGPT Plugins内幕、源码及案例实战(一)

ChatGPT Plugins内幕、源码及案例实战 6.1 ChatGPT Plugins的工作原理 本节主要跟大家谈ChatGPT的插件(Plugins),这个内容非常重要。现在很多企业级的开发,一般都会基于ChatGPT 插件进行一些服务的封装,相当于开发了一个代理(Agent),把一些服务或者API封装在里面,然后…

eclipse编辑器汉化;eclipse安装中文插件

eclipse IDE默认是英文环境,使用起来略微不便,汉化还是很有必要的;下面记录一下安装中文插件的过程: 文章目录 一、 选择安装包地址二、 在eclipse安装中文插件2.1 在线安装2.2 手动下载安装包2.3 导入到eclipse 三、汉化插件介绍 一、 选择安…

实例005 可以拉伸的菜单界面

实例说明 如果管理程序功能菜单非常多,而用户只使用一些常用菜单,这时,可以将主菜单项下的不常用菜单隐藏起来。此种显示方式类似于对菜单进行拉伸。使用时,只需单击展开菜单,即可显示相应菜单功能。运行本例&#xf…

python matplotlib中colorbar的位置设置

colorbar单独设置一个轴对象,再对轴对象进行灵活设置 import numpy as np import matplotlib.pyplot as plt# 创建一个二维随机数组 data np.random.rand(10, 10)# 创建一个图形和一个子图 fig, ax plt.subplots()# 绘制热力图 heatmap ax.imshow(data, cmaphot…

在linux中快速安装Redis数据库

Redis中文网 点击该链接下载最5.0.4版本的Redis的压缩包 使用Xftp工具将Redis安装包上传到linux中 1.将压缩包解压到/opt目录下: tar -zxvf redis-5.0.4.tar.gz 2. 更新yun: sudo yum makecache fast 3.安装gcc: yum -y install gcc 4.安装完成通过输入 : gcc -v …

tiny tool - get_file_path_name_by_drop_file

文章目录 tiny tool - get_file_path_name_by_drop_file概述工程效果收获的知识点vs2022工程, 必须自己设置对话框可以接受文件的风格vs2022建立的工程, 默认是unicode编码, 设置剪贴板数据时, 必须要设置为unicode的格式, 否则剪切板中只有第一个字符工程主要实现END tiny too…

短信压力测试系统,支持自定义接口

短信压力测试系统,支持自定义接口 支持卡密充值,短信压力测试系统,解决一切骚扰电话,教程在压缩包里面 可多个服务器挂脚本分担压力,套了cdn导致无法正常执行脚本可以尝试添加白名单 这边建议使用MySQL方式 同服务器下直接配置…

MySQL生产环境高可用架构实战

分布式技术MongoDB 1. MySQL高可用集群介绍1.1 数据库主从架构与分库分表1.2 MySQL主从同步原理 2. 动手搭建MySQL主从集群2.1 基础环境搭建2.2 安装MySQL服务2.2.1 初始化MySQL2.2.2 启动mysql2.2.3 连接MySQL 2.3 搭建主从集群2.3.1 配置master主服务2.3.2 配置slave从服务主…

Radzen Blazor Studio 1.12 Crack

Radzen Blazor Studio 是一款桌面工具,使 开发人员 能够创建精美的商业 Blazor 应用程序。快速地。 开放技术栈 没有供应商锁定。生成的源代码是人类可读的,您可以使用免费工具构建它。 Radzen 由流行的开源技术 - ASP.NET Core、Blazor、Bootstrap 提供…

较少的分区也报错too many range table entries

问题现象 postgresql中update执行语句报错too many range table entries 源sql with t as (select id from LZLTAB where id8723 limit 100 ) update LZLTAB setSTATUS 00,FILE_ID null,DATE_UPDATED localtimestamp(0) where id in (select id from t)如果把update改写成…

碳排放预测模型 | Python实现基于机器学习的碳排放预测模型——数据清理和可视化

文章目录 效果一览文章概述研究内容源码设计参考资料效果一览 文章概述 碳排放预测模型 | Python实现基于机器学习的碳排放预测模型——数据清理和可视化 研究内容 碳排放被认为是全球变暖的最主要原因之一。 该项目旨在提供各国碳排放未来趋势的概述以及未来十年的全球趋势预测…

三维空间刚体运动之旋转矩阵与变换矩阵

1. 旋转矩阵 1.1 点、向量和坐标系 点:点是空间中的基本元素,没有长度,没有体积; 向量:把两个点连接起来,就构成了向量,向量可以看成从某点指向另一点的一个箭头;只有当我们指定这…

threejs精灵和粒子系统

个人博客地址: https://cxx001.gitee.io 前面我们了解到了场景中的网格对象由几何体和材质组成,并且分别系统学习了它们。这节我们将学习一个特殊的网格对象-----粒子(精灵)。 了解粒子 一个粒子(新版叫精灵)是 一个二维平面(小方块) ,它总是面向摄像…

Linux--阅读文本指令:more、less

生成10000行数字并将其写入普通文件的指令&#xff1a; count0; while [ $count -le 10000 ]; do echo "hello bit ${count}"; let count; done > file.txt 直接cat < file.txt会刷屏的&#xff0c;故引入more 注&#xff1a;enter键控制下翻&#xff0c;q直…

拷贝对象,拷贝快乐:揭开JavaScript中拷贝的神奇面纱

文章目录 浅拷贝&#xff08;Shallow Copy&#xff09;1. 使用 Object.assign() 方法2. 使用展开运算符&#xff08;Spread Operator&#xff09;3. 使用数组的 slice() 或 concat() 方法 深拷贝&#xff08;Deep Copy&#xff09;1. 使用 JSON 对象的方法2. 使用递归方法自行实…

@validated的自定义注解校验编程式校验

自定义注解校验 前面的文章中&#xff0c;我们都是采用validate机制自带的条件注解来进行参数校验&#xff0c; 比如Min、NotNull…等等&#xff0c; 这些的确可以帮我们省去一部分的参数校验&#xff0c;可惜还有一部分的业务校验规则并不是如这般简单的&#xff0c; 比如前端…

C++ 程序设计入门

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…

mybatis web使用01

CREATE TABLE t_act ( id bigint auto_increment, actno varchar(255) NOT NULL, balance int(11) default 0, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 创建 maven web 工程 在 IntelliJ IDEA 上使用 Maven 创建 web 工程的步骤如下&#xff1a; 在…

机器学习-方差和偏差理论

机器学习-方差和偏差理论 关于机器学习方差和偏差的内容其实很重要&#xff0c;这个方差和偏差可以帮助我们去分析&#xff0c;模型的泛化能力和过拟合的程度。 下面我们先给存储方差和偏差的公式&#xff1a; 注意&#xff0c;下式当中&#xff0c; f ( x ; D ) 表示在数据集…

jmeter测并发

文章目录 jmeter是什么安装使用1、测试计划2、线程组3、添加请求4、添加报告开始测试 jmeter是什么 Apache JMeter是一个开源的负载测试工具&#xff0c;用于评估和分析系统的性能和功能。它可以模拟多种类型的负载&#xff0c;包括 Web 应用程序、数据库服务器、FTP 服务器等…