上一节(3.3 CPU 视图)讨论了 CPU 访问 BO 的路径:内核通过drm_vma_offset_manager为drm_gem_object分配伪 offset,用户态以该 offset 调用mmap,并在缺页处理中将实际物理页映射至进程页表。
本节讨论 GPU 侧的对应问题:GPU 命令中引用的 BO 地址由何而来。
该问题的答案并非一成不变。过去二十余年间,GPU 寻址硬件经历了数次结构性演进,每一次演进都重新定义了 BO 被 GPU 访问的软件接口,并直接推动了 DRM 中一系列核心机制的形成:drm_mm、BO-list 与 Relocation、基于dma_resv的隐式同步、drm_gpuvm以及 SVM。
本节不展开实现细节,而是以硬件演进为主线,梳理 GPU 视图下的四代寻址模型(如下图),为 3.4.1~3.4.3 及第十、十一章提供整体框架。
1、GPU 视图的三个基本问题
无论硬件代际与驱动实现如何,GPU 访问 BO 均需解决以下三个问题:
- 谁分配地址: BO 的 GPU 地址由内核分配,还是由用户态分配
- 何时生效:地址在何时写入 GPU 命令:分配时、提交时,或在提交前预先绑定
- 如何驻留| GPU 执行期间如何保证 BO 不被换出或迁移,并保证访问顺序正确
后续各代模型均可视为对上述三个问题的不同解答,下文以此为维度逐代分析。
2、第一代:物理直连
硬件特征:GPU 不具备独立的 MMU,直接使用总线物理地址(或经 IOMMU 映射的 DMA 地址)访问内存。早期显卡以及当前多数 SoC 显示控制器、简单 2D 引擎属于此类。
软件契约:由于 GPU 不进行地址翻译,BO 必须物理连续。驱动从 CMA 或预留内存中分配连续区域,并将所得 DMA 地址直接写入寄存器或命令。
DRM 中与之对应的是最简单的后备实现drm_gem_dma_object(4.2 节),其核心字段仅有一个:
structdrm_gem_dma_object{structdrm_gem_objectbase;dma_addr_tdma_addr;/* 设备直接使用的 DMA 地址 */...};该模型下三个问题的解答为:地址由内核物理分配器决定;分配即生效;BO 常驻且不迁移。
其局限同样明显:大块连续内存分配困难,且 GPU 可访问任意物理地址,缺乏进程间隔离。
3、第二代:全局 GART / GTT 与 Relocation
3.1 硬件:全局地址翻译表
为解除物理连续性约束,硬件引入了全局地址翻译表,将分散于系统内存中的物理页映射为 GPU 地址空间中的一段连续区间(即孔径,aperture)。
- 1997 年,AGP 总线在芯片组中引入GART(Graphics Address Remapping Table);
- 进入 PCIe 时代后,该翻译表集成至 GPU 内部,如 radeon 的 PCIE GART 与 Intel 的 GGTT;
- Intel 将其称为GTT(Graphics Translation Table),DRM 中「GTT 内存域」的命名即源于此。
对于独立显卡,VRAM 通过 GPU 本地地址直接访问;系统内存则须先在 GART 中分配区间并写入表项,方可被 GPU 访问。
aperture 地址空间由内核通过通用区间分配器drm_mm管理。在当前内核中,amdgpu 的 GTT 管理器仍采用ttm_resource_manager与drm_mm组合的实现:
structamdgpu_gtt_mgr{structttm_resource_managermanager;structdrm_mmmm;spinlock_tlock;};3.2 软件契约:Relocation
全局 GART 引入了一项关键约束:所有进程共享同一 GPU 地址空间,地址只能由内核统一分配。BO 在 GART 中的位置及其是否会被迁移,对用户态均不可见。
然而,用户态在构造命令时必须引用 BO 地址。为此形成了Relocation(重定位)契约:
- 用户态在命令中预留地址占位,并附带重定位表,每个表项描述「命令缓冲区偏移 N 处应填入 handle 为 H 的 BO 地址加 delta」;
- 同时提交BO-list,列出本次提交引用的全部 BO handle;
- 内核在提交时将各 BO 放置到 GART 中(必要时先行换入),确定最终地址后回填至命令缓冲区。
i915 的execbuffer与 radeon 的 CS 均采用该模型。
在此模型中,GEM core 负责将用户态 handle 数组解析为drm_gem_object指针数组,对应接口为drm_gem_objects_lookup():
intdrm_gem_objects_lookup(structdrm_file*filp,void__user*bo_handles,intcount,structdrm_gem_object***objs_out);3.3 同步:隐式同步
由于内核在每次提交时均持有完整的 BO-list,同步亦可由内核统一完成:每个 BO 关联一个dma_resv,提交前等待其中已有的 fence,提交后将本次作业的 fence 添加至其中。用户态无需显式声明依赖关系,此即隐式同步(7.3 节)。
3.4 局限
Relocation 模型实现简单且安全性可控,但其开销随 GPU 负载增长而显著上升:
- 逐次回填:每次提交均需处理重定位表,draw call 较多时表项可达数千乃至上万,CPU 开销显著;
- 全局地址空间:进程间缺乏硬件隔离,内核须校验命令内容,以防止越权访问其他进程的 BO;
- 地址不稳定:BO 一旦迁移,后续提交须重新回填。
该模型下三个问题的解答为:地址由内核(drm_mm管理 GTT)分配;提交时回填生效;每次提交通过校验 BO-list 保证驻留。3.4.2 将对该模型展开分析。
4、第三代:每进程 GPU VM
4.1 硬件:每上下文独立的 GPU 页表
关键转变源于硬件:GPU 具备了与 CPU 类似的多级页表 MMU,并支持按上下文切换页表。
- AMD 自 Cayman / SI 起引入VMID,为每个进程提供独立的 GPU 虚拟地址空间(radeon 于 2012 年支持 GPU VM);
- Intel 自 Gen6 的 aliasing PPGTT 起步,至 Gen8(2014)实现full PPGTT;
- NVIDIA 自 NV50 起支持 GPU VM。
4.2 软件契约:用户态管理 VA
在每进程地址空间下,GPU 地址无需全局协调,可由用户态自行分配:用户态选定 VA 区间,经绑定接口请求内核将 BO 映射至该区间;此后命令直接引用该 VA,提交时无需回填。
主流驱动的绑定接口如下:
| 驱动 | 绑定方式 | uAPI |
|---|---|---|
| i915 | softpin:用户态指定 BO 的 GPU 地址 | EXEC_OBJECT_PINNED(v4.5) |
| amdgpu | 独立的 VA ioctl | DRM_AMDGPU_GEM_VA |
| nouveau | VM_BIND | DRM_NOUVEAU_VM_BIND |
| Xe | 仅支持 VM_BIND,不支持 relocation | DRM_XE_VM_BIND |
Relocation 已逐步退出主流。i915 在 TGL 之后的平台上明确拒绝 relocation:
/* Relocations are disallowed for all platforms after TGL-LP. This * also covers all platforms with local memory. */if(entry->relocation_count&&GRAPHICS_VER(eb->i915)>=12&&!IS_TIGERLAKE(eb->i915))return-EINVAL;需要指出,各代模型之间并非简单的替代关系。amdgpu 虽通过 VA ioctl 管理地址,CS 时仍需经AMDGPU_CHUNK_ID_BO_HANDLES提交 BO-list,用于驻留校验与 fence 记账(第九章);GART 亦继续存在,转为内核内部使用的映射窗口。
4.3 映射反向索引
地址分配权下放至用户态后,内核需处理新的问题:同一 BO 可被映射至多个 VM 及多个 VA 区间。当 TTM 驱逐或迁移该 BO 时,内核必须定位其全部映射并逐一失效相应页表项,迁移完成后再行重建。
这要求维护「BO → 映射」的反向索引。早期由各驱动自行实现(如 amdgpu 的amdgpu_bo_va);自 v6.6 起,DRM 提供通用框架drm_gpuvm,并在drm_gem_object中引入专用字段:
structdrm_gem_object{...struct{structlist_headlist;/* 所有指向本 BO 的 GPUVM 映射 */structmutexlock;}gpuva;};该模型下三个问题的解答为:地址由用户态分配;绑定后生效,命令直接引用 VA;驱逐时失效映射,迁移后重建。
5、第四代:统一寻址与可恢复缺页
第三代模型仍有一项前提:GPU 可访问的内存必须先以 BO 的形式存在,并经显式绑定。第四代硬件能力消除了这一前提:
- ATS / PASID:GPU 经 IOMMU 直接使用 CPU 进程页表(2015 年 KFD 在 Kaveri APU 上基于 IOMMUv2 实现);
- 可恢复缺页:GPU 访问未映射地址时不再触发致命错误,而是暂停执行并上报缺页,待驱动建立映射后重试(如 AMD GFX9+ 的 retry fault、Intel Xe);
- HMM(v4.14):对于不支持 ATS 的独立显卡,由驱动将 CPU 页表镜像至 GPU 页表,并通过
mmu_notifier维持一致性。
由此,GPU 可直接使用malloc返回的 CPU 指针:GPU 地址与 CPU 进程 VA 一致,映射在 GPU 缺页时建立,数据按需在系统内存与显存之间迁移。KFD SVM(v5.14,2021 年)与通用框架drm_gpusvm(v6.15,2025 年)先后合入主线。
在该模型下,GPU 访问的对象不再限于drm_gem_object,已超出 GEM 的范畴。本节仅作概述,详细内容见第十一章 SVM。
6、各代模型对比
| 谁分配地址 | 何时生效 | 如何驻留 | 关键机制 | |
|---|---|---|---|---|
| 第一代 物理直连 | 内核物理分配器 | 分配即确定 | 常驻,不迁移 | CMA、drm_gem_dma_object |
| 第二代 全局 GART | 内核(drm_mm管 GTT) | 提交时内核回填 | 每次提交校验 BO-list | Relocation、隐式同步 |
| 第三代 每进程 VM | 用户态 | 提前绑定,命令直接用 VA | 驱逐时失效并重建映射 | VM_BIND、drm_gpuvm |
| 第四代 统一寻址 | 与 CPU 进程 VA 一致 | GPU 缺页时建立 | 按需缺页与迁移 | HMM、drm_gpusvm |
由此可归纳出演进的主线:地址分配权由内核逐步下放至用户态,最终与 CPU 地址空间统一;地址生效时机由「提交时」前移至「绑定时」,进而后移至「访问时」。
7、GEM core 在 GPU 视图中的职责
GEM core 并未定义通用的 GPU 绑定接口,这是有意的设计选择:绑定方式随硬件代际差异显著,从 execbuffer、CS 到 GEM_VA、VM_BIND,均以驱动私有 ioctl 实现。GEM core 仅提供三项与硬件代际无关的基础设施:
| 基础设施 | 位置 | 用途 | 详见 |
|---|---|---|---|
| handle → object 查找 | drm_gem_object_lookup()/drm_gem_objects_lookup() | BO-list、绑定 ioctl 的参数解析 | 3.4.2 |
resv | drm_gem_object.resv | 隐式同步、驻留期间的 fence 记账 | 第七章 |
gpuva | drm_gem_object.gpuva | BO → GPU 映射的反向索引 | 3.4.3、第十章 |
GPU 地址空间本身的分配同样不由 GEM 负责,驱动根据需要选用相应的通用分配器:
- GTT aperture:
drm_mm(3.4.1); - VRAM:
drm_buddy(5.7); - GPU 虚拟地址空间:
drm_gpuvm(第十章)。
这与 3.3 节形成对照:在 CPU 视图中,GEM core 提供完整的 offset 与 mmap 机制;在 GPU 视图中,GEM core 仅提供基础设施,具体机制由驱动及上层框架实现。
8、本节结构
GPU 视图的后续三节按硬件演进顺序展开:
- 3.4.1 drm_mm 管理的 GTT 空间在 amdgpu bo 创建中的应用:第二代模型的地址空间管理,分析
drm_mm对 GART 孔径的划分; - 3.4.2 gem_object 如何被 GPU 访问(上):旧模型 BO-list + Relocation + 隐式同步:第二代模型的软件契约;
- 3.4.3 gem_object 如何被 GPU 访问(下):gpuva 字段与映射反向索引:第三代模型在 GEM 侧的数据结构。
第三代模型的完整实现见第十章 GPUVM,第四代模型见第十一章 SVM。BO 跨设备访问的相关内容见3.5 设备间视图。