理解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.mfbz.cn/a/339178.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[学习笔记]刘知远团队大模型技术与交叉应用L4-Prompt-learning Delta-learning

Prompt-Learning and Delta-Tunning 背景和概览 但是从T5开始&#xff0c;大模型越来越大了。 微调很难了。 模型的趋势 Model Scaling&#xff1a;模型越来越大 Difficult Tuning&#xff1a;微调越来越难 Prompt-Learning 基本组成与流程介绍 预训练和fine-tuning有一…

数学建模学习笔记||层次分析法

评价类问题 解决评价类问题首先需要想到一下三个问题 我们评价的目标是什么我们为了达到这个目标有哪几种可行方案评价的准则或者说指标是什么 对于以上三个问题&#xff0c;我们可以根据题目中的背景材料&#xff0c;常识以及网上收集到的参考资料进行结合&#xff0c;从而筛…

反欺诈与异常点检测

1. 反欺诈检检测 1.1 反欺诈检测的难点 反诈骗实际是个多分类问题&#xff0c;每种不同的诈骗都当做一种单独的类型。除了欺诈手段多样且持续变化&#xff0c;欺诈检测一般还面临以下问题&#xff1a; 1. 大部分情况下数据是没有标签的&#xff0c;各种成熟的监督学习没有用武…

反序列化字符串逃逸(下篇)

这里承接上篇文章反序列化字符串逃逸&#xff08;上篇&#xff09;-CSDN博客带大家学习反序列化字符串逃逸减少&#xff0c;没有看过的可以先去看看&#xff0c;不会吃亏。 例题&#xff1a; <?php highlight_file(__FILE__); error_reporting(0); function filter($name…

vectorCast基于分类树设计测试用例

根据代码的条件,以图表的形式为大家展示出各个变量组合的等价类划分。性别分为2类,年龄分为3类,工作年数分为3类。 那么它们最全面的组合结果就是2*3*3=18 也就是说它们最多有18种组合情况的测试用例 2.选中该函数,点击右键 3.自动生成一个map的基于分类树的测试用例 4.此…

commit 历史版本记录修正

commit 历史版本记录修正 当 Bug 发生的时候&#xff0c;我们会需要去追踪特定 bug 的历史记录&#xff0c;以查出该 bug 真正发生的原因&#xff0c;这个时候就是版本控制带来最大价值的时候。 因此&#xff0c;要怎样维持一个好的版本记录是非常重要的&#xff0c;下面是一…

机器学习--Matplotlib

机器学习–Matplotlib Matplotlib 是专门用于开发2D图表(包括3D图表)以渐进、交互式方式实现数据可视化 简单的Matplotlib画图 — 以折线图为例 matplotlib.pyplot模块 matplotlib.pytplot包含了一系列类似于matlab的画图函数。 import matplotlib.pyplot as plt图形绘制流…

7.【CPP】String类

一.汉字的编码 我们知道计算机存储英文字母&#xff0c;标点&#xff0c;数字用的是ascall码&#xff0c;128种用一个字节表示绰绰有余。而汉字远远不止128种&#xff0c;因此汉字需要两个字节表示。 1.gbk编码中汉字占两个字节。 2.utf-8中&#xff0c;一个汉字占三个字节。…

Java - 深入四大限流算法:原理、实现与应用

文章目录 Pre概述简单计数器原理实现测试优缺点 滑动窗口算法原理实现测试优缺点 漏桶算法原理实现测试优缺点 令牌桶算法原理实现测试优缺点 小结 Pre 深入理解分布式技术 - 限流 并发编程-25 高并发处理手段之消息队列思路 应用拆分思路 应用限流思路 SpringBoot - 优雅…

(上) C语言中的语句分类及分支语句:if语句、switch语句介绍

目录 前言 一、语句的分类 1. 空语句 2. 表达式语句 3. 函数调用语句 4. 复合语句 5. 控制语句 二、分支语句 1. if语句 (1) if的使用 (2) else的使用 (3) 分支中包含多条语句 (4) 嵌套if (5) 悬空else问题 2. switch语句 (1) if语句和switch语句的对比 (2) s…

摇臂MG995舵机模块实战教程

简介 舵机也叫伺服电机&#xff0c;最早用于船舶上实现其转向功能&#xff0c;由于可以通过程序连续控制其转角&#xff0c;因而被广泛应用智能小车以实现转向以及机器人各类关节运动中。舵机&#xff08;英文叫Servo&#xff09;&#xff1a;它由直流电机、减速齿轮组、传感器…

计算机网络——面试问题

1 从输⼊ URL 到⻚⾯展示到底发⽣了什么&#xff1f; 1. 先检查浏览器缓存⾥是否有缓存该资源&#xff0c;如果有直接返回&#xff1b;如果没有进⼊下⼀ 步⽹络请求。 2. ⽹络请求前&#xff0c;进⾏ DNS 解析 &#xff0c;以获取请求域名的 IP地址 。 3. 浏览器与服务器…

Sqoop与Kafka的集成:实时数据导入

将Sqoop与Kafka集成是实现实时数据导入和流处理的关键步骤之一。Sqoop用于将数据从关系型数据库导入到Hadoop生态系统中&#xff0c;而Kafka则用于数据流的传输和处理。本文将深入探讨如何使用Sqoop与Kafka集成&#xff0c;提供详细的步骤、示例代码和最佳实践&#xff0c;以确…

Git与GitHub零基础教学

大家好&#xff0c;我是星恒&#xff0c;这个帖子给大家分享的是git和github的全套教程&#xff0c;包含github初始&#xff0c;git常用命令以及基本使用&#xff0c;git的ssh链接github&#xff0c;github使用token登录&#xff0c;github和idea的配合&#xff0c;一些平时常用…

适合初学者的 机器学习 资料合集(可快速下载)

AI时代已经来临&#xff0c;机器学习成为了当今的热潮。但是&#xff0c;很多人在面对机器学习时却不知道如何开始学习。 今天&#xff0c;我为大家推荐几个适合初学者的机器学习开源项目&#xff0c;帮助大家更好地了解和掌握机器学习的知识。这些项目都是开源的&#xff0c;…

EtherNet/IP开发:C++开发CIP源代码

① 介绍一下CIP CIP是一种考虑到自动化行业而设计的通用协议。然而&#xff0c;由于其开放性&#xff0c;它可以并且已经应用于更多的领域。CIP网络库包含若干卷&#xff1a; 第1卷介绍了适用于所有网络自适应的CIP的常见方面。本卷包含通用对象库和设备配置文件库&#xff0…

信息安全的脆弱性及常见安全攻击

目录 信息安全概述信息安全现状及挑战传统安全防护逐步失效 安全风险能见度不足看不清资产看不见新型威胁看不见内网潜藏风险 常见的网络安全术语信息安全的脆弱性及常见安全攻击网络环境的开放性协议栈的脆弱性及常见攻击常见安全风险 协议栈自身的脆弱性网络的基本攻击模式 链…

Dubbo的几个序列化方式

欢迎订阅专栏&#xff0c;会分享Dubbo里面相关的技术实现 这篇文章就不详细的介绍每种序列化方式的实现细节&#xff0c;大家可以自行去问度娘&#xff0c;我也会找一些资料。需要注意的是&#xff0c;这个先后顺序不表示性能优越 ObjectInput、ObjectOutput 这两是Dubbo序列…

Linux_清理docker磁盘占用

文章目录 前言一、docker system 命令1. docker system df&#xff08;本文重点使用&#xff09;2. docker system prune&#xff08;本文重点使用&#xff09;3. docker system info4. docker system events 二、开始清理三、单独清理Build Cache四、单独清理未被使用的网络 前…

如何理解 GO 语言的接口 - 鸭子模型

个人认为&#xff0c;要理解 Go 的接口&#xff0c;一定先了解下鸭子模型。 鸭子模型 那什么鸭子模型&#xff1f; 鸭子模型的解释&#xff0c;通常会用了一个非常有趣的例子&#xff0c;一个东西究竟是不是鸭子&#xff0c;取决于它的能力。游泳起来像鸭子、叫起来也像鸭子…
最新文章