virtio-net 实现机制【一】(图文并茂)

news2024/11/29 22:40:45

1. 基于virtio的半虚拟化概述

1.1 virtio运行结构

① virtio表示虚拟化IO,用于实现设备半虚拟化,即虚拟机中运行的操作系统需要加载特殊的驱动(e.g. virtio-net)且虚拟机知道自己是虚拟机

相较于基于完全模拟的全虚拟化,基于virtio的半虚拟化可以提升设备访问性能

② 运行在虚拟机中的部分称为前端驱动,负责对虚拟机提供统一的接口

③ 运行在宿主机中的部分称为后端驱动,负责适配不同的物理硬件设备

1.2 virtio架构层次

1.2.1 virtio前端驱动

① 运行在虚拟机中

② 针对不同类型的设备有不同的驱动程序,但是与后端驱动交互的接口都是统一的

③ 本文分析virtio-net模块,源码位于drivers/net/virtio_net.c

1.2.2 virtio层

① virtio层实现虚拟队列接口,作为前后端通信的桥梁

② 不同类型的设备使用的虚拟队列数量不同,比如virtio-net使用两个队列,一个用于接收,另一个用于发送

③ 源码位于drivers/virtio/virtio.c

1.2.3 virtio-ring层

① virtio-ring层是虚拟队列的具体实现

② 源码位于driver/virtio/virtio_ring.c

1.2.4 virtio后端驱动

① 运行在宿主机中

② 实现virtio后端的逻辑,主要是操作硬件设备,比如向内核协议栈发送一个网络包完成虚拟机对于网络的操作

③ 在Qemu + KVM虚拟化环境中,源码位于Qemu源码中(尚未详细分析)。后续将分析seL4中的后端实现

【文章福利】小编在群文件上传了一些个人觉得比较好得学习书籍、视频资料,有需要的可以进群【977878001】领取!!!额外赠送一份价值699的内核资料包(含视频教程、电子书、实战项目及代码)

内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料

学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈

2. Linux virtio核心数据结构

2.1 virtio_bus结构

struct bus_type是基于总线驱动模型的公共数据结构,定义新的bus,就是填充该结构。virtio_bus定义在drivers/virtio/virtio.c中,具体如下,

说明1:注册virtio_bus

virtio_bus以core_initcall的方式被注册,该方式注册的启动顺序优先级很高(作为对比,module_init最终对应的是device_initcall),在使用中要注意不同组件的启动顺序

说明2:virtio_dev_match函数

virtio驱动的match涉及到virtio_device_id结构,在virtio_device结构中包含该结构;在virtio_driver中则是包含该驱动支持的virtio_device_id列表

具体的match流程如下,

可见virtio驱动的match函数先匹配device字段,后匹配vendor字段,二者都满足条件时match成功

补充:从virtio_dev_match的流程可以看出,virtio_driver中的id_table必须以id->device = 0结尾,以便结束循环

2.2 virtio_device结构

struct virtio_device定义在include/linux/virtio.h中,具体如下,

重点说明如下4个字段,

2.2.1 struct virtio_device_id id

其中的device成员标识了当前virtio_device的用途,virtio-net是其中的一种,

2.2.2 const struct virtio_config_ops *config

virtio_config_ops操作集中的函数主要与virtio_device的配置相关,主要有如下2类操作,

① 实例化 / 反实例化virtqueue,其中要特别注意find_vqs函数,该函数用于实例化virtio_device所持有的virtqueue

②. 获取 / 设置virtio_device的属性与状态,相关属性均在虚拟机虚拟出的PCI配置空间

2.2.3 struct list_head vqs

virtio_device持有的virtqueue链表,virtio-net中建立了2条virtqueue(虚拟队列)

2.2.4 u64 features

virtio_driver & virtio_device同时支持的通信特性,也就是前后端最终协商的通信特性

2.3 virtio_driver结构

struct virtio_driver定义在include/linux/virtio.h中,具体如下,

重点说明如下4个字段,

2.3.1 const struct virtio_device_id *id_table

对应virtio_device结构中的id成员,virtio_device中标识的是当前device的id属性;而virtio_driver中的id_table则是当前driver支持的所有id列表

2.3.2 const unsigned int *feature_table & unsigned int feature_table_size

feature列表包含了当前driver支持的所有virtio传输属性,feature_table_size则说明属性数组的元素个数

2.3.3 probe函数

virtio_driver层面注册的probe函数,如上文所述,virtio_bus层面也注册了probe函数,在Linux总线驱动框架 & virtio核心层中,当virtio_device & virtio_driver匹配成功后,先调用bus层面的probe函数,在virtio_bus层面的probe函数中,又调用了virtio_driver层面的probe函数

