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

news2025/1/10 21:47:18

文章目录

  • 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框架图
在这里插入图片描述
在这里插入图片描述
host-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/701523.html

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

相关文章

深入理解Java虚拟机jvm-垃圾收集器日志参数

垃圾收集器日志参数 查看GC基本信息查看GC详细信息查看GC前后的堆、方法区可用容量变化查看GC过程中用户线程并发时间以及停顿的时间查看收集器Ergonomics机制(自动设置堆空间各分代区域大小、收集目标等内容,从Parallel收集器开始支持)自动调…

windows下nginx location root路径问题

Windows版本nginx的多级路径问题 windows 下 nginx location root路径为多级目录的问题解决方案:使用双反斜杠--\\Windows版本的nginx常用命令一览: windows 下 nginx location root路径为多级目录的问题 nginx的windows版本中location路径为多级目录 需…

Git客户端:Tower for Mac 注册版

Tower是一款Mac OS X系统上的Git客户端软件,它提供了丰富的功能和工具,帮助用户更加方便地管理和使用Git版本控制系统。以下是Tower的一些特点: 1. 界面友好:Tower的界面友好,使用户能够轻松地掌握软件的使用方法。 …

【vs2022】MSVCRTD.lib 4098: 默认库“libcmtd.lib”与其他库的使用冲突

verbose-print- 整理:warning LNK4098: 默认库“LIBCMT”与其他库的使用冲突;请使用 /NODEFAULTLIB:library 讲解的非常详细: Linker 加入命令行选项 To set this linker option in the Visual Studio development environment Open the p…

Chapter 1: Introduction - Why Program? | Python for Everybody 讲义_En

文章目录 Python for Everybody课程简介Python for Everybody (Why Program?)Why should you learn to write programs?Creativity and motivationComputer hardware architectureUnderstanding programmingWords and sentencesConversing with PythonTerminology: Interpret…

MyBatis全篇

文章目录 MyBatis特性下载持久化层技术对比 搭建MyBatis创建maven工程创建MyBatis的核心配置文件创建mapper接口创建MyBatis的映射文件测试功能加入log4j日志功能加入log4j的配置文件 核心配置文件的完善与详解MyBatis的增删改查测试功能 MyBatis获取参数值在IDEA中设置中配置文…

Java Web HTMLCSS(2)23.6.28

2,CSS 2.1 概述 CSS 是一门语言,用于控制网页表现。我们之前介绍过W3C标准。W3C标准规定了网页是由以下组成: 结构:HTML表现:CSS行为:JavaScript CSS也有一个专业的名字:Cascading Style Sh…

Springboot 如何自动上传秒杀商品数据到Redis中上架商品

一、概述 如下图秒杀活动: 在这个秒杀活动中,需要自动上架一定时间段的商品,我们如何实现自动上传呢? 我们可以通过定时任务来实现的。在秒杀活动开始前,需要将商品信息存储到数据库中,并设置好库存和价格…

Linux学习之进程的通信方式信号:kill命令

kill -l可以看到可以使用信号量。 把下边的内容使用编辑器,比如vim写到./a.sh。 #!/bin/bashecho $$ while : # 无限循环 do: donecat a.sh看一下文件里边的内容。 chmod ur,ux a.sh给当前用户赋予a.sh文件的写和执行权限。 在第一个端口里边,使用./a…

在 TypeScript 中有效地使用 keyof 和 typeof 来表示类型

在本文中,我们将学习如何通过组合类型运算符和枚举来提取和声明常量类型typeof,以使您的代码库得到优化。keyof 先决条件 为了获得更好的编码体验,您应该在 IDE 中安装 TypeScript,例如VSCode。它将为您提供许多基本功能&#xff…

Linux——进程通信之共享内存

目录 一. 回顾上文 二.共享内存 1.定义 2.特点: 3.实现步骤: 如下为成功链接共享内存使用权的完整步骤: 4.函数介绍 4.1shmget函数 4.1.2参数介绍 4.2ftok函数: 4.2.1参数介绍 关于ftok(); shmget();函数的代码实验…

基于当量因子法、InVEST、SolVES模型等多技术融合在生态系统服务功能社会价值评估中的应用

查看原文>>>基于当量因子法、InVEST、SolVES模型等多技术融合在生态系统服务功能社会价值评估中的应用及论文写作、拓展分析 本文将讲述用于评估生态系统服务价值的当量因子法、InVEST模型、SolVES模型及其原理,您将学会三种模型的原理与运行方法&#xf…

基于Docker的JMeter分布式压测

目录 前言: Docker Docker在JMeter分布式测试中的作用 Dockerfile用于JMeter基础: Dockerfile for JMeter Server / Slave: 总结 前言: 基于Docker的JMeter分布式压测是一种将JMeter测试分布在多个容器中进行的方法,可以提高…

《计算机系统2》学习笔记

目录 计算机系统漫游 Amdahl定理 信息的表示和处理 信息存储 进制转化 小端法 大端法 布尔代数 位级运算 逻辑运算 移位运算 整数表示 无符号数编码 补码编码 有符号数和无符号数之间的转换 扩展数的位表示 截断数字 整数运算 无符号加法 无符号数求反 有…

信号链噪声分析5

目录 概要 整体架构流程 技术名词解释 技术细节 小结 概要 提示:这里可以添加技术概要 残余相位噪声测量法消除了外部噪声源(例如电源或输入时钟)的影响,而绝对相位 噪声测量法包含了这些来源的噪声。残余相位噪声装置可以隔离并…

Upload靶场通关笔记

文章目录 一、Pass-011.抓包上传2.获取上传路径3.工具验证 二、Pass-02三、Pass-031.使用httpd.conf自定义后缀2.提取上传文件名3.工具测试4.注意点四、Pass-041.上传.htaccess2.上传图片3.工具测试 五、Pass-05六、Pass-061.空格.号绕过2.工具测试 七、Pass-07八、Pass-081.特…

联想黄莹:6G将是全智能应用下连接虚拟与现实世界的“超级通道”

6月28日,以“时不我待”为主题的MWC上海世界移动通信大会正式开幕。在当天下午举办的“6G愿景及关键推动力”大会论坛上,联想集团副总裁、联想研究院5G实验室负责人黄莹博士发表了“共铸辉煌:对6G技术和应用的思考与展望”主题演讲。他认为&a…

STM32F407 GPIO口输出配置配置步骤

STM32F407ZGT6 是意法半导体(STMicroelectronics)公司推出的一款高性能ARM Cortex-M4核心的32位微控制器(MCU)。它是 STM32F4 系列的一员,具备强大的处理能力和丰富的外设功能,适用于各种应用领域。 【1】…

3.6.6.异步SIGIO : fcntl(F_GETFL、F_SETFL、O_ASYNC、F_SETOWN) 3.6.7.存储映射IO

3.6.6.异步IO :SIGIO 3.6.6.1、何为异步IO (1)几乎可以认为:异步IO就是操作系统用软件实现的一套中断响应系统。 (2)异步IO的工作方法是:我们当前进程注册一个异步IO事件(使用signal注册一个信号SIGIO的处理函数)&…

【Django学习】(十)模型序列化器_关联字段序列化

这篇文章是针对模型类序列化器以及如何关联字段序列化 进行深入讲解的; class ProjectModelSerializer(serializers.ModelSerializer):email serializers.EmailField(write_onlyTrue)interfaces InterfaceModelSerializer(label所属接口的信息, help_text所属接口…