理解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.rhkb.cn/news/244040.html

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

相关文章

MySQL之数据库DDL

文章目录 MySQL数据库基本操作数据定义DDL对数据库的常用操作创建表修改表格式结构 MySQL数据库基本操作 首先我们先了解SQL的语言组成&#xff0c;他分为四个部分 数据定义语言&#xff08;DDL&#xff09;数据操纵语言&#xff08;DML&#xff09;数据控制语言&#xff08;…

three.js从入门到精通系列教程004 - three.js透视相机(PerspectiveCamera)滚动浏览全景大图

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>three.js从入门到精通系列教程004 - three.js透视相机&#xff08;PerspectiveCamera&#xff09;滚动浏览全景大图</title><script src"js/three.js"&g…

Python 自动化办公:一键批量生成 PPT

Stata and Python 数据分析 一、导读 在实际工作中&#xff0c;经常需要批量处理Office文件&#xff0c;比如需要制作一个几十页的PPT进行产品介绍时&#xff0c;一页一页地制作不仅麻烦而且格式可能不统一。那么有什么办法可以一键生成PPT呢&#xff1f;Python提供的pptx 包…

mysql生成最近24小时整点时间临时表

文章目录 生成最近24小时整点生成最近30天生成最近12个月 生成最近24小时整点 SELECT-- 每向下推1行, i比上次减去1b.*, i.*,DATE_FORMAT( DATE_SUB( NOW(), INTERVAL ( -( i : i - 1 ) ) HOUR ), %Y-%m-%d %H:00 ) AS time FROM-- 目的是生成12行数据( SELECTa FROM( SELECT…

【Python从入门到进阶】47、Scrapy Shell的了解与应用

接上篇《46、58同城Scrapy项目案例介绍》 上一篇我们学习了58同城的Scrapy项目案例&#xff0c;并结合实际再次了项目结构以及代码逻辑的用法。本篇我们来学习Scrapy的一个终端命令行工具Scrapy Shell&#xff0c;并了解它是如何帮助我们更好的调试爬虫程序的。 一、Scrapy Sh…

CTF CRYPTO 密码学-5

题目名称&#xff1a;山岚 题目描述&#xff1a; 山岚 f5-lf5aa9gc9{-8648cbfb4f979c-c2a851d6e5-c} 解题过程&#xff1a; Step1&#xff1a;根据题目提示栅栏加密 分析 观察给出的密文发现有f、l、a、g等字符有规律的夹杂的密文中间&#xff0c;看出都是每3个字符的第1…

P9232 [蓝桥杯 2023 省 A] 更小的数

[蓝桥杯 2023 省 A] 更小的数 终于本弱一次通关了一道研究生组别的题了[普及/提高−] 一道较为简单的双指针题,但一定有更好的解法. 题目描述 小蓝有一个长度均为 n n n 且仅由数字字符 0 ∼ 9 0 \sim 9 0∼9 组成的字符串&#xff0c;下标从 0 0 0 到 n − 1 n-1 n−1&a…

k8s使用ingress实现应用的灰度发布升级

v1是1.14.0版本nginx ,实操时候升级到v2是1.20.0版本nginx&#xff0c;来测试灰度发布实现过程 一、方案&#xff1a;使用ingress实现应用的灰度发布 1、服务端&#xff1a;正常版本v1&#xff0c;灰度升级版本v2 2、客户端&#xff1a;带有请求头versionv2标识的请求访问版…

【Linux】vim 操作指令详解

Linux 1 what is vim &#xff1f;2 vim基本概念3 vim的基本操作 &#xff01;3.1 vim的快捷方式3.1.1 复制与粘贴3.1.2 撤销与剪切3.1.3 字符操作 3.2 vim的光标操作3.3 vim的文件操作 总结Thanks♪(&#xff65;ω&#xff65;)&#xff89;感谢阅读下一篇文章见&#xff01;…

Git教程学习:09 Git分支

文章目录 1 分支的简介2 分支的相关操作2.1 分支的创建2.2 分支的切换2.3 分支的合并2.4 分支推送到远程2.5 分支的删除2.6 分支的重命名 3 分支开发工作流程3.1 长期分支3.2 短期分支 1 分支的简介 几乎所有的版本控制系统都以某种形式支持分支。使用分支意味着我们可以把我们…

使用DockerFile构建镜像与镜像上传

目录 前言&#xff1a;为什么要使用Dockerfile &#xff1f; DockerFile构建镜像 1、构建基础对象 2、Dockerfile文件结构 3、构建Dockerfile文件镜像 二、镜像上传&#xff08;阿里云&#xff09; 前言&#xff1a;为什么要使用Dockerfile &#xff1f; 首先Dockerfile …

网安防御保护防火墙初使用

要求 搭建之后 配置如下&#xff1a; 首先看要求是使用总公司部分则&#xff0c;先配置总公司的防火墙&#xff0c;注意配置总公司防火墙进入G0/0/0口的IP有个默认192.168.0.1 24&#xff0c;但是我们的云&#xff08;cloud&#xff09;上增加的端口绑定网卡IP为192.168.100.1…

React Router v6 改变页面Title

先说正事再闲聊 1、在路由表加个title字段 2、在index包裹路由 3、在App设置title 闲聊&#xff1a; 看到小黄波浪线了没 就是说默认不支持title字段了 出来的提示&#xff0c; 所以我本来是像下面这样搞的&#xff0c;就是感觉有点难维护&#xff0c;就还是用上面的方法了 …

高效工作必备神器:这款在线绘图软件完美替代Visio!

Visio是什么软件&#xff1f; Visio是微软公司开发的一款专业化的流程图绘制辅助工具&#xff0c;主要用于帮助IT和商务人员对复杂信息、系统和流程进行可视化处理、分析和交流。Visio提供了丰富的绘图功能&#xff0c;用户可以利用它创建各种类型的图表&#xff0c;包括但不限…

如何在Docker下部署MinIO存储服务通过Buckets实现文件的远程上传

&#x1f4d1;前言 本文主要是Linux下通过Docker部署MinIO存储服务实现远程上传的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1a;CSDN主页放风讲故事 &#…

Linux操作系统——理解文件系统

预备知识 到目前为止&#xff0c;我们所学习到的关于文件的操作&#xff0c;全部都是基于文件被打开&#xff0c;被访问&#xff0c;访问期间比较重要的有重定向&#xff0c;缓冲区&#xff0c;一切皆文件&#xff0c;当我们访问完毕的时候需要将文件关闭&#xff0c;关闭时那…

SpringBoot:Bean生命周期自定义初始化和销毁

&#x1f3e1;浩泽学编程&#xff1a;个人主页 &#x1f525; 推荐专栏&#xff1a;《深入浅出SpringBoot》《java项目分享》 《RabbitMQ》《Spring》《SpringMVC》 &#x1f6f8;学无止境&#xff0c;不骄不躁&#xff0c;知行合一 文章目录 前言一、Bean注解指…

【服务器】安装Docker环境

目录 &#x1f33a;【前言】 &#x1f33c;1. 打开Xshell软件 &#x1f33b;2. 安装Docker环境 ①&#xff1a;下载docker.sh脚本 ②&#xff1a;列出下载的内容 ③&#xff1a;执行一下get-docker.sh文件&#xff0c;安装docker ④&#xff1a;运行docker服务 ⑤&…

linux环境开发工具---yum与vim

1.Linux软件包管理器yum 1.1什么是软件包 在学习linux过程中&#xff0c;我们常常会遇到某些指令用不了的时候&#xff0c;原因除了权限问题外&#xff0c;还有可能是你当前的linux环境并没有安装相应的软件包。而在Linux下载安装软件的办法有两个&#xff0c;一个是先下载所需…

力扣1143. 最长公共子序列(动态规划)

Problem: 1143. 最长公共子序列 文章目录 题目描述思路解题方法复杂度Code 题目描述 思路 我们先假设已经将两个字符串转换为两个char类型的数组&#xff08;t1,t2&#xff09;便于比较 1.如果t1[i] t2[j],有三种决策&#xff1a;&#xff08;i1&#xff0c;j1&#xff09;&a…