2.4 virtqueue结构

2.5 vring结构

2021 08 / 06补充:一定要结合一个后端驱动进行分析,可以对照rpmsg-lite分析

可以就分析rpmsg-lite对virtqueue的操作部分,不用上升到rpmsg协议的部分

说明1:vring的三个构成区域

① Destcriptor Table:描述内存buffer,主要包括addr & len等信息

② Avail Ring:用于前端驱动(Guest)通知后端驱动(Host)有可用的描述符

e.g. 前端驱动有一个报文需要发送,需要将其加入Avail Ring,之后通知后端驱动读取

③ Used Ring:用于后端驱动(Host)通知前端驱动(Guest)有可用的描述符,或者是后端驱动已将前端驱动提供的描述符使用完毕

e.g. 后端驱动有一个报文需要发送,需要将其加入Used Ring,之后通知前端驱动读取

可见avail & used的命名都是站在Host的角度进行的

说明2:vring的存储

vring结构只是用于描述vring在内存中的布局(因此包含的都是指针变量),实际用于通信的vring是存储在内存中

上文提到的vring的三个区域是在内存中连续存储的,而且是存储在Guest & Host共享的一片连续内存中

我们可以通过vring_init函数理解vring存储结构的布局

实际vring的内存布局如下图所示,

在计算used ring的起始地址时,在avail->ring[num]的地址之后又加了sizeof(__virtio16),也就是增加了2B,是为了容纳avail ring末尾的used_event,该机制详见下文(是一种控制中断触发频率的机制)

说明3:实际vring的大小

实际vring的大小可以通过vring_size函数获得

① 计算avail ring时加3,分别为flags、idx和used_event

② 计算used ring时加3,分别为flags、idx和avail_event

③ 计算过程中,包含了为满足对齐要求padding的空间

说明4:used_event与avail_event机制概述

这2个字段均与virtio设备的VIRTIO_RING_F_EVENT_IDX特性有关,由于virtio驱动触发对方中断将导致CPU反复进出虚拟机 & 宿主机模式,从而降低性能,因此需要控制触发中断频率的机制

① avail ring中的used_event

a. 由前端驱动(Geust)设置,标识希望后端驱动(Host)触发中断的阈值

b. 后端驱动(Host)在向Used Ring加入buffer后,检查Used Ring中的idx字段,只有达到阈值才触发中断

② used_ring中的avail_event

a. 由后端驱动(Host)设置,标识希望前端驱动(Guest)触发中断的阈值

b. 前端驱动(Guest)在向Avail Ring加入buffer后,检查Avail Ring的idx字段,只有达到阈值才触发中断

综上所属,vring结构的构成如下图所示,

2.6 vring_virtqueue结构

vring_virtqueue结构用于描述前端驱动(Guest)中的一条虚拟队列

总结:virtio_device / vring_virtqueue / virtqueue / vring结构之间的关系

3. virtio操作

如上文所述,virtio框架向虚拟机中的前端驱动提供了统一的IO操作接口,我们下面就分析这些操作

在理解了virtio的操作之后,配合不同虚拟设备的属性,就比较容易理解虚拟设备前端驱动的实现。比如virtio-net就是网卡驱动 + virtio操作

3.1 创建virtqueue

3.1.1 核心流程梳理(重点)

3.1.1.1 virtio_pci_probe阶段

在virtio框架中,首先向虚拟机注册的是一个pci_driver,后续所有vitio设备均是以虚拟pci设备的形式注册,因此第1步流程即是运行virtio_pci_probe函数

3.1.1.2 virtio_pci_modern_probe阶段

在virtio_pci_probe函数中,会调用virtio_pci_modern_probe函数,进行进一步初始化

在virtio_pci_modern_probe函数中会进行2步非常重要的操作,

① 设置virtio_device中的virtio_config_ops回调函数集合,其中就包括了最重要的find_vqs回调函数

② 设置setup_vq回调函数,该回调函数会在find_vqs回调函数中被调用

所以在初始化virtqueue的过程中,是先调用find_vqs回调函数,后调用setup_vq回调函数

3.1.1.3 find_vqs回调函数阶段

在virtio_pci_probe的最后阶段,会注册virtio_device,该操作会触发virtio驱动的probe函数被调用,在该函数中,会触发find_vqs回调函数被调用

下面我们以virtio-net前端驱动为例,说明创建virtqueue的流程

 virtio_pci_probe
--> virtio_pci_modern_probe
    // 设置find_vqs回调函数
    // 设置setup_vq回调函数
