理解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.hqwc.cn/news/424307.html

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

相关文章

【JavaEE Spring】MyBatis 操作数据库 - 进阶

MyBatis 操作数据库 - 进阶 1. 动态SQL1.1 \<if>标签1.2 \<trim>标签1.3 \<where>标签1.4 \<set>标签1.5 \<foreach>标签1.6 \<include>标签 1. 动态SQL 动态 SQL 是Mybatis的强⼤特性之⼀&#xff0c;能够完成不同条件下不同的 sql 拼接…

卡尔曼滤波器原理By_DR_CAN 学习笔记

DR_CAN卡尔曼滤波器 Kalman Filter Recursive Algorithm迭代过程 数学基础正态分布和6-SigmaData FusionCovariance MatrixState Space Representation离散化推导 linearizationTaylor Series2-DSummary Step by Step Derivation of Kalman Gain矩阵求导公式 Prior / Posterio…

C语言爬虫采集图书网站百万数据

最近需要查阅一些资料&#xff0c;只给到相关项目名称以及关键词&#xff0c;想通过图书文库找到对应书籍&#xff0c;那么怎么才能在百万数据库中找到自己需要的文献呢&#xff1f; 今天我依然用C语言写个爬虫程序&#xff0c;从百万数据库中查找到适合的文章&#xff0c;能节…

汽车网络架构与常用总线汇总

汽车CAN总线简述 CAN 是控制器局域网Controller Area Network 的缩写&#xff0c;1986年&#xff0c;由德国Bosch公司为汽车开发的网络技术&#xff0c;主要用于汽车的监测与控制&#xff0c;目的为适应汽车“减少线束的数量”“通过多个网络进行大量数据的高速传输”的需求。…

10分钟入手一套监控系统

写在前面的话 在这里&#xff0c;我将分享一些观测云的小技巧&#xff0c;让您能更好地注册、接入和利用免费额度。 pv每天是2000的免费额度&#xff0c;这里有个技巧&#xff0c;就是支持配置采样率&#xff0c;以1%的采样率来计算&#xff0c;每天也有20万的额度了就是采样设…

Mysql数据库表单的可视化

1. 软件安装 安装地址&#xff1a;安装地址 推荐安装教程&#xff1a;安装教程 2. 数据库连接 1&#xff09;点击“新建”按钮&#xff0c;输入mysql的用户名和密码&#xff0c;创建与数据库的连接。 2&#xff09;进入后&#xff0c;可以看到自己本机上的数据库。 3. 导出…

国外网站seo教程,海外网站seo优化方案

随着互联网的发展&#xff0c;全球市场已经变得前所未有的普及。外贸企业不再局限于本土市场&#xff0c;而是可以轻松触及全球范围的潜在客户。全球市场的开放也意味着竞争的激烈。无论是大型跨国公司还是中小型企业&#xff0c;都在全球市场争夺有限的客户资源。 一、关键词策…

鸿蒙自定义刷新组件使用

前言 DevEco Studio版本&#xff1a;4.0.0.600 1、RefreshLibrary_HarmonyOS.har&#xff0c;用于HarmonyOS "minAPIVersion": 9, "targetAPIVersion": 9, "apiReleaseType": "Release", "compileSdkVersion": "3.…

高清短视频素材网站有哪些?分享十个做短视频必备的素材下载网站!

对于专注于短视频制作和剪辑的朋友来说&#xff0c;找到高质量的视频素材至关重要。你可能会想&#xff1a;“高清短视频素材网站有哪些&#xff1f;”别担心&#xff0c;今天我要为大家推荐十个提供优质素材的网站&#xff0c;帮你轻松搞定短视频制作&#xff01; 怪木素材网…

GEE错误——Image (Error) Image.select: Pattern ‘SR_B1‘ did not match any bands.

错误 我花了大量时间试图解决这个问题,但我找不到解决办法。开发人员和小组成员,我需要你们的帮助,这是我学习项目的一部分。 原始代码链接: https://code.earthengine.google.com/ccd8eb582aa520b970f4dee2d5118def Image (Error) Image.select: Pattern SR_B1 did …

AI 迎来“App Store”时代,AIGC 应用商业化大潮将至

2023 年被称为 AI 元年&#xff0c;一年多以来&#xff0c;AI 一直以前所未有的速度持续进化。 早在 11 月的开发者大会上&#xff0c;OpenAI 推出 GPTs 之时就宣布会开放 GPTs 商城。时隔两个月&#xff0c;商城正式推出&#xff0c;让开发者可以售卖自己定制的 GPT 机器人。…

关于 open ai,你了解多少?

OpenAI 的历史 第一阶段&#xff1a;2015-2018 年 2015 年&#xff0c;埃隆马斯克、山姆阿尔特曼、彼得蒂尔和杰西卡利文斯顿联合创立了 OpenAI。OpenAI 的目标是开发安全、有益的 AGI&#xff0c;并确保其造福全人类。在这一阶段&#xff0c;OpenAI 主要专注于基础研究&…