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"。
- 查询物理地址并映射到GPU IOMMU
驱动程序通过操作系统接口,获取 Ah
对应的主机物理地址 。由于内存已被锁定,这些物理地址在传输期间是固定不变的。驱动程序将这些主机物理地址 映射到 GPU的IOMMU 的页表中。IOMMU 是一个硬件单元,它负责将设备发起的访问请求中的"设备可见地址"(IOVA)转换为主机的物理地址。对于NVIDIA GPU,这个角色通常由 GPU内部的GMMU 和 系统级的IOMMU 共同扮演。
第2步:构建命令并提交给Copy Engine
-
构建DMA命令数据包
-
GPU驱动程序在GPU的显存中准备一个命令数据包。这个数据包描述了传输的细节:
-
源地址 :设备内存地址
Ad
。 -
目标地址 :一个由GPU驱动程序分配的、在GPU IOMMU页表中注册过的 IO虚拟地址 。这个IOVA对应着主机物理地址
Ah
。 -
传输大小:1024字节。
-
传输类型:Device-to-Host。
-
-
-
通知Copy Engine
- 驱动程序通过写GPU的某个门铃寄存器 ,来通知Copy Engine有新的命令在等待处理。这个寄存器通常是一个PCIe BAR空间中的地址。【注,细节见附录1】
第3步:GPU Copy Engine 的执行
-
获取命令
- Copy Engine 通过PCIe总线,从显存中读取刚才驱动程序构建的命令数据包。
-
执行DMA传输
-
读取阶段 :Copy Engine 从源地址
Ad
(位于显存)读取数据。 -
写入阶段:Copy Engine 准备将数据写入目标地址(即那个IOVA)。
-
当它发起写入请求时,这个请求会经过 GPU内部的GMMU。
-
GMMU会查询其页表,发现这个IOVA映射的是一个主机物理地址。
-
于是,GMMU会生成一个指向该系统物理地址的PCIe写入事务(TLP包)。
-
-
这个PCIe事务通过PCIe根复合体,最终到达系统内存控制器。
-
内存控制器将数据写入对应的主机物理内存 中,也就是
Ah
虚拟地址最终映射的物理位置。
-
第4步:传输完成与中断
-
发出完成信号
- 当Copy Engine完成整个DMA传输后,它会在GPU上设置一个状态标志,并通过PCIe总线向CPU发起一个中断请求。
-
中断处理
-
CPU的中断控制器收到该中断,并调用预先注册好的GPU驱动程序的中断服务程序。
-
ISR会确认中断,检查传输状态,并通知上层CUDA运行时库:传输已完成。
-
-
清理工作
-
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 内的初始化与请求
-
应用程序调用:
- 在 Guest OS 中,CUDA 应用程序调用
cudaMemcpy(Ah, Ad, 1024, cudaMemcpyDeviceToHost)
。
- 在 Guest OS 中,CUDA 应用程序调用
-
CUDA 驱动准备:
-
Guest OS 内的 CUDA 驱动会像在裸机上一样开始工作。
-
它需要确保目标缓冲区
Ah
(主机端)是 "锁定的",即其物理页面在内存中是固定的,不会被换出。这是 DMA 操作的前提。
-
-
陷入 Hypervisor:
-
当驱动尝试调用操作系统服务来"锁定"
Ah
对应的内存时,由于这是一个特权操作,会触发 VM Exit,CPU 从 Guest OS 切换到 Hypervisor。 -
Hypervisor 接管控制权,代表 Guest OS 执行真正的内存锁定操作。它:
-
找到
Ah
这个 GVA 对应的 Guest 物理地址。 -
通过查询 EPT,将 GPA 锁定为 Host 物理地址,并确保该映射稳定存在。
-
-
第2阶段:构建 DMA 命令
-
分配 IOVA:
-
Hypervisor 的虚拟化模块(如 Intel VT-d)为这次 DMA 传输分配一个 IO虚拟地址。
-
Hypervisor 在 IOMMU 的页表 中建立映射:IOVA -> Host 物理地址。这个映射关系是 GPU 能够正确写入数据的关键。
-
-
驱动构建命令包:
-
控制权返回 Guest OS 内的 CUDA 驱动。
-
驱动在 Guest OS 的主机内存 中构建一个 DMA 命令数据包。这个包包含:
-
源地址 :
Ad
(GPU设备内存地址)。 -
目标地址 :IOVA (注意,不是
Ah
的 GPA,更不是 HPA)。驱动从 Hypervisor 提供的机制中获得这个 IOVA。 -
传输大小:1024 字节。
-
操作类型:Device-to-Host。
-
-
-
通知 GPU:
-
驱动通过写 GPU 的 Doorbell 寄存器(该寄存器通过 PCIe BAR 映射到 Guest 的物理地址空间)来通知 GPU 有新的命令。
-
这个写操作本身会触发一次 VM Exit,由 Hypervisor 确保写操作被正确传递到物理 GPU。
-
第3阶段:GPU 执行 DMA 传输
-
GPU 获取命令:
-
GPU 的 Copy Engine 收到门铃通知后,开始工作。
-
它首先发起一次 DMA 读取 ,从主机内存 中(使用驱动提供的地址)将步骤5中构建的 DMA 命令包 取到 GPU 内部。这个读取操作可能也需要经过 IOMMU 的地址翻译。
-
-
GPU 执行数据传输:
-
Copy Engine 解析命令包。
-
读取阶段 :它从源地址
Ad
(位于 GPU 显存)中读取 1024 字节的数据。 -
写入阶段 :它准备将数据写入目标地址,即命令包中指定的 IOVA。
-
-
关键的地址翻译:
-
GPU 发起一个指向 IOVA 的 PCIe Write TLP 包。
-
这个 TLP 包经过 PCIe 根复合体,被 IOMMU 拦截。
-
IOMMU 查询其页表 (由 Hypervisor 在步骤4中设置),执行实时地址翻译,将 TLP 包中的 IOVA 替换为对应的 Host 物理地址。
-
翻译后的 TLP 包被发送到系统内存控制器。
-
-
数据落盘:
- 内存控制器将数据写入 Host 物理内存 中对应的位置。
第4阶段:传输完成与清理
-
中断与通知:
-
GPU Copy Engine 完成 DMA 传输后,会向 CPU 发起一个 中断。
-
该中断被 Hypervisor 接收,Hypervisor 根据中断路由信息,将其 注入 到对应的 Guest OS 中。
-
-
Guest OS 处理完成:
-
Guest OS 的中断服务程序被触发,通知 Guest CUDA 驱动:传输已完成。
-
驱动进而唤醒应用程序线程,
cudaMemcpy
调用返回。
-
-
资源清理:
- Hypervisor 和 Guest 驱动协作,释放之前锁定的内存,并清除 IOMMU 页表中为这次传输建立的临时
IOVA -> HPA
映射。
- Hypervisor 和 Guest 驱动协作,释放之前锁定的内存,并清除 IOMMU 页表中为这次传输建立的临时
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页表,是真正的资源控制者)。 |
结论 :虚拟化环境下的流程,通过引入 IOMMU 和 Hypervisor 作为"可信中介",增加了一个隔离层。GPU 使用一个临时的、受控的 IOVA 进行操作,由 IOMMU 负责将其"重定向"到正确的 Host 物理地址。这套机制虽然增加了少量开销,但实现了至关重要的安全隔离 (防止虚拟机进行恶意DMA)和资源管理。对于应用程序而言,整个复杂过程是完全透明的。
4. 附录1.门铃寄存器后的流程
操作门铃寄存器,会向GPU(或其Copy Engine)发送一个信号:"你有新的命令需要处理了,命令的位置在XXX"。
这个过程具体如下:
-
准备命令包:
-
GPU驱动程序会在主机内存中准备好一个或多个DMA命令数据包。这个包里面包含了详细的指令:源地址、目标地址、传输大小、传输类型(H2D/D2H)等。
-
同时,驱动程序也会在GPU显存 中设置一个区域,称为 "命令队列"。
-
-
通知GPU:
-
驱动程序通过CPU的
MOV
指令,向Doorbell寄存器(它是一个映射到CPU物理地址空间的PCIe BAR空间中的特定地址)写入一个值(例如,队列的指针或一个简单的增量值)。 -
这个写操作会生成一个PCIe写事务(TLP包),通过PCIe总线到达GPU。
-
-
GPU的处理:
-
GPU内部的Doorbell控制器侦测到这个写入操作。
-
收到门铃信号后,GPU并不会立即去主机内存读取命令包。相反,它知道需要去处理自己的显存中的命令队列了。
-
GPU的DMA控制器 开始从主机内存 中拉取 驱动程序事先准备好的命令包。这个"拉取"操作本身,就是一次由GPU发起的DMA读取操作。
-
-
执行命令:
-
GPU的Copy Engine解析从主机内存拉取过来的命令包。
-
根据命令的描述,Copy Engine开始执行真正的数据搬运DMA操作。例如,对于H2D拷贝,它会从命令中指定的主机内存源地址 读取数据,然后写入到显存目标地址。
-