--> register_virtio_device // 触发virtio probe函数被调用
  
// virtio probe函数阶段
virtio_dev_probe
    // 调用virtio_driver注册的probe函数
--> virtnet_probe
  --> init_vqs
        // 分配virtqueue结构(send_queue & recv_queue)
    --> virtnet_alloc_queues
    --> virtnent_find_vqs
      --> find_vqs回调函数(vp_modern_find_vqs)
        --> vp_find_vqs
          --> vp_find_vqs_msix // 还注册了vring_interrupt
            --> vp_setup_vq
              --> setup_vq回调函数(setup_vq)
                --> vring_create_virtqueue
                      // 分配存储vring的连续物理内存
                  --> vring_alloc_queue
                      // 生成vring_virtqueue结构,并初始化
                  --> __vring_new_virtqueue

下面我们就分析最核心的几个函数

3.1.2 setup_vq函数分析

setup_vq函数有如下3个核心步骤,

3.1.2.1 检查virtqueue配置参数

前端驱动读取PCI配置空间中的参数,判断其合法性

其中要注意virtqueue的长度(queue_size)必须是2的幂次,因为后续需要通过简单的位与运算实现绕回

3.1.2.2 实际生成virtqueue

实际生成virtqueue通过vring_create_virtqueue函数实现,此处需要注意如下3点,

① 对齐要求

如上文所述,vring结构在内存布局上有对齐要求,该要求在创建virtqueue时传递,就是此处的SMP_CACHE_BYTES宏

② notify hook函数

notify hook函数用于前端驱动(Guest)触发后端驱动(Host)中断,通知其有数据需要处理

这里的notification register也在PCI配置空间中,该地址在setup_vq函数中指定

③ callback hook函数

callback hook函数在virtqueue被触发时调用,以virtio-net驱动为例,callback hoot函数在virtnet_find_vqs函数中指定

3.1.2.3 同步GPA到宿主机

virtqueue作为前端驱动与后端驱动的交互媒介,需要在虚拟机和宿主机中同步这段共享内存的地址

调用vring_create_vritqueue函数生成的virtqueue,分配的内存为GPA,需要将其同步到宿主机,宿主机才能将其转换为HVA使用(因为虚拟机的GPA就是宿主机分配的)

3.1.3 vring_create_virtqueue函数分析

可见vring的内存被分配在连续的1个或多个page中,而且如果内存条件不满足,会动态调整vring的长度(num变量)

3.1.4 __vring_new_virtqueue函数分析

__vring_new_virtqueue函数的实现注释已经比较清楚了,需要说明的是,该函数返回的是virtqueue结构。在Linux的virtio层实现中,代码会根据需要在virtqueue与vring_virtqueue结构间进行转换

3.2 前端驱动发送数据

3.2.1 流程概要

step 1:从descriptor table中取出描述符

step 2:根据要发送的数据填充并组织描述符

step 3:将组织好的描述符加入avail ring

step 4:触发后端驱动中断,通知其处理数据

说明:vring的描述符结构与scatterlist结构是绝配

3.2.2 virtqueu_add函数分析

前端驱动发送数据的核心为virtqueue_add函数,下面给出该函数的分析

上面的截图很壮观,下面通过一张图展现该过程,

说明1:可见virtqueue_add函数封装了一次数据请求,所有out & in请求均组织为一个decriptor chain提交到avail ring

从上文分析可见,如果将out & in数据请求组织在一起,将使得接收端的处理逻辑非常复杂。因此在实际使用中(e.g. virtio-net,rpmsg),一般为out & in的数据请求单独建立virtqueue,即输入和输出使用不同的虚拟队列

说明2:由上图可知,descriptor table以静态链表的方式管理,因此空闲链表中各个描述符在物理上不一定是连续的,而是依靠描述符中的next域维护链接关系

说明3:对virtqueue_add函数的使用

virtqueue_add函数被封装为如下4种方式供前端驱动调用,

① virtqueue_add_sgs

可以同时提交out & in数据请求,且个数可设置

② virtqueue_add_outbuf

只提交一个out数据请求

③ virtqueue_add_inbuf

只提交一个in数据请求

④ virtqueue_add_inbuf_ctx

只提交一个in数据请求,且携带上下文信息

注意:ctx上下文信息与inderect特性是互斥的

3.3 前端驱动触发中断

前端驱动通过virtqueue_kick函数通知后端驱动有数据需要处理

其中virqueue_kick_prepare函数判断是否需要触发中断,virtqueue_notify函数实际触发中断

