深入解析Linux虚拟化KVM-Qemu分析之virtio设备

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

说明

  1. KVM版本5.9.1
  2. QEMU版本5.0.0
  3. 工具Source Insight 3.5 Visio

1. 概述

先来张图

  • 图中罗列了四个关键模块Virtio Device、Virtio Driver、Virtqueue、Notificationeventfd/irqfd
  • Virtio Driver前端部分处理用户请求并将I/O请求转移到后端
  • Virtio Device后端部分由Qemu来实现接收前端的I/O请求并通过物理设备进行I/O操作
  • Virtqueue中间层部分用于数据的传输
  • Notification交互方式用于异步事件的通知

本文先从Qemu侧的virtio device入手我会选择从一个实际的设备来阐述没错还是上篇文章中提到的网络设备。

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

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

2. 流程分析

在Qemu的网卡虚拟化时通常会创建一个虚拟网卡前端和虚拟网卡后端如下图

  • 在虚拟机创建的时候指定参数-netdev tap, id = tap0 -device virtio-net-pci, netdev=tap0
  • 创建一个Tap网卡后端设备
  • 创建一个Virtio-Net网卡前端设备
  • 网卡前端设备和后端设备进行交互最终与Host的驱动完成数据的收发

全文围绕着Tap设备的创建和Virtio-Net设备的创建展开。

入口流程如下

  • Qemu的代码阅读起来还是比较费劲的各种盘根错节里边充斥着面向对象的思想先给自己挖个坑后续会专题研究的this is for you, you have my words.;
  • 图中与本文相关的有三个模块1模块初始化2网络设备初始化3设备初始化
  • Qemu中设备模拟通过type_init先编译进系统在module_call_init时进行回调比如图中的xxx_register_types在这些函数中都是根据TypeInfo类型信息来创建具体的实现
  • net_init_client用来创建网络设备比如Tap设备
  • device_init_func根据Qemu命令的传入参数创建虚拟设备比如Virtio-Net

下边进入细节the devil is in the details。

3. tap创建

从上文中我们知道Tap与Virtio-Net属于前后端的关系最终是通过结构体分别指向对方如下图

  • NetClientState是网卡模拟的核心结构表示网络设备中的几个端点两个端点通过peer指向对方

创建Tap设备的主要工作就是创建一个NetClientState结构并添加到net_clients链表中

函数的调用细节如下图

  • 处理流程只关注了核心的处理流程整个过程有很多关于传入参数的处理选择性忽略了
  • net_tap_init与Host的tun驱动进行交互其实质就是打开该设备文件并进行相应的配置等
  • net_tap_fd_init根据net_tap_info结构创建NetClientState并进行相关设置这里边net_tap_info结构体中的接收函数指针用于实际的数据传输处理
  • tap_read_poll用于将fd添加到Qemu的AioContext中用于异步响应当有数据来临时捕获事件并进行处理

以上就是Tap后端的创建过程下文将针对前端创建了。

4. virtio-net创建

这是一个复杂的流程。

4.1 数据结构

Qemu中用C语言实现了面向对象的模型用于对设备进行抽象精妙

针对Virtio-Net设备结构体及拓扑组织关系如下图

  • DeviceState作为所有设备的父类其中派生了VirtIODevice和PCIDevice而本文研究的Virtio-Net派生自VirtIODevice
  • Qemu中会虚拟一个PCI总线同时创建virtio-net-pcivirtio-balloon-pcivirtio-scsi-pci等PCI代理设备这些代理设备挂载在PCI总线上同时会创建Virtio总线用于挂载最终的设备比如VirtIONet
  • PCI代理设备就是一个纽带

4.2 流程分析

与设备创建相关的三个函数可以从device_init_func入口跟踪得知

  • 当Qemu命令通过-device传入参数时device_init_func会根据参数去查找设备并最终调用到该设备对应的类初始化函数、对象初始化函数、以及realize函数
  • 所以我们的分析就是这三个入口

4.2.1 class_init

  • 在网卡虚拟化过程中参数只需要指定PCI代理设备即可也就是-device virtio-net-pci, netdev=tap0从而会调用到virtio_net_pci_class_init函数
  • 由于实现了类的继承关系在子类初始化之前需要先调用父类的实现图中也表明了继承关系以及调用函数顺序
  • C语言实现继承也就是将父对象放置在自己结构体的开始位置图中的颜色能看出来

4.2.2 instance_init

类初始化结束后开始对象的创建

  • 针对Virtio-Net-PCI的实例化比较简单作为代理负责将它的后继对象初始化也就是本文的前端设备Virtio-Net

4.2.3 realize

  • realize的调用比较绕简单来说它的类继承关系中存在多个realize的函数指针最终会从父类开始执行一直调用到子类而这些函数指针的初始化在什么时候做的呢没错就是在class_init类初始化的时候进行了赋值细节不表结论可靠
  • 最终的调用关系就如图了

到目前为止我们似乎都还没有看到Virtio-Net设备的相关操作不用着急已经很接近真相了

  • virtio_net_pci_realize函数会触发virtio_device_realize的调用该函数是一个通用的virtio设备实现函数所有的virtio设备都会调用而我们的前端设备Virtio-Net也是virtio设备
  • virtio_net_device_realize就到了我们的主角了它进行了virtio通用的设置后续在数据通信中再分析还创建了一个NetClientState端点与Tap设备对应分别指向了对方惺惺相惜各自安好
  • virtio_bus_device_plugged表示设备插入总线时的处理完成的工作就是按照PCI总线规划配置各类信息以便与Guest OS中的virtio驱动交互后续的文章再分析了

 

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux