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 共享。两者的协同工作模式决定了数据流动的效率。

关键概念:VRAM 提供极低延迟和高带宽(可达 TB/s 级),适合存储帧缓冲、纹理和渲染目标;系统内存容量大但带宽受限(PCIe 4.0 x16 约 32 GB/s),适合存储不常访问的资源或作为溢出缓冲区。

3.1.1 内存层次与访问路径

GPU 访问内存的路径分为两种:

  • 本地访问:GPU 核心通过内存控制器直接读写 VRAM,延迟最低(几十纳秒),带宽最高。
  • 远程访问:GPU 通过 PCIe 控制器发起 DMA 请求,经过 PCIe 总线访问系统内存,延迟较高(微秒级),带宽受限于 PCIe 链路。
GPU 内存层次与访问路径 GPU Core VRAM (GDDR6/HBM) PCIe Controller System RAM CPU PCIe 总线 PCIe 总线 本地总线 内存总线 图例: 本地访问:GPU→VRAM(低延迟,高带宽) 远程访问:GPU→PCIe→System RAM(高延迟,低带宽)

3.1.2 协同工作模式

在 Linux DRM 驱动中,显存与系统内存的协同通过以下机制实现:

模式 描述 典型场景 带宽影响
本地渲染 所有渲染资源(纹理、顶点、帧缓冲)完全驻留在 VRAM 中 高性能游戏、3D 建模 最优,无 PCIe 瓶颈
统一内存架构(UMA) GPU 和 CPU 共享同一物理内存池(常见于集成 GPU) 笔记本、低功耗设备 中等,受内存带宽限制
显存溢出(Oversubscription) VRAM 不足时,部分资源通过 PCIe 交换到系统内存 大纹理场景、多屏输出 较差,频繁换页导致卡顿
零拷贝(Zero-Copy) GPU 直接访问系统内存中的 DMA 缓冲区,避免数据复制 视频编解码、AI 推理 中等,减少 CPU 开销
优化建议:在驱动开发中,应优先将高频访问的渲染资源(如当前帧的深度缓冲、颜色缓冲)分配在 VRAM 中;对于只读或低频资源(如静态纹理),可考虑使用 GART 映射到系统内存,以节省 VRAM 空间。

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 的固件区域或调试接口。
PCIe BAR 映射结构 CPU 物理地址空间 BAR0: MMIO (控制寄存器) BAR1: VRAM 映射 BAR2: 扩展功能 GPU 内部资源 控制/状态寄存器 VRAM 物理内存 固件/调试区域 PCIe 映射 PCIe 映射 PCIe 映射

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;
}
注意事项:BAR 映射的大小受限于 PCIe 地址空间(通常为 256 MB 或 512 MB)。对于大容量 VRAM(如 8 GB),BAR1 只能映射 VRAM 的一部分,剩余部分需要通过 GART 或分段映射访问。此外,ioremap 后的内存访问应使用 readl/writel 等严格顺序的 I/O 操作,避免 CPU 缓存导致的一致性问题。

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 硬件自动完成虚拟地址到物理地址的转换:

  1. 虚拟地址分解:GPU 核心生成一个 48 位虚拟地址,GART 将其分解为页目录索引、页表索引和页内偏移。
  2. 页表遍历:GART 硬件读取页目录基址寄存器(PDBR),逐级查找页表,获取物理页帧号(PFN)。
  3. 物理地址生成:将 PFN 与页内偏移组合,生成最终的物理地址,并发送到内存控制器。
  4. 属性检查:同时检查 PTE 中的权限位和缓存位,决定是否允许访问以及是否启用写合并(Write-Combine)或回写(Write-Back)策略。
GART 地址转换流程 GPU 虚拟地址 (GVA) 页目录 (Page Directory) 页表 (Page Table) 物理地址 (PA) VRAM / System RAM 页目录索引 页表索引 页内偏移 物理页帧号 GART 硬件引擎 - 页表缓存 (TLB) - 地址转换逻辑 - 权限检查 - 缓存策略控制 - 缺页处理

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);
}
性能关键点:GART 的 TLB 缺失会导致 GPU 流水线停顿,因此应尽量使用大页(如 64 KB 或 2 MB)来减少页表项数量,提高 TLB 命中率。在驱动初始化时,可以预先分配连续的物理内存并建立大页映射,避免运行时频繁更新页表。

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 页表命中率,可以精准定位瓶颈。