|
|
|
|
公众号矩阵

Linux虚拟化KVM-Qemu分析之Vhost-Net

结构体的核心围绕着数据和通知机制,其中数据在vhost_virtqueue中体现,而通知主要是通过vhost_poll来实现,具体的细节下文将进一步描述。

作者:LoyenWang来源:LoyenWang|2021-05-07 06:42

 

本文转载自微信公众号「LoyenWang」,作者LoyenWang。转载本文请联系LoyenWang公众号。

背景

  • Read the fucking source code! --By 鲁迅
  • A picture is worth a thousand words. --By 高尔基

说明:

  • KVM版本:5.9.1
  • QEMU版本:5.0.0
  • 工具:Source Insight 3.5, Visio
  • 文章同步在博客园:https://www.cnblogs.com/LoyenWang/

1. 概述

让我们先来看看问题的引入,在之前的virtio系列文章中,网络虚拟化的框架如下图所示:

  • Qemu中的virtio-net设备数据包收发,通过用户态访问tap设备完成的;
  • 收发过程涉及Guest OS,KVM,Qemu中的virtio-net设备,Host中的网络协议栈等的交互,路径长并且涉及的切换多,带来了性能的损耗;
  • vhost-net的引入,就是将vitio-net后端设备的数据处理模块下沉到Kernel中,从而提高整体的效率;

vhost-net的框架图如下:

  • 从图中可以看出,Guest的网络数据交互直接可以通过vhost-net内核模块进行处理,而不再需要从内核态切换回用户态的Qemu进程中进行处理;
  • 之前的文章分析过virtio设备与驱动,针对数据传遵循virtio协议,因此vhost-net中需要去实现virtqueue的相关机制;

本文将分析vhost-net的原理,只说重点,进入主题。

2. 数据结构

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来实现,具体的细节下文将进一步描述。

3. 流程分析

3.1 初始化

vhost-net为内核模块,注册为misc设备,Qemu通过系统调用接口与内核交互,Qemu中的初始化如下图:

  • Qemu中tap设备初始化在net_init_tap中完成,其中net_init_tap_one打开vhost-net设备文件,用于与内核的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.2 数据发送

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.3 数据接收

数据的接收是发送的逆过程,流程一致:

1)

初始化部分与发送过程一致;

Tap/Tun驱动从NIC接收到数据包,准备发送给vhost-net;

2)

  • 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中进行处理;

vhost-net的整体内容较多,从上到下涉及到的细节很繁琐,短短的一篇文章难以涵盖全部,权当给个轮廓了。

暂且告一段落吧。

参考

Introduction to virtio-networking and vhost-net

Deep dive into Virtio-networking and vhost-net

Vhost-net Device IOTLB

【编辑推荐】

  1. 这才是真的码“农”!Linux基金会要推广开源技术种菜了
  2. 看了这篇还不会Linux性能分析和优化,你来打我
  3. Linux实时补丁即将合并进Linux 5.3
  4. 嵌入式Linux文件与串口编程视频课程
  5. Vsphere 7.0企业级服务器虚拟化
【责任编辑:武晓燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

订阅专栏+更多

数据湖与数据仓库的分析实践攻略

数据湖与数据仓库的分析实践攻略

助力现代化数据管理:数据湖与数据仓库的分析实践攻略
共3章 | 创世达人

8人订阅学习

云原生架构实践

云原生架构实践

新技术引领移动互联网进入急速赛道
共3章 | KaliArch

40人订阅学习

数据中心和VPDN网络建设案例

数据中心和VPDN网络建设案例

漫画+案例
共20章 | 捷哥CCIE

234人订阅学习

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊

51CTO服务号

51CTO官微