cudaMemcpy(Ah, Ad, 1024, D2H) 的执行细节

1. 裸机情况下的执行

cudaMemcpy(Ah, Ad, 1024, D2H); 这个 Ah是cpu 侧的虚拟地址 cpuva。

cudaMemcpy(Ah, Ad, 1024, cudaMemcpyDeviceToHost) 中,Ah 是主机端的虚拟地址: cpuva。

下面我们来详细拆解GPU的Copy Engine是如何完成这个拷贝的,特别是如何解决"Ah 是主机虚拟地址"这个核心问题。

1.1. GPU如何访问 主机虚拟地址的原理

GPU作为一个PCIe设备,在发起DMA传输时,只能使用物理地址 。它无法理解也不应直接使用 CPU的虚拟地址空间。因此,整个过程的核心就是 "将主机虚拟地址(Ah)转换为可供GPU DMA使用的物理地址,并确保地址映射在GPU的页表中"

1.2. GPU Copy Engine 完成 D2H 拷贝步骤详解

整个过程涉及 CPU 驱动程序、GPU Copy Engine 和系统内存管理单元的紧密协作。

第1步:CPU驱动程序的准备工作(在 cudaMemcpy 调用时)

当 host 程序调用 cudaMemcpy 时,CUDA运行时库会调用底层的GPU驱动程序。驱动程序需要为这次传输做好准备:

1.锁定主机内存

驱动程序会调用操作系统内核函数(如 pin_memory 或类似机制),将 Ah 所指向的1KB主机内存锁定在物理内存中 。这是为了防止这块内存在传输过程中被操作系统换出到磁盘(分页)。如果内存页被换出,其物理地址会改变,GPU的DMA传输就会失败或损坏数据。被锁定的内存常被称为 "Pinned Memory"或"Page-Locked Memory"

  1. 查询物理地址并映射到GPU IOMMU

驱动程序通过操作系统接口,获取 Ah 对应的主机物理地址 。由于内存已被锁定,这些物理地址在传输期间是固定不变的。驱动程序将这些主机物理地址 映射到 GPU的IOMMU 的页表中。IOMMU 是一个硬件单元,它负责将设备发起的访问请求中的"设备可见地址"(IOVA)转换为主机的物理地址。对于NVIDIA GPU,这个角色通常由 GPU内部的GMMU系统级的IOMMU 共同扮演。

第2步:构建命令并提交给Copy Engine

  1. 构建DMA命令数据包

    • GPU驱动程序在GPU的显存中准备一个命令数据包。这个数据包描述了传输的细节:

      • 源地址 :设备内存地址 Ad

      • 目标地址 :一个由GPU驱动程序分配的、在GPU IOMMU页表中注册过的 IO虚拟地址 。这个IOVA对应着主机物理地址 Ah

      • 传输大小:1024字节。

      • 传输类型:Device-to-Host。

  2. 通知Copy Engine

    • 驱动程序通过写GPU的某个门铃寄存器 ,来通知Copy Engine有新的命令在等待处理。这个寄存器通常是一个PCIe BAR空间中的地址。【注,细节见附录1】

第3步:GPU Copy Engine 的执行

  1. 获取命令

    • Copy Engine 通过PCIe总线,从显存中读取刚才驱动程序构建的命令数据包。
  2. 执行DMA传输

    • 读取阶段 :Copy Engine 从源地址 Ad(位于显存)读取数据。

    • 写入阶段:Copy Engine 准备将数据写入目标地址(即那个IOVA)。

      • 当它发起写入请求时,这个请求会经过 GPU内部的GMMU

      • GMMU会查询其页表,发现这个IOVA映射的是一个主机物理地址

      • 于是,GMMU会生成一个指向该系统物理地址的PCIe写入事务(TLP包)。

    • 这个PCIe事务通过PCIe根复合体,最终到达系统内存控制器。

    • 内存控制器将数据写入对应的主机物理内存 中,也就是 Ah 虚拟地址最终映射的物理位置。

第4步:传输完成与中断

  1. 发出完成信号

    • 当Copy Engine完成整个DMA传输后,它会在GPU上设置一个状态标志,并通过PCIe总线向CPU发起一个中断请求
  2. 中断处理

    • CPU的中断控制器收到该中断,并调用预先注册好的GPU驱动程序的中断服务程序

    • ISR会确认中断,检查传输状态,并通知上层CUDA运行时库:传输已完成。

  3. 清理工作

    • CUDA运行时库随后唤醒您的主机线程,cudaMemcpy 调用返回。

    • 驱动程序可能会解除主机内存的锁定(如果该内存不是由 cudaHostAlloc 分配的固定内存,而是临时锁定的),并清理在GPU IOMMU中的临时映射。

1.3. cudaMemcpy D2H 关键点总结

虚拟地址问题

GPU不直接使用 Ah 这个CPU虚拟地址。驱动程序通过锁定内存IOMMU映射 ,将 Ah 转换为稳定的物理地址,再为GPU提供一个它能够理解的IOVA。

Copy Engine角色

它是一个高度专业化的DMA控制器,独立于GPU的3D/Compute核心,专门负责在设备与主机之间高效地搬运数据。

数据流

Ad -> Copy Engine -> PCIe -> GPU GMMU -> 系统IOMMU -> 内存控制器 -> Ah 对应的物理内存。

软件协作

整个过程是CUDA运行时、GPU驱动程序和操作系统内核协同工作的结果,它们共同抽象了硬件的复杂性,为程序员提供了一个简单的API。

这个过程确保了GPU能够安全、高效地直接与主机程序定义的普通指针(虚拟地址)进行数据交换。

2. 虚拟化情况下的透传 gpu 时的执行流程

在虚拟化环境(以 GPU透传模式 为例)中,执行 cudaMemcpy(Ah, Ad, 1024, cudaMemcpyDeviceToHost) 的完整且详细的执行流程。

我们将重点关注 地址翻译Hypervisor 的作用

2.1. 场景设定

物理机:运行 Hypervisor。

虚拟机:运行 Guest OS 和 CUDA 应用程序。

GPU分配:物理 GPU 通过 PCIe 透传方式直接分配给虚拟机。

地址

Ad:GPU设备内存地址(在GPU的物理地址空间内,处理相对简单)。

Ah:Guest OS 中的一个虚拟地址。这是整个流程复杂性的根源。

2.2 详细执行流程

第1阶段:Guest OS 内的初始化与请求
  1. 应用程序调用

    • 在 Guest OS 中,CUDA 应用程序调用 cudaMemcpy(Ah, Ad, 1024, cudaMemcpyDeviceToHost)
  2. CUDA 驱动准备

    • Guest OS 内的 CUDA 驱动会像在裸机上一样开始工作。

    • 它需要确保目标缓冲区 Ah(主机端)是 "锁定的",即其物理页面在内存中是固定的,不会被换出。这是 DMA 操作的前提。

  3. 陷入 Hypervisor

    • 当驱动尝试调用操作系统服务来"锁定" Ah 对应的内存时,由于这是一个特权操作,会触发 VM Exit,CPU 从 Guest OS 切换到 Hypervisor。

    • Hypervisor 接管控制权,代表 Guest OS 执行真正的内存锁定操作。它:

      • 找到 Ah 这个 GVA 对应的 Guest 物理地址

      • 通过查询 EPT,将 GPA 锁定为 Host 物理地址,并确保该映射稳定存在。

第2阶段:构建 DMA 命令
  1. 分配 IOVA

    • Hypervisor 的虚拟化模块(如 Intel VT-d)为这次 DMA 传输分配一个 IO虚拟地址

    • Hypervisor 在 IOMMU 的页表 中建立映射:IOVA -> Host 物理地址。这个映射关系是 GPU 能够正确写入数据的关键。

  2. 驱动构建命令包

    • 控制权返回 Guest OS 内的 CUDA 驱动。

    • 驱动在 Guest OS 的主机内存 中构建一个 DMA 命令数据包。这个包包含:

      • 源地址Ad(GPU设备内存地址)。

      • 目标地址IOVA (注意,不是 Ah 的 GPA,更不是 HPA)。驱动从 Hypervisor 提供的机制中获得这个 IOVA。

      • 传输大小:1024 字节。

      • 操作类型:Device-to-Host。

  3. 通知 GPU

    • 驱动通过写 GPU 的 Doorbell 寄存器(该寄存器通过 PCIe BAR 映射到 Guest 的物理地址空间)来通知 GPU 有新的命令。

    • 这个写操作本身会触发一次 VM Exit,由 Hypervisor 确保写操作被正确传递到物理 GPU。

第3阶段:GPU 执行 DMA 传输
  1. GPU 获取命令

    • GPU 的 Copy Engine 收到门铃通知后,开始工作。

    • 它首先发起一次 DMA 读取 ,从主机内存 中(使用驱动提供的地址)将步骤5中构建的 DMA 命令包 取到 GPU 内部。这个读取操作可能也需要经过 IOMMU 的地址翻译。

  2. GPU 执行数据传输

    • Copy Engine 解析命令包。

    • 读取阶段 :它从源地址 Ad(位于 GPU 显存)中读取 1024 字节的数据。

    • 写入阶段 :它准备将数据写入目标地址,即命令包中指定的 IOVA

  3. 关键的地址翻译

    • GPU 发起一个指向 IOVAPCIe Write TLP 包

    • 这个 TLP 包经过 PCIe 根复合体,被 IOMMU 拦截。

    • IOMMU 查询其页表 (由 Hypervisor 在步骤4中设置),执行实时地址翻译,将 TLP 包中的 IOVA 替换为对应的 Host 物理地址

    • 翻译后的 TLP 包被发送到系统内存控制器。

  4. 数据落盘

    • 内存控制器将数据写入 Host 物理内存 中对应的位置。
第4阶段:传输完成与清理
  1. 中断与通知

    • GPU Copy Engine 完成 DMA 传输后,会向 CPU 发起一个 中断

    • 该中断被 Hypervisor 接收,Hypervisor 根据中断路由信息,将其 注入 到对应的 Guest OS 中。

  2. Guest OS 处理完成

    • Guest OS 的中断服务程序被触发,通知 Guest CUDA 驱动:传输已完成。

    • 驱动进而唤醒应用程序线程,cudaMemcpy 调用返回。

  3. 资源清理

    • Hypervisor 和 Guest 驱动协作,释放之前锁定的内存,并清除 IOMMU 页表中为这次传输建立的临时 IOVA -> HPA 映射。

3. 核心总结与裸机的区别

1. 内存锁定的执行者不同

裸机:CUDA驱动直接调用操作系统内核的函数来锁定物理内存页。

虚拟机 :Guest OS内的驱动尝试锁定内存时,此操作会陷入到Hypervisor。由Hypervisor代表Guest OS执行真正的内存锁定,并管理Guest物理地址到主机物理地址的EPT映射。

2. 地址映射的建立者不同

裸机 :CUDA驱动作为内核级驱动,拥有最高权限。它可以直接读取到主机物理地址 ,并直接编程GPU自己的MMU页表,建立IOVA到Host PA的映射。

虚拟机:Guest OS内的驱动无法得知主机物理地址,它看到的只是Guest物理地址。因此:

映射建立者 :是Hypervisor 。它分配一个IOVA,并在IOMMU的页表 中建立 IOVA -> Host PA 的映射。

Guest驱动:只负责向Hypervisor申请并使用这个IOVA,它无法直接操作GPU MMU或IOMMU。

3. DMA地址翻译的机制不同

裸机 :GPU的DMA引擎使用IOVA。在优化好的系统中,GPU MMU中的IOVA到Host PA的映射通常已经预置好,或者系统配置为IOVA等于Host PA,因此DMA请求可能无需经过系统IOMMU的翻译,或翻译过程很简单。

虚拟机IOMMU是必需的 。GPU发出的包含IOVA的DMA请求,必须经过系统IOMMU的拦截和实时翻译,将IOVA转换为Host PA,才能完成写入。这引入了少量开销,但带来了至关重要的安全性和隔离性。

环节 裸机环境 虚拟化环境(透传)
内存锁定 驱动直接调用OS内核完成。 操作陷入Hypervisor,由Hypervisor完成。
目标地址 驱动直接使用Host物理地址或一个直接映射的IOVA。 驱动使用由 Hypervisor 分配的 IOVA
地址翻译 由GPU MMU或简单的映射处理。 必须由系统IOMMU 执行 IOVA -> HPA 的翻译。
关键管理者 CUDA驱动(拥有最高硬件权限)。 Hypervisor(管理EPT和IOMMU页表,是真正的资源控制者)。

结论 :虚拟化环境下的流程,通过引入 IOMMUHypervisor 作为"可信中介",增加了一个隔离层。GPU 使用一个临时的、受控的 IOVA 进行操作,由 IOMMU 负责将其"重定向"到正确的 Host 物理地址。这套机制虽然增加了少量开销,但实现了至关重要的安全隔离 (防止虚拟机进行恶意DMA)和资源管理。对于应用程序而言,整个复杂过程是完全透明的。

4. 附录1.门铃寄存器后的流程

操作门铃寄存器,会向GPU(或其Copy Engine)发送一个信号:"你有新的命令需要处理了,命令的位置在XXX"。

这个过程具体如下:

  1. 准备命令包

    • GPU驱动程序会在主机内存中准备好一个或多个DMA命令数据包。这个包里面包含了详细的指令:源地址、目标地址、传输大小、传输类型(H2D/D2H)等。

    • 同时,驱动程序也会在GPU显存 中设置一个区域,称为 "命令队列"

  2. 通知GPU

    • 驱动程序通过CPU的MOV指令,向Doorbell寄存器(它是一个映射到CPU物理地址空间的PCIe BAR空间中的特定地址)写入一个值(例如,队列的指针或一个简单的增量值)。

    • 这个写操作会生成一个PCIe写事务(TLP包),通过PCIe总线到达GPU。

  3. GPU的处理

    • GPU内部的Doorbell控制器侦测到这个写入操作。

    • 收到门铃信号后,GPU并不会立即去主机内存读取命令包。相反,它知道需要去处理自己的显存中的命令队列了。

    • GPU的DMA控制器 开始从主机内存拉取 驱动程序事先准备好的命令包。这个"拉取"操作本身,就是一次由GPU发起的DMA读取操作

  4. 执行命令

    • GPU的Copy Engine解析从主机内存拉取过来的命令包。

    • 根据命令的描述,Copy Engine开始执行真正的数据搬运DMA操作。例如,对于H2D拷贝,它会从命令中指定的主机内存源地址 读取数据,然后写入到显存目标地址

相关推荐
碧海潮生_CC6 小时前
【CUDA笔记】04 CUDA 归约, 原子操作,Warp 交换
笔记·cuda
fpcc4 天前
并行编程实战——CUDA编程的流的优先级
c++·cuda
碧海潮生_CC5 天前
【CUDA笔记】03 CUDA GPU 架构与一般的程序优化思路(下)
笔记·架构·cuda
中医正骨葛大夫7 天前
一文解决如何在Pycharm中创建cuda深度学习环境?
pytorch·深度学习·pycharm·软件安装·cuda·anaconda·配置环境
lvxiangyu1111 天前
wsl2 ubuntu24 opengl 无法使用nvidia显卡 解决方法记录
wsl·cuda·opengl
李昊哲小课12 天前
wsl ubuntu24.04 cuda13 cudnn9 pytorch 显卡加速
人工智能·pytorch·python·cuda·cudnn
wanzhong233313 天前
CUDA学习2-CPU和GPU的性能优化
深度学习·gpu·cuda·高性能计算
碧海潮生_CC19 天前
【CUDA笔记】01-入门简介
笔记·cuda
喆星时瑜22 天前
关于 ComfyUI 的 Windows 本地部署系统环境教程(详细讲解Windows 10/11、NVIDIA GPU、Python、PyTorch环境等)
python·cuda·comfyui
安全二次方security²25 天前
CUDA C++编程指南(1)——简介
nvidia·cuda·c/c++·device·cuda编程·architecture·compute unified