3. GPU内存模型:显存(VRAM)与系统内存(System RAM)的协同、PCIe BAR映射与GART机制
在现代图形系统中,GPU 并非孤立工作,它需要同时访问本地显存(VRAM)和系统内存(System RAM)。理解这两种物理内存的协同方式、PCIe BAR(基地址寄存器)的映射原理以及 GART(图形地址重映射表)机制,是优化图形管线内存带宽的关键前提。本章将深入剖析 GPU 内存模型的层次结构、地址映射流程以及带宽瓶颈所在。
3.1 显存(VRAM)与系统内存(System RAM)的协同
GPU 拥有两种主要的内存资源:显存(VRAM)和系统内存(System RAM)。VRAM 是 GPU 的专用高速内存,通常通过高带宽接口(如 GDDR6、HBM2e)与 GPU 核心直连;而系统内存则通过 PCIe 总线与 CPU 共享。两者的协同工作模式决定了数据流动的效率。
3.1.1 内存层次与访问路径
GPU 访问内存的路径分为两种:
- 本地访问:GPU 核心通过内存控制器直接读写 VRAM,延迟最低(几十纳秒),带宽最高。
- 远程访问:GPU 通过 PCIe 控制器发起 DMA 请求,经过 PCIe 总线访问系统内存,延迟较高(微秒级),带宽受限于 PCIe 链路。
3.1.2 协同工作模式
在 Linux DRM 驱动中,显存与系统内存的协同通过以下机制实现:
| 模式 | 描述 | 典型场景 | 带宽影响 |
|---|---|---|---|
| 本地渲染 | 所有渲染资源(纹理、顶点、帧缓冲)完全驻留在 VRAM 中 | 高性能游戏、3D 建模 | 最优,无 PCIe 瓶颈 |
| 统一内存架构(UMA) | GPU 和 CPU 共享同一物理内存池(常见于集成 GPU) | 笔记本、低功耗设备 | 中等,受内存带宽限制 |
| 显存溢出(Oversubscription) | VRAM 不足时,部分资源通过 PCIe 交换到系统内存 | 大纹理场景、多屏输出 | 较差,频繁换页导致卡顿 |
| 零拷贝(Zero-Copy) | GPU 直接访问系统内存中的 DMA 缓冲区,避免数据复制 | 视频编解码、AI 推理 | 中等,减少 CPU 开销 |
3.2 PCIe BAR映射:GPU的地址窗口
PCIe BAR(Base Address Register)是 PCIe 设备向系统报告其所需内存或 I/O 地址空间的机制。对于 GPU 而言,BAR 映射是 CPU 访问 GPU 内部资源(如命令队列、寄存器、帧缓冲)的桥梁。
3.2.1 BAR的类型与作用
GPU 通常实现多个 BAR,每个 BAR 对应不同的功能区域:
- BAR0(MMIO):映射 GPU 的控制寄存器、状态寄存器、门铃(Doorbell)等,用于 CPU 下发命令和查询状态。
- BAR1(VRAM 映射):将 GPU 的 VRAM 的一部分或全部映射到 CPU 的物理地址空间,使 CPU 可以直接读写 VRAM(如更新纹理、读取渲染结果)。
- BAR2(可选):用于扩展功能,如映射 GPU 的固件区域或调试接口。
3.2.2 BAR映射的配置流程
在 Linux 内核中,PCIe BAR 的配置由 PCI 子系统自动完成,但 DRM 驱动需要主动获取 BAR 的基地址和大小:
// 获取 BAR0 和 BAR1 的基地址与大小
struct pci_dev *pdev = to_pci_dev(dev->dev);
resource_size_t bar0_start = pci_resource_start(pdev, 0);
resource_size_t bar0_len = pci_resource_len(pdev, 0);
resource_size_t bar1_start = pci_resource_start(pdev, 1);
resource_size_t bar1_len = pci_resource_len(pdev, 1);
// 将 BAR1 映射到内核虚拟地址空间
void __iomem *vram_base = ioremap(bar1_start, bar1_len);
if (!vram_base) {
DRM_ERROR("Failed to ioremap BAR1\n");
return -ENOMEM;
}
3.3 GART机制:GPU的页表引擎
GART(Graphics Address Remapping Table)是 GPU 内部的一个硬件页表引擎,用于将 GPU 虚拟地址(GVA)映射到物理内存(VRAM 或系统内存)。它类似于 CPU 的 MMU,但专门为图形工作负载优化。
3.3.1 GART的工作原理
GART 维护一个页表(Page Table),每个页表项(PTE)记录一个 4 KB 或 64 KB 页的物理地址和属性(如可读、可写、缓存策略)。当 GPU 发出内存访问请求时,GART 硬件自动完成虚拟地址到物理地址的转换:
- 虚拟地址分解:GPU 核心生成一个 48 位虚拟地址,GART 将其分解为页目录索引、页表索引和页内偏移。
- 页表遍历:GART 硬件读取页目录基址寄存器(PDBR),逐级查找页表,获取物理页帧号(PFN)。
- 物理地址生成:将 PFN 与页内偏移组合,生成最终的物理地址,并发送到内存控制器。
- 属性检查:同时检查 PTE 中的权限位和缓存位,决定是否允许访问以及是否启用写合并(Write-Combine)或回写(Write-Back)策略。
3.3.2 GART与CPU MMU的对比
| 特性 | CPU MMU | GPU GART |
|---|---|---|
| 页大小 | 4 KB / 2 MB / 1 GB | 4 KB / 64 KB / 2 MB |
| TLB 大小 | 通常 64-512 项 | 通常 1024-4096 项 |
| 缺页处理 | 由内核缺页异常处理程序完成 | 由 GPU 驱动或硬件缺页处理单元完成 |
| 缓存策略 | 回写(Write-Back)为主 | 写合并(Write-Combine)或直写(Write-Through) |
| 地址空间 | 每个进程独立(48 位虚拟地址) | 每个 GPU 上下文独立(48 位虚拟地址) |
| 主要优化目标 | 降低延迟、支持稀疏内存 | 提高带宽、支持大页、减少 TLB 缺失 |
3.3.3 GART在DRM驱动中的实现
在 Linux DRM 驱动中,GART 的管理通常通过 drm_mm 内存管理器或自定义的 GART 分配器实现。以下是一个简化的 GART 页表更新流程:
// 更新 GART 页表项(PTE)
struct gart_pte {
u64 pfn : 40; // 物理页帧号
u64 valid : 1; // 有效位
u64 writable : 1; // 可写位
u64 cache_type : 2;// 缓存策略: 0=UC, 1=WC, 2=WB
u64 reserved : 20;
};
void gart_update_pte(struct gart_device *gart, u64 gpu_vaddr,
dma_addr_t phys_addr, size_t size)
{
u64 pfn = phys_addr >> PAGE_SHIFT;
u64 pte_index = (gpu_vaddr - gart->base) >> PAGE_SHIFT;
struct gart_pte *pte = &gart->ptes[pte_index];
pte->pfn = pfn;
pte->valid = 1;
pte->writable = 1;
pte->cache_type = 1; // Write-Combine
// 刷新 TLB(通过写 GART 控制寄存器)
writel(1, gart->regs + GART_TLB_INVALIDATE);
}
3.4 内存带宽优化策略
基于上述内存模型,以下是针对 GPU 内存带宽的优化策略:
- 优先使用 VRAM:将高频访问的渲染资源(如深度缓冲、颜色缓冲、纹理)分配在 VRAM 中,避免 PCIe 瓶颈。
- 利用 GART 大页:在驱动中启用 64 KB 或 2 MB 大页映射,减少 TLB 缺失,提高地址转换效率。
- 写合并(WC)策略:对于 CPU 写入 GPU 的数据(如命令缓冲区),使用 Write-Combine 缓存策略,合并写操作,减少 PCIe 事务次数。
- 异步 DMA 传输:使用 DMA-BUF 机制实现 GPU 与 CPU 之间的异步数据传输,避免同步等待。
- 内存池化:通过
drm_mm或自定义分配器管理 VRAM 和 GART 空间,减少碎片化,提高分配效率。
perf 工具统计 PCIe 事务量,或通过 GPU 硬件计数器(如 AMD GPU 的 umr 工具、NVIDIA 的 nvidia-smi)监控 VRAM 带宽利用率。结合 GART 页表命中率,可以精准定位瓶颈。