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拷贝,它会从命令中指定的主机内存源地址 读取数据,然后写入到显存目标地址

相关推荐
ouliten8 小时前
cuda编程笔记(29)-- CUDA Graph
笔记·深度学习·cuda
KIDGINBROOK17 小时前
分布式与长序列attention
attention·cuda
BothSavage1 天前
Ubuntu-8卡H20服务器升级nvidia驱动+cuda版本
linux·服务器·ubuntu·gpu·nvidia·cuda·nvcc
ouliten3 天前
cuda编程笔记(28)-- cudaMemcpyPeer 与 P2P 访问机制
笔记·cuda
ulimate_4 天前
树莓派:树莓派能安装CUDA吗
树莓派·cuda
zhy295634 天前
【DNN】基础环境搭建
人工智能·tensorrt·cuda·开发环境·cudnn
ouliten6 天前
cuda编程笔记(27)-- NVTX的使用
笔记·cuda
ouliten14 天前
cuda编程笔记(24)-- Global Memory之间的拷贝
笔记·cuda
小脑斧要动脑16 天前
【CUDA】【WIP】环境安装-wsl2下cuda安装
cuda