理解PCIE设备透传

PCIE设备透传解决的是使虚拟机直接访问PCIE设备的技术,通常情况下,为了使虚拟机能够访问Hypervisor上的资源,QEMU,KVMTOOL等虚拟机工具提供了"trap and emulate", Virtio半虚拟化等机制实现。但是这些实现都需要软件的参与,性能较低。

trap and emulate情况下,虚拟机每次访问硬件资源都要进行VMExit退出虚拟机执行相应的设备模拟或者访问设备的操作,完成后再执行VMEnter进入虚拟机。频繁的模式切换导致IO访问的低效。

而Virtio则是一种半虚拟化机制,要求虚拟机中运行的操作系统需要加载特殊的virtio前端驱动(Virtio-xxx),虚拟机通过循环命令队列和Hypervisor上运行的Virtio后端驱动进行通信,后端驱动负责适配不同的物理硬件设备,再收到命令后,后端驱动执行命令。

PCIE设备透传到底"透"了什么?

参考如下两篇文章搭建PCIE设备PASS-THROUGH的环境:

KVM虚拟化之小型虚拟机kvmtool的使用-CSDN博客

ubuntu18.04下pass-through直通realteck PCI设备到qemu-kvm虚拟机实践_kvm网卡直通-CSDN博客

透了HOST MEMORY

设备透传解决了让虚拟机中的驱动使用IOVA访问物理内存的问题,在KVMTOOL中,它是通过调用VFIO的VFIO_IOMMU_MAP_DMA 命令来实现的,用来将IOVA映射到具体的物理页面上(通过HVA 得到HVA对应的物理页面,再进行映射)。下图说明了一切问题:

0.映射SIZE为整个GPA大小,也就是虚拟机的整个物理内存。

1.kvm->ram_start和bank->host_addr相同,表示被映射的区域,VFIO驱动会通过bank->host_addr找到对应的PAGE页面。

2.iova为bank->guest_phys_addr,也就是虚拟机内的GPA。也就是说,IOMMU页表建立后,透传的设备驱动可以通过和CPU一致的物理地址,访问到真实的物理页面上(HPA),这样,从CPU和涉笔的角度,可以做大IOVA==GPA。

3.映射完成后,从虚拟机的角度来看,CPU看到的物理地址(GPA)和硬件看到的物理地址(IOVA)都通过各自的路径(前者通过EPT,后者通过IOMMU)访问同一个存储单元。

4. IOVA到HPA的映射通过HOST主机的VFIO驱动完成,VFIO驱动代码规模比较小,VFIO驱动的一个重要功能之一通过设备节点的方式,使用户态应用能够进行IOMMU映射,从这个角度来讲,VFIO是一个精简的IOMMU驱动和管理框架。

GPA和IOVA建立后的效果如下,设备和CPU通过相同的地址,就可以访问到同一个物理单元,这样虚拟机系统不需通过VMM就可以直接访问到设备,这就是设备“透传”的本质吧。

下面是一个演示设备透传的程序:

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <errno.h>
#include <linux/vfio.h>#define IOVA_DMA_MAPSZ  (1*1024UL*1024UL)
#define IOVA_START      (0UL)
#define VADDR           0x400000000000
// refer https://www.cnblogs.com/dream397/p/13546968.html
// container fd: the container provides little functionality, with all but a couple vrson and extension query interfaces.
// 1. first identify the group associated wth the desired device.
// 2. unbinding the device from the host driver and binding it to a vfio driver, then a new group would appear for the group as /dev/vfio/$group.
//    make sure all the devices belongs to the group are all need to do the unbind and binding operations or error will got for next group ioctl.
// 3. group is ready, then add to the caontainer by opening the vfio group character device and use VFIO_GROUP_SET_CONTAINER ioctl to add the group
//    fd to container.depending the iommu, multi group can be set to one container.
// 4. after group adding to container, the remaning ioctls became available. enable the iommu device access.now you can get each device belongs the
//    iommu group and get the fd.
// 5. the vfio device ioctls includes for describing the device, the IO regions, and their read/write/mmap operations, and others such as describing
//    and registering interrupt notificactions./** #1:echo vfio-pci > /sys/bus/pci/devices/0000:02:00.0/driver_override* #2:echo 10de 1d13 > /sys/bus/pci/drivers/vfio-pci/new_id*root@zlcao-RedmiBook-14:~# ls -l /dev/vfio/*总用量 0*crw------- 1 root root 243,   0 11月  8 12:40 12*crw-rw-rw- 1 root root  10, 196 11月  8 12:31 vfio*/int main(void)
{int container, group, device, i;void *maddr = NULL;struct vfio_group_status group_status = { .argsz = sizeof(group_status) };struct vfio_iommu_type1_info *iommu_info = NULL;size_t iommu_info_size = sizeof(*iommu_info);struct vfio_device_info device_info = { .argsz = sizeof(device_info) };struct vfio_iommu_type1_dma_map dma_map;struct vfio_iommu_type1_dma_unmap dma_unmap;container = open("/dev/vfio/vfio", O_RDWR);if (container < 0) {printf("%s line %d, open vfio container error.\n", __func__, __LINE__);return 0;}if (ioctl(container, VFIO_GET_API_VERSION) != VFIO_API_VERSION) {printf("%s line %d, vfio api version check failure.\n", __func__, __LINE__);return 0;}if (ioctl(container, VFIO_CHECK_EXTENSION, VFIO_TYPE1_IOMMU) == 0) {printf("%s line %d, vfio check extensin failure.\n", __func__, __LINE__);return 0;}group = open("/dev/vfio/9", O_RDWR);if (group < 0) {printf("%s line %d, open vfio group error.\n", __func__, __LINE__);return 0;}if (ioctl(group, VFIO_GROUP_GET_STATUS, &group_status)) {printf("%s line %d, failed to get vfio group status.\n", __func__, __LINE__);return 0;}if ((group_status.flags & VFIO_GROUP_FLAGS_VIABLE) == 0) {printf("%s line %d, vfio group is not viable.\n", __func__, __LINE__);return 0;}if (ioctl(group, VFIO_GROUP_SET_CONTAINER, &container)) {printf("%s line %d, vfio group set conatiner failure.\n", __func__, __LINE__);return 0;}if (ioctl(container, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU) != 0) {printf("%s line %d, vfio set type1 mode failure %s.\n", __func__, __LINE__, strerror(errno));return 0;}iommu_info = malloc(iommu_info_size);if (iommu_info == NULL) {printf("%s line %d, vfio alloc iommu info failure %s.\n", __func__, __LINE__, strerror(errno));return 0;}memset(iommu_info, 0x00, iommu_info_size);iommu_info->argsz = iommu_info_size;if (ioctl(container, VFIO_IOMMU_GET_INFO, iommu_info)) {printf("%s line %d, vfio failed to get iomu info, %s.\n", __func__, __LINE__, strerror(errno));return 0;}// todo// collect available iova regions from VFIO_IOMMU_GET_INFO.// 0000:02:00.0 must in this group.device = ioctl(group, VFIO_GROUP_GET_DEVICE_FD, "0000:02:00.0");if (device < 0) {printf("%s line %d, get vfio group device error.\n", __func__, __LINE__);return 0;}ioctl(device, VFIO_DEVICE_RESET);if (ioctl(device, VFIO_DEVICE_GET_INFO, &device_info)) {printf("%s line %d, get vfio group device info error.\n", __func__, __LINE__);return 0;}{struct vfio_region_info region = {.index = VFIO_PCI_CONFIG_REGION_INDEX,.argsz = sizeof(struct vfio_region_info),};if (ioctl(device, VFIO_DEVICE_GET_REGION_INFO, &region)) {printf("%s line %d, get vfio group device region info error.\n", __func__, __LINE__);return 0;}}maddr = mmap((void *)VADDR, IOVA_DMA_MAPSZ, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED, -1, 0);if (maddr == MAP_FAILED) {printf("%s line %d, faild to map buffer, error %s.\n", __func__, __LINE__, strerror(errno));return -1;}memset(&dma_map, 0x00, sizeof(dma_map));dma_map.argsz = sizeof(dma_map);dma_map.flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE;dma_map.iova = IOVA_START;dma_map.vaddr = (unsigned long)maddr;dma_map.size = IOVA_DMA_MAPSZ;if (ioctl(container, VFIO_IOMMU_MAP_DMA, &dma_map)) {printf("%s line %d, faild to do dma map on this conatainer.\n", __func__, __LINE__);return -1;}printf("%s line %d, do vfio dma mamp 1M memory buffer success, the iova is 0x%llx, dmaavddr 0x%llx, userptr %p.\n",__func__, __LINE__, dma_map.iova, dma_map.vaddr, maddr);memset(&dma_unmap, 0x00, sizeof(dma_unmap));dma_unmap.argsz = sizeof(dma_unmap);dma_unmap.iova = IOVA_START;dma_unmap.size = IOVA_DMA_MAPSZ;if (ioctl(container, VFIO_IOMMU_UNMAP_DMA, &dma_unmap)) {printf("%s line %d, faild to do dma unmap on this conatainer.\n", __func__, __LINE__);return -1;}munmap((void *)maddr, IOVA_DMA_MAPSZ);close(device);close(group);close(container);return 0;
}

