Virtio的代码主要分两个部分:QEMU和内核驱动程序。Virtio设备的模拟就是通过QEMU完成的,QEMU代码在虚拟机启动之前,创建虚拟设备。虚拟机启动后检测到设备,调用内核的virtio设备驱动程序来加载这个virtio设备。

对于KVM虚拟机,都是通过QEMU这个用户空间程序创建的,每个KVM虚拟机都是一个QEMU进程,虚拟机的virtio设备是QEMU进程模拟的,虚拟机的内存也是从QEMU进程的地址空间内分配的。

VRING是由虚拟机virtio设备驱动创建的用于数据传输的共享内存,QEMU进程通过这块共享内存获取前端设备递交的IO请求。

如下图所示,虚拟机IO请求的整个流程:

1) 虚拟机产生的IO请求会被前端的virtio设备接收,并存放在virtio设备散列表scatterlist里;

2) Virtio设备的virtqueue提供add_buf将散列表中的数据映射至前后端数据共享区域Vring中;

3) Virtqueue通过kick函数来通知后端qemu进程。Kick通过写pci配置空间的寄存器产生kvm_exit;

4) Qemu端注册ioport_write/read函数监听PCI配置空间的改变,获取前端的通知消息;

5) Qemu端维护的virtqueue队列从数据共享区vring中获取数据

6)  Qemu将数据封装成virtioreq;

7)  Qemu进程将请求发送至硬件层。

前后端主要通过PCI配置空间的寄存器完成前后端的通信,而IO请求的数据地址则存在vring中,并通过共享vring这个区域来实现IO请求数据的共享。

从上图中可以看到,Virtio设备的驱动分为前端与后端:前端是虚拟机的设备驱动程序,后端是host上的QEMU用户态程序。为了实现虚拟机中的IO请求从前端设备驱动传递到后端QEMU进程中,Virtio框架提供了两个核心机制:前后端消息通知机制和数据共享机制。

消息通知机制,前端驱动设备产生IO请求后,可以通知后端QEMU进程去获取这些IO请求,递交给硬件。

数据共享机制,前端驱动设备在虚拟机内申请一块内存区域,将这个内存区域共享给后端QEMU进程,前端的IO请求数据就放入这块共享内存区域,QEMU接收到通知消息后,直接从共享内存取数据。由于KVM虚拟机就是一个QEMU进程,虚拟机的内存都是QEMU申请和分配的,属于QEMU进程的线性地址的一部分,因此虚拟机只需将这块内存共享区域的地址传递给QEMU进程,QEMU就能直接从共享区域存取数据。

PCI配置空间

由整体流程图可知,guest和host交互传送信息的两个重要结构分别的PCI config和vring,本节重点分析实现消息通知机制的PCI配置空间。

虚拟机是如何获取PCI配置空间的?

首先,我们为虚拟机创建的virtio设备都是PCI设备,它们挂在PCI总线上,遵循通用PCI设备的发现、挂载等机制。

当虚拟机启动发现virtio PCI设备时,只有配置空间可以被访问,配置空间内保存着该设备工作所需的信息,如厂家、功能、资源要求等,通过对这个空间信息的读取,完成对PCI设备的配置。同时配置空间上有一块存储器空间,里面包含了一些寄存器和IO空间。

前后端的通知消息就是写在这些存储空间的寄存器,virtio会为它的PCI设备注册一个PCI BAR来访问这块寄存器空间。配置空间如下图所示:

虚拟机系统在启动过程中在PCI总线上发现virtio-pci设备,就会调用virtio-pci的probe函数。该函数会将PCI配置空间上的寄存器映射到内存空间,并将这个地址赋值给virtio_pci_device的 ioaddr 变量。之后要对PCI配置空间上的寄存器操作时,只需要ioaddr+偏移量。

pci_iomap函数完成PCI BAR的映射,第一个参数是pci设备的指针,第二个参数指定我们要映射的是0号BAR,第三个参数确定要映射的BAR空间多大,当第三个参数为0时,就将整个BAR空间都映射到内存空间上。VirtioPCI设备的0号BAR指向的就是配置空间的寄存器空间,也就是配置空间上用于消息通知的寄存器。

通过pci_iomap之后,我们就可以像操作普通内存一样(调用ioread和iowrite)来读写pci硬件设备上的寄存器。

虚拟机是如何操作这个配置空间的?

1. kick
当前端设备的驱动程序需要通知后端QEMU程序执行某些操作的时候,就会调用kcik函数,来触发读写PCI配置空间寄存器的动作。
2. 读写PCI寄存器
ioread/iowrite实现了对配置空间寄存器的读写,例如:

vp_dev->ioaddr + VIRTIO_PCI_QUEUE_NOTIFY 表示写notify这个寄存器,位置如图 2 1所示。

ioread读取QEMU端在配置空间寄存器上写下的值。
在读写PCI设备配置空间的操作中,我们可以看到都是通过iodaar+偏移,来指向某个寄存器,ioaddr这个变量是我们在Virtio-pci设备初始化的时候对它赋值,并指向配置空间寄存器的首地址位置。

QEMU如何感知虚拟机的操作的?

虚拟机内调用kick函数实现通知之后,会产生KVM_EXIT。Host端的kvm模块捕获到这个EXIT之后,根据它退出的原因来做处理。如果是一个IO_EXIT,kvm会将这个退出交给用户态的QEMU程序来完成IO操作。
QEMU为kvm虚拟机模拟了virtio设备,因此后端的virtio-pci设备也是在QEMU进程中模拟生成的。QEMU对模拟的PCI设备的配置空间注册了回调函数,当虚拟机产生IO_EXIT,就调用这些函数来处理事件。

这里只分析legacy模式,其实在初始化阶段guest会判断设备是否支持modern模式,如果支持,回调函数会发生一些变化。挖个坑有时间以后补。
1. 监听PCI寄存器
virtio_ioport_write/read就是QEMU进程监听PCI配置空间上寄存器消息的函数,针对前端iowrite/ioread读写了哪个PCI寄存器,来决定下一步操作:

2. 监听函数的注册
PCI寄存器的这些监听函数,都是在QEMU为虚拟机创建虚拟设备的时候注册。

QEMU先为虚拟机的virtio-pci设备创建PCI配置空间,配置空间内包含了设备的一些基本信息;在配置空间的存储空间位置注册了一个PCI BAR,并为这个BAR注册了回调函数监听寄存器的改变。

这部分代码是初始化配置空间的基本信息。  

给PCI设备注册了PCI BAR,指定起始地址为PCI_BASE_ADDRESS_SPACE_IO(即PCI配置空间中存储空间到配置空间首地址的偏移值);

指定这个BAR的大小为size,回调函数为virtio_pci_config_ops中的读写函数。

这里的read/write最终都会调用virtio_ioport_write(virtio_ioport_write处理前端写寄存器时触发的事件,virtio_ioport_read处理前端要读寄存器时触发的事件)来统一的管理。

VIRTIO PCI 设备相关推荐

  1. QEMU如何虚拟PCI设备

    引子 PCI(PCIE)设备在PC架构中有着举足轻重的地位,了解PCI总线与PCI设备在QEMU中的工作机制有助于我们了解CPU和设备之间的沟通机制,会对PC系统有更全面的认知,同时对virtio设备 ...

  2. PCIe配置空间和PCI设备中的寄存器

    1.访问PCI配置空间,PCI基本配置空间的读写使用下列函数: 原型定义在<linux/pci.h> int pci_read_config_byte(struct pci_dev *pd ...

  3. linux驱动 pcie 框架_Linux PCI 设备驱动基本框架(二)

    针对相应设备定义描述该PCI设备的数据结构: structdevice_private {/*注册字符驱动和发现PCI设备的时候使用*/ struct pci_dev *my_pdev;// stru ...

  4. Pci设备驱动:设备枚举

    有了设备模型基础及usb设备驱动的基础知识,来了解PCI设备驱动,就相对简单了,因为PCI设备驱动仍然套用了设备驱动模型的方式,用到的仍然是设备模型的相应函数,只是把相应的pci设备挂载到PCI总线的 ...

  5. DPDK pci设备初始化(十七)

    一.pci设备背景知识 传统的sata,是一种 半双工设备, 同一时间只能有一个方向在传输数据,传输速率就比较慢了.pci设备是一种全双工设备, 同一时间可以发送数据到其他pci设备,也可以接收来自其 ...

  6. linux设备驱动之pci设备的驱动架构

    驱动工程师最关心就是如何编写PCI设备驱动了. 经过前面的处理,所有设备及其信息都已经遍历出来了.在深入分析PCI驱动架构之前,我们来回顾一下前面遍历PCI设备时,对pci_dev->dev的一 ...

  7. Linux PCI 设备驱动基本框架(一)

    Linux将所有外部设备看成是一类特殊文件,称之为"设备文件",如果说系统调用是Linux内核和应用程序之间的接口,那么设备驱动程序则可以看成是 Linux内核与外部设备之间的接口 ...

  8. linux pcie命令,setpci命令_Linux setpci 命令用法详解:查询和配置PCI设备的使用工具...

    setpci命令是一个查询和配置PCI设备的使用工具. 语法setpci(选项)(参数) 选项-v:显示指令执行的细节信息: -f:当没有任何操作需要完成时,不显示任何信息: -D:测试模式,并不真正 ...

  9. Linux内核机器ID,linux-如何强制内核重新读取/重新初始化PCI设备ID?

    我的机器(正在运行Linux内核3.2.38的计算机)在引导时具有错误的PCI设备的子系统ID(子设备和子供应商ID).如果我然后在系统仍处于启动状态(即热插拔)时物理地拔出PCI设备并重新插入,则它 ...

最新文章

  1. 单轴步进驱动模块SH-20403
  2. Object.entries(obj)
  3. 不生成Android Dependencies
  4. additive tree
  5. Sublime Text官方文档 中英文版本
  6. 安卓手机绘制uml图_Android Studio中绘制simpleUML类图详细说明及使用
  7. java需要会的工具_Java开发者必备的几款工具,一定要掌握!
  8. 人脸识别sdk_开发实录:免费人脸识别SDK实现人证比对全过程
  9. 如何在ogre游戏界面添加自己的信息,比如显示某变量的值
  10. Linux UIO 驱动模型
  11. three.js实现球体地球城市模拟迁徙
  12. id3-sklearn算法实现(西瓜数据集)
  13. 小程序input获得焦点触发_小程序文本框焦点 小程序input自动获取焦点
  14. 搜狗浏览器怎么打开html文件在哪里,搜狗浏览器的文件菜单打开文件在哪
  15. VirtualDub
  16. 如何将PDF文档进行翻译?PDF文档翻译简单方法介绍
  17. 第二章 pandas基础
  18. python while循环和for循环转换_Python的While循环和for循环,python,while
  19. BugKu -- never give up
  20. Integer 用法

热门文章

  1. 畅游CTO洪晓健:BW和CE3游戏引擎各取所长
  2. 使用openfire开发插件
  3. require()------node js
  4. linux下安装 postgresql 14
  5. 【VBA编程】VBA基础语法(二)
  6. TSINGSEE车载视频监控技术在城市公交场景中的应用
  7. 关于Centos7 下df -h 无响应
  8. itol绘制高颜值的进化树
  9. RobotSim机器人快速成型-离线编程仿真软件
  10. 【matlab图像处理】理想低通滤波器