3.3.1 virtqueue_kick_prepare函数分析

说明:vring_need_event函数实现

只有当event_idx在[old, new - 1]范围时,才会允许触发中断

3.3.2 virtqueue_notify函数分析

virtqueue_notify会调用上文介绍的notify回调函数,实现对后端驱动的通知,在本文环境中,该回调函数为vp_notify函数

3.4 前端驱动被触发中断

3.4.1 注册中断处理函数

在创建virtqueue时,会为每条virtqueue注册中断,可参考vp_find_vqs_msix函数

可见中断处理函数为vring_interrupt,注意这里注册的是msi中断

3.4.2 vring_interrupt函数分析

vring_interrupt函数的核心操作是调用创建virtqueue时注册的callback回调函数,以virtio-net模块为例,接收和发送队列注册的callback回调函数如下

3.5 前端驱动接收数据

3.5.1 virtqueue_get_buf_ctx函数分析

说明:virtqueue_get_buf_ctx函数的返回值为vq->desc_state[i].data,该值在调用virtqueue_add时设置

virtqueue_add写入该值,目的就是用于索引buffer(the token identifying the buffer)

3.5.2 补充:对vring_desc_state desc_state结构中data成员的使用

3.5.2.1 在vring_virtqueue结构中定义

如上文所述,在vring_virtqueue结构中定义了desc_state数组,根据注释,该结构描述了每个描述符的状态(更好的理解是每个描述符有一个)

数组大小为virtqueue大小,空间随vring_virtqueue结构一同分配,该数组用于存储每次数据传输请求的上下文

vring_desc_state结构如下,

我们这里就是讨论其中data成员的使用

3.5.2.2 在virtqueue_add函数中设置

这里注意2点,

① 填入data的值

此处填入的值为virtqueue_add函数的入参data

② 填入desc_state数组的下标

此处使用的下标为head,为本次数据请求的chain descriptor的首个描述符下标

3.5.2.3 在virtqueue_get_buf_ctx中读取

此处使用的下标i是used ring中取出的chain descriptor中首个描述符的下标,这里对应了一次vritqueue_add加入的数据请求

此处就将当时virtqueue_add函数写入的data作为返回值

说明:这里就可以看出virtio机制设计的巧妙之处,后端驱动在使用不同的chain descriptor后不需要按取出的顺序归还

这里有2点机制上的保障,

① descriptor table使用静态链表方式管理

② desc_state数组按描述符管理

3.5.3 detach_buf函数分析

最终给出一张图,就是虚拟机和宿主机指向同一段内存,以实现二者之间的交互

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

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

相关文章

PR-视频加介绍背景

每天一个PS/PR小技巧(原理实践) 比如我们有一个展示视频,我们希望在视频一开始时添加一个介绍背景(纯色背景): 点击新建->颜色遮罩: 定义名称,选择颜色,然后就能在工…

Bug: conda环境与jupyter notebook kernel核环境不一致

问题 一般在服务器上进行环境安装的时候有多种方式,比如docker, conda等。conda肯使用起来更加简便,docker更适合服务器部署的时候使用。 本文记录在使用conda时候出现的问题,jupter notebook中的环境不一致导致的。 首先conda创建环境 co…

蓝桥杯嵌入式第二篇配置按键

文章目录前言一、原理图查看二、cubeMX配置三、代码讲解(使用按键控制LED灯的开关)四、HAL_Delay的内部实现五.遗留的问题总结前言 点完灯后接下来我们就开始按键的学习了,这也是很简单的,大家不用担心。 一、原理图查看 可以看到板子上面是有4个按键…

定时任务多线程-springboot

定时任务 在项目开发过程中,经常需要定时任务来帮助我们实现某些业务功能,比如定时生成数据报表、生成对账单、订单超时处理等。Spring Boot提供了内置的Scheduled注解实现定时任务的功能。 步骤 1.修改启动类 在启动类上加上EnableScheduling开启定时…

华为数字化转型之道 方法篇 第五章 视IT为投资,用产品思维管理IT

第五章 视IT为投资,用产品思维管理IT 5.1 数字时代IT系统的重新定位 比较传统信息化和数字化转型下的IT系统特征,我们发现业务环境、IT能力、业务和IT的关系都发生了巨大的变化(见图5-1) 从“管理系统”到“作业平台” 传统信息化下的IT系统往往侧重于信息记录、流程固化…

LeetCode994. 腐烂的橘子(C++中等题)

题目 在给定的 m x n 网格 grid 中,每个单元格可以有以下三个值之一: 值 0 代表空单元格; 值 1 代表新鲜橘子; 值 2 代表腐烂的橘子。 每分钟,腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。 返回 直到单元格…

因子模型:协方差矩阵

本文是Quantitative Methods and Analysis: Pairs Trading此书的读书笔记。 因子协方差矩阵(factor covariance matrix)在计算风险的时候很重要。如果一个模型有个因子,那么协方差矩阵的大小就是。对角线元素是每个因子的方差,非…

[附源码]Python计算机毕业设计SSM流浪动物管理系统(程序+LW)

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

用Python来开发安卓程序:(1)BeeWare安卓开发环境的搭建

文章目录1. 前言2. BeeWare简介3. 开发环境搭建注意事项4. 安装BeeWare5. 开发环境搭建5.1 创建项目5.1.1 创建一个新项目5.2 运行项目5.3 打包项目5.3.1 打包为Windows程序5.3.1.1 首先briefcase create安装应用的脚手架5.3.1.2 然后构建应用5.3.1.3 接着,运行构建…

2. JVM内存模型

1. JVM虚拟机内存模型图解 JAVA虚拟机主要由这三部分组成类装载子系统,字节码执行引擎,运行时数据区上一节我们不是学了类的加载吗,那些类加载器许多都是C帮我们做的,那么我们这个类装载子系统就是帮我们把类放入运行时数据区的&a…

[附源码]Python计算机毕业设计SSM浪漫烘焙屋(程序+LW)

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

原创|一个统计查询模块基于设计模式的抽象设计

文章目录一、需求背景二、详细设计UML设计包设计三、程序设计1、VideoAdStatCaliberEnum2、LiveDashboardBusiness3、StatHandleDispatcher4、StatCaliberEnum5、StatContext5、AbstractStatHandler6、LoggerService7、AbstractVideoAdStatHandler1、VideoAdStatContext2、Vide…

cpu设计和实现(协处理器cp0)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 除了通用计算器负责控制和计算之外,cpu如果需要正常有序地运行,还需要一定地协处理器来帮助完成对应地工作。在mips下面&am…

Vue实现流程图,借鉴vue-tree-color 实现流程框架技术

Vue实现流程图,借鉴vue-tree-color 实现流程框架技术 文章目录Vue实现流程图,借鉴vue-tree-color 实现流程框架技术借鉴鸣谢演示效果引入依赖添加全局组件的二次封装步骤1 创建组件目录Vuenode.jstree.less使用组件引入使用数据结构案例借鉴鸣谢 实现组…

[附源码]Python计算机毕业设计Django的桌游信息管理系统

项目运行 环境配置: Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术: django python Vue 等等组成,B/S模式 pychram管理等等。 环境需要 1.运行环境:最好是python3.7.7,…

元宇宙产业委调研行杭州站 | 联合西溪谷管委会共商元宇宙赋能实体经济

11月29日下午,由杭州西溪谷建设发展管理委员会和中国移动通信联合会元宇宙产业工作委员会共同举办的“元宇宙赋能实体经济企业家沙龙暨元宇宙产业委调研行杭州站”在西溪谷杭州蚂蚁链产业创新中心召开。20余家元宇宙和区块链企业到场,西溪谷管委会党委书…

pytorch复习笔记--loss.backward()、optimizer.step()和optimizer.zero_grad()的用法

目录 1--loss.backward()的用法 2--optimizer.step()的用法 3--optimizer.zero_grad()的用法 4--举例说明 5--参考 1--loss.backward()的用法 作用:将损失loss向输入测进行反向传播;这一步会计算所有变量x的梯度值 ,并将其累积为 进行备…

迎合国家新政策,共享购联合共享经济,三方互利,消费增值

共享单车打通出行“最后一公里”,共享充电宝让人们出门在外免于“电量烦恼”,共享办公降低办公成本……共享经济已深入到人们日常生活。近日,国家信息中心发布的《中国共享经济发展报告(2022)》显示,2021年…

EMR-Jindo Spark 核心引擎优化

Jindo-Spark 是阿里云智能E-MapReduce 团队在开源的Apache Spark 基础上自主研发的分布式云原生 OLAP 引擎,已经在近千E-MapReduce 客户中大规模部署使用。Jindo Spark 在开源版本基础上做了大量优化和扩展,深度集成和连接了众多阿里云基础服务。凭借该引…

工作流-流程实例【ProcessInstance】与执行实例【Execution】

一、ProcessInstance与Execution的区别 这是一个Activiti的难点,能够懂得这个,工作流也就入门大半了。 下面,我就细致的讲解一下他们的区别。 (1)首先,我们来看一张我总结的图片(这个图片中两条…