测试程序在IOMMU上影射了1M的空间,IOVA范围为[0, 0x100000],我们DUMP PCIE连接IOMMU其页表为:

然后DUMP HOST HVA[0x400000000000,0x400000100000]范围的页表映射:

仔细对比两张截图,会发现他们的物理页框顺序完全一致,这样,在虚拟机中CPU通过GPA访问得到的数据和设备通过同样的IOVA访问的数据会保持一致,就像在真实的硬件上执行时的情况一样,这就是透传的效果,IOMMU功不可没,它让GUEST OS中具备了越过VMM直接访问设备的能力。

PCIE BAR空间的透传

PCI设备上可能会有板上的存储空间,比如PCIE显卡上的独立显存,或者PCIE网卡上的发送和接收缓冲队列,处理器需要将这些板上的内存映射到地址空间进行访问,但是与标准中预先定义好的内存不同,不同的机器上插的PCIE设备不同,这些都是变化的,处理器不可能为所有的PCIE设备预先定义一个地址空间的映射方案,因此,PCI标准提出了一个灵活的办法,各个PCIE设备自己提出需要占据的地址空间大小,以及映射方式(MMIO还是PIO),然后将这些诉求信息记录在配置空间的BAR字段,每个PCIE设备最多可以映射六个区域,对应六个BAR,至于映射到地址空间的什么位置,由BIOS在系统初始化时,查询PCIE设备的诉求信息,统一为PCI设备划分地址空间。

注意,前面提到的地址空间是PA(HPA 或者GPA)。

那么VCPU是如何访问透传到虚拟机的PCIE设备的BAR空间,从而达到访问PCIE设备上的存储的目的的呢?

先看一个PCIE设备透传前后,BAR空间映射的例子:

Realtek的一块PCIE有线网卡的信息如下,可以看到,它有三个BAR空间, BAR0是PIO模式访问的IO空间,BAR2是一块MMIO映射的Memory,大小为4K,启动时BIOS分配的地址是0xdf104000,最后一块BAR是Region4,它的起始地址为0xdf100000,大小为16K。当前设备使用的kernel-driver是vfio-pci,说明当前设备已经处于透传状态。

在虚拟机中的设备状态如下,虚拟机中的lspci工具基于BB,比较简陋,但是我们仍然能够通过vendor id/product id确认设备00:00.0就是透传到虚拟机的网卡设备:

lspci无法得到设备BAR信息,可以通过/proc/iomem以及/proc/ioport获取,如下图,我们找到了透传到虚拟机后的网卡的三个BAR空间信息,从每个BAR的大小来看,是和主机端一致的,这也侧面说明我们找对了。

BAR资源的透传是说,从VM中访问资源地址0xd2004000, 和在HOST中访问0xdf100000访问到的内容是一样的,它的过程和在KVM中添加一个内存条的步骤是一致的,都是将一个HVA区域映射到 VM中一段指定的GPA,只不过,在映射内存的时候,HVA是mmap映射的一段主存,而GPA是0(VM的物理地址从0开始),而在映射BAR空间的时候,HVA则是用户态MMAP HOST机上的BAR空间得到的用户态地址,GPA则成为了一段VM中的一段空闲的物理空间,这里是0xd2004000。

整个映射的逻辑如下图所示:

KVMTOOL定义了PCIE MMIO GPA的范围:


参考文章

基于virtio的半虚拟化概述 - 知乎

结束

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

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

相关文章

websocket实现聊天室(vue2 + node)

通过websocket实现简单的聊天室功能 需求分析如图&#xff1a; 搭建的项目结构如图&#xff1a; 前端步骤&#xff1a; vue create socket_demo (创建项目)views下面建立Home , Login组件路由里面配置路径Home组件内部开启websocket连接 前端相关组件代码&#xff1a; Login…

【操作系统】实验二 Proc文件系统

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的很重要&…

使用Rancher管理Kubernetes集群

部署前规划 整个部署包括2个部分&#xff0c;一是管理集群部署&#xff0c;二是k8s集群部署。管理集群功能主要提供web界面方式管理k8s集群。正常情况&#xff0c;管理集群3个节点即可&#xff0c;k8s集群至少3个。本文以3节点管理集群&#xff0c;3节点k8s集群为例 说明部署过…

vivado JTAG链、连接、IP关联规则

JTAG链 这列出了定义板上可用的不同JTAG链。每个链都列在下面<jtag_chain>以及链的名称&#xff0c;以及定义名称和链中组件的位置&#xff1a; <jtag_chains> <jtag_chain name"chain1"> <position name"0" component"part0…

ELK 分离式日志(1)

目录 一.ELK组件 ElasticSearch&#xff1a; Kiabana&#xff1a; Logstash&#xff1a; 可以添加的其它组件&#xff1a; ELK 的工作原理&#xff1a; 二.部署ELK 节点都设置Java环境: 每台都可以部署 Elasticsearch 软件&#xff1a; 修改elasticsearch主配置文件&…

计算机视觉工程师就业前景如何?

计算机视觉作为一门快速发展的技术领域&#xff0c;其就业前景非常广阔。以下是对计算机视觉就业前景的分析&#xff1a; 市场规模&#xff1a;计算机视觉行业的市场规模正在持续扩大。根据行业分析报告&#xff0c;预计全球计算机视觉市场规模将在2025年达到530亿美元&#xf…

【Web实操10】定位实操_图片上面定位文字

参考实现的效果是这样的&#xff1a; 目前还没有学到渐变色&#xff0c;所以标签效果的渐变色没有实现&#xff0c;只是利用radius设置了圆角图形&#xff0c;辅之以背景色和设置其中文本文字的颜色和居中对齐。 在自己写的过程中&#xff0c;对于标签的定位写成了相对定位&a…

RabbitMQ的安装使用

RabbitMQ是什么&#xff1f; MQ全称为Message Queue&#xff0c;消息队列&#xff0c;在程序之间发送消息来通信&#xff0c;而不是通过彼此调用通信。 RabbitMQ 主要是为了实现系统之间的双向解耦而实现的。当生产者大量产生数据时&#xff0c;消费者无法快速消费&#xff0c;…

打开json文件,读取里边的每一行数据,每一行数据是一个字典,使用matplotlib画图

这段代码的目的是读取 JSON 文件&#xff0c;提取关键信息&#xff0c;然后使用 Matplotlib 绘制四个子图&#xff0c;分别显示不同的指标随着 iter 变化的情况。这种图形化分析有助于直观地了解模型的性能。 画图结果如下&#xff1a; json文件格式如下&#xff1a;下面只粘贴…

计算机网络学习The next day

在计算机网络first day中&#xff0c;我们了解了计算机网络这个科目要学习什么&#xff0c;因特网的概述&#xff0c;三种信息交换方式等&#xff0c;在今天&#xff0c;我们就来一起学习一下计算机网络的定义和分类&#xff0c;以及计算机网络中常见的几个性能指标。 废话不多…

yarn集群datanode无法启动问题排查

一、问题场景 hdfs无法访问&#xff0c;通过jps命令查看进程&#xff0c;发现namenode启动成功&#xff0c;但是所有datanode都没有启动&#xff0c;重启集群&#xff08;start-dfs.sh&#xff09;后仍然一样 二、原因分析 先看下启动的日志有无报错。打开Hadoop的日志目录 …

C#,入门教程(24)——类索引器(this)的基础知识

上一篇&#xff1a; C#&#xff0c;入门教程(23)——数据类型转换的一点基础知识https://blog.csdn.net/beijinghorn/article/details/124187182 工业软件首先要求高可靠性、高可维护性。 作为工业软件的开发者&#xff0c;我们对语言重载的需求是&#xff1a;“不可或缺”。 …

Gitee Reward让开源作者不再为爱发电

一、什么是Gitee Reward&#xff1f; Gitee Reward是Gitee为改善开源开发生命周期提出的新策略。开源项目的支持者们可以更轻松地为其喜爱的项目提供资金&#xff0c;贡献者们也可以因为其不懈的开源贡献得到奖励。 二、Gitee Reward上允许哪些类型的项目&#xff1f; 允许任…

stable diffuison的安装和使用

stable diffuison的安装和使用 简单介绍 Stable Diffusion是一个深度学习文本到图像的生成模型&#xff0c;它可以根据文本描述生成详细的图像。这个模型主要应用于文本生成图像的场景中&#xff0c;通过给定的文本提示词&#xff0c;模型会输出一张与提示词相匹配的图片。 S…

Cadence——布局部分相关教程

本文章基于【凡亿】Cadence Allegro 17.4零基础入门66讲PCB Layout设计实战加个人理解写出 &#xff08;一&#xff09;中英文切换 注意&#xff1a;只是将选项卡部分切换中文 1&#xff0c;设置中文 a,打开PCB Editor 17.4以后&#xff0c;点击Help和About b,可以看到与下…

服务器或服务器主板中的BIOS更新详解

BIOS更新总共有三种方式&#xff1a;DOS、UEFI Shell以及BMC网页更新&#xff0c;而其中&#xff0c;DOS与Shell的更新方式类似&#xff0c;因此以下为统一描述。 一、UEFI Shell或DOS下更新 当我们下载了官网的BIOS更新包并解压后可以获得一些更新文件&#xff0c;在更新文件…

Python基础第七篇(Python的文件操作)

文章目录 一、文件编码二、文件的读取操作1.操作代码2.读出结果 三、文件的写出操作1.源代码2.读出结果 四、文件的追加操作1.源代码2.读出结果 这篇文章旨在深入浅出地介绍Python在文件操作上的能力&#xff0c;包括文件的编码、读取和写入等基本操作。内容丰富、易于理解&…

公网环境调试本地配置的Java支付宝沙箱环境模拟支付场景

文章目录 前言1. 下载当面付demo2. 修改配置文件3. 打包成web服务4. 局域网测试5. 内网穿透6. 测试公网访问7. 配置二级子域名8. 测试使用固定二级子域名访问 前言 在沙箱环境调试支付SDK的时候&#xff0c;往往沙箱环境部署在本地&#xff0c;局限性大&#xff0c;在沙箱环境…

java SSM项目预算生成管理系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM项目预算生成管理系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的 源代码和数据库&#xff0c;系统主…

浅析Java虚拟机中的ZGC

引言 为什么需要垃圾回收&#xff08;Garbage Collection&#xff09; 垃圾回收是Java开发中的关键机制&#xff0c;负责自动管理内存&#xff0c;防止内存泄漏&#xff0c;提高开发效率和应用程序的稳定性。 Java中主要的垃圾回收方法 标记-清除算法&#xff08;Mark and …