第三章:GEM分析:gem_object 如何被 GPU 访问(上):旧模型 BO-list + Relocation + 隐式同步

📅 2026/8/11 11:49:12
第三章:GEM分析:gem_object 如何被 GPU 访问(上):旧模型 BO-list + Relocation + 隐式同步
一个drm_gem_object要被 GPU 访问它的 GPU 地址从哪来、何时确定、如何保持有效这是理解gem_object设计的一个很好角度接下来我们就看下上述问题所涉及的技术进化共分两部分本文旧模型地址在每次命令提交时通过 BO 列表临时确定/校验必要时靠 relocation 回填到命令流提交结束后这套定址关系并不持久保留。GPUVM新模型 VM_BIND地址由用户态预先 bind成持久的drm_gpuva映射提交时不再重复定址。1. 核心问题gem_object 凭什么能被 GPU 访问drm_gem_object 讲过drm_gem_object只是对象本身它不包含 GPU 地址。可是 GPU 的着色器/命令流要访问一块 buffer必须通过一个GPU 地址去寻址。于是有一个绕不开的问题一个用户态(CPU侧)只持有 handle 的gem_object是如何在某次提交里获得一个 GPU 能用的地址并真正被 GPU 读写的旧模型的回答是把责任压在每一次命令提交上(内核做的工作)用户态每次提交命令流batch / IB时把本次用到的所有gem_object列成一张BO 列表交给内核内核在提交时把这些对象换入到可寻址位置、确定它们当前的 GPU 地址、地址不稳定时回填命令流、并根据这些对象上的 fence 建立读写同步。提交结束这套临时定址关系并不持久保留。它有两个演进小阶段都以gem_object为主角relocation 阶段早期 i915UMAgem_object的 GPU 地址不稳定内核必须逐条改写命令流里对它的地址引用。BO-list 校验 隐式同步阶段amdgpu及后期 i915 softpingem_object的地址已稳定不再改写命令流但每次提交仍要提交整张 BO 列表做校验与同步。下面按一个gem_object从被列入 BO-list 到被 GPU 访问的路径展开。2. 为什么 gem_object 的地址会不稳定一个gem_object在被 GPU 访问前其后端存储必须位于 GPU 可寻址处显存 / GTT 映射的系统内存。早期模型里对象可能被换入换出、在显存与系统内存间迁移每次提交时它的 GPU 地址可能都不一样。命令流里引用这个对象时写的是具体地址。地址一变命令流里的引用就失效了。于是内核需要一种机制在提交时把命令流里对某个gem_object的地址引用改写成它当前的真实地址——这就是relocation重定位。内核 execbuffer 时用户态构建命令流batch buffer...LOAD_ADDR 占位 ← 指向 texture gem_object...relocation 表{ offset命令流内偏移,targettexture 的 handle,delta }校验/换入 gem_object确定各对象当前 GPU 地址按 reloc 表回填:命令流[offset] 地址(target)delta提交给 GPU 执行2.2 根因在硬件GPU 有没有页表地址不稳定并不是软件设计失误而是早期 GPU 硬件没有独立页表的直接后果。这条软件演进线本质上是硬件先具备某种寻址能力、软件模型才随之改变。按 GPU MMUGPU 页表能力可分三档硬件档位GPU 寻址能力对应的软件模型无 GPU 页表早期消费级 GPU / 固定管线引擎发出的基本是物理地址至多有 GART/AGP 这种单级、全局的地址重映射粒度粗非每进程只能物理/固定定址对象迁移后地址变 →必须 relocation本文第 3 节每进程 GPU 页表GCN / Fermi-Kepler / Gen 之后每个上下文有独立 GPU 虚拟地址空间对象迁移只需更新 PTE虚拟地址不变对象获得稳定 per-VM GPU VA不再需要 relocationamdgpuamdgpu_bo_va本文第 4 节i915 softpin页表更灵活 可恢复缺页Vega / Pascal 之后支持稀疏映射、按页 map/unmap、GPU recoverable page fault、ATS/PRI撑起VM_BIND / drm_gpuvm稳定 VA、稀疏绑定、bind/submit 解耦、按需建映射几个关键点GART/AGP 不是页表它把分散的系统内存页拼成一段连续的 GPU 可见地址解决GPU 能否访问系统内存而不提供每进程隔离的虚拟地址空间所以缓解不了 relocation。地址从不稳定 → 稳定这一步根子是硬件加了每进程页表有了页表这层间接物理页迁移可被 PTE 更新吸收虚拟地址得以恒定。amdgpu 因此从一开始就没走 relocation 路线。可恢复缺页GPU page fault是较晚才成熟的硬件能力它让先给虚拟地址、用到时再填页表成为可能是 immediate mode、SVM/HMM、按需迁移等新方向的硬件基础。一句话软件每一次减负去掉 relocation、解耦 bind 与 submit、支持稀疏背后都是硬件先补上了一层更强的间接寻址能力。本文旧模型对应的正是页表能力尚不完备的阶段。3. 阶段一地址不稳定时——relocation 让 gem_object 可寻址i9153.1 一个 gem_object 如何进入一次提交一次提交DRM_IOCTL_I915_GEM_EXECBUFFER2携带一个exec object 数组——数组里每个元素就代表本次要让 GPU 访问的一个gem_object每个对象再挂一个relocation 数组structdrm_i915_gem_exec_object2{__u32 handle;// BO 的用户态 handle__u32 relocation_count;// 本 BO 内需要重定位的条目数__u64 relocs_ptr;// 指向 relocation_entry 数组__u64 alignment;// 对齐要求__u64 offset;// 出参: 内核回填本 BO 当前 GPU 地址// (NO_RELOC/PINNED 时作为入参: presumed_offset)__u64 flags;// EXEC_OBJECT_WRITE / PINNED / ......};structdrm_i915_gem_relocation_entry{__u32 target_handle;// 被引用的目标 BO__u32 delta;// 加到目标地址上的偏移__u64 offset;// 在本 BO内、要被改写的位置__u64 presumed_offset;// 用户态猜测的目标地址__u32 read_domains;// 目标被读的内存域__u32 write_domain;// 目标被写的内存域(全局至多一个写域)};3.2 内核处理步骤收集与校验把 exec 列表里所有 BO 换入到可寻址位置确定各自当前 GPU 地址。列表顺序有约束——一个 BO 的 relocation 只能引用已在列表中出现过的 BO。重定位对每条 relocation计算目标地址 delta写入本 BO 的offset处。presumed_offset 优化如果某 BO 这次的地址与presumed_offset相同说明地址没变可跳过这条重定位的改写与相关同步。I915_EXEC_NO_RELOC让用户态承诺地址不变整表跳过。domain 跟踪根据read_domains/write_domain决定需要的 cache flush / invalidate。提交并回写把 batch 提交给 GPU把各 BO 的当前 GPU 地址回填到offset字段供下次作presumed_offset。3.3 softpinrelocation 的落幕后来引入EXEC_OBJECT_PINNEDsoftpin用户态自己选定并固定 BO 的 GPU 地址内核不再改写命令流。这实际上是稳定地址思路的雏形也是通往 VM_BIND 的过渡。至此relocation 对新用户态基本退役DRM_IOCTL_I915_GEM_EXECBUFFER老版在 5.13 被移除。4. 阶段二地址已稳定——amdgpu 用 BO-list 隐式同步访问 gem_objectamdgpu 从一开始就给每个gem_object在每进程的 GPU 虚拟地址空间amdgpu_vm里建立持久绑定通过amdgpu_bo_va把对象绑定到稳定的 GPU VA。因此 amdgpu不需要 relocation——命令流可以直接写死这个稳定地址。值得注意amdgpu_bo_va对象 × VM 的持久绑定正是后来通用drm_gpuva/drm_gpum_bo的思想雏形。也就是说让 gem_object 拥有持久 GPU VA这个方向amdgpu 早已走在半途——这条演进线的终点就是 gpuva。但 amdgpu 仍属旧模型因为每次命令提交CS仍要把用到的gem_object列成一张 BO 列表交给内核校验与同步structdrm_amdgpu_bo_list_entry{__u32 bo_handle;// BO handle__u32 bo_priority;// 迁移时的优先级提示};BO-list 可以预先创建DRM_IOCTL_AMDGPU_BO_LIST拿到一个 handle 复用或随每次 CS 用AMDGPU_CHUNK_ID_BO_HANDLES内联携带。一次提交DRM_IOCTL_AMDGPU_CS由若干chunk组成Chunk含义AMDGPU_CHUNK_ID_IB间接缓冲命令流入口AMDGPU_CHUNK_ID_BO_HANDLES本次提交涉及的 BO 列表AMDGPU_CHUNK_ID_DEPENDENCIES显式依赖的 fenceAMDGPU_CHUNK_ID_SYNCOBJ_IN/OUT输入/输出 syncobjAMDGPU_CHUNK_ID_FENCE写回完成 fence内核 CS 处理时会锁住并校验 BO-list 里所有gem_object是否需回迁/换入到可寻址处、把它们的dma_resv纳入调度、按对象上的既有 fence 建立隐式同步最后把 IB 交给调度器。4.1 隐式同步是什么隐式同步implicit sync指内核自动根据哪些gem_object被读、哪些被写在提交之间插入等待用户态不必显式声明依赖。机制上依赖每个对象的dma_resv保存 read/write fence见 gem_object.md 2.5。读操作要等该 BO 上所有写 fence完成写操作要等该 BO 上所有读 fence 和写 fence完成并把自己的完成 fence 记为新的写 fence。i915 的EXEC_OBJECT_WRITE标志、amdgpu 从 BO-list dma_resv推导都是这一机制的体现。它的粒度是整个 BO——这正是后面要讲的痛点之一。5. 一个 gem_object 被 GPU 访问的完整时序relocation 版GPU内存管理 (TTM/GTT)内核 (execbuffer/CS)用户态 (Mesa/UMD)GPU内存管理 (TTM/GTT)内核 (execbuffer/CS)用户态 (Mesa/UMD)loop[每条 relocation]构建 batch遇跨对象引用处留占位并记 relocEXECBUFFER2(exec_object[] reloc[])校验/换入所有 gem_object确定各自 GPU 地址各 gem_object 当前地址命令流[offset] 地址(target)delta依据 read/write domain 做 cache flush/invalidate按各 gem_object 的 dma_resv 建立隐式同步提交 batchGPU 此时才真正访问这些 gem_object回填各 gem_object offset(供下次 presumed_offset)完成 fence6. 从 gem_object 视角看旧模型的痛点痛点说明提交开销大每个gem_object每次提交都要走一遍入列表 → 加锁 → 校验 →reloc 版还要改写命令流。对象越多越慢。定址与提交耦合gem_object的地址是在提交时才确定/校验的用户态无法为它预先规划稳定、持久的地址布局。隐式同步粒度粗同步以整个gem_object为单位。子分配suballocation场景下一个对象内的不同区间本可并行却被当成整体强行串行。i915 为此提供EXEC_OBJECT_ASYNC等旁路。无法表达部分/稀疏绑定无法把一个gem_object的不同子段、或一段虚拟区间分别绑到不同对象、或留空洞——正是 Vulkan Sparse Resources 需要的能力。relocation 开销与串行化内核为定址改写命令流需要 CPU 触碰对象、可能引发额外同步presumed_offset只能缓解、不能消除。难以并行化 bind对象的定址与提交混在一条路径上无法把绑定这件事异步化、批量化。7. 新旧模型对照演进新模型: VM_BIND drm_gpuvmbind/unbind 与 submit 解耦用户态规划稳定 GPU VA多用显式同步(syncobj/fence)支持稀疏/部分绑定旧模型: BO-list (可选)relocation每次 submit 提交完整 BO 列表地址在 submit 时确定/校验隐式同步, 粒度整个 BO无法稀疏/部分绑定维度旧模型新模型 (VM_BIND)地址确定时机每次 submit预先 bind一次绑定长期有效submit 携带内容完整 BO 列表仅命令流 同步原语地址是否稳定早期不稳定(需 reloc)/后期稳定稳定由用户态规划同步方式以隐式为主以显式(syncobj)为主稀疏/部分绑定不支持支持(sparse)内核公共框架各驱动自研drm_gpuvm/drm_gpuva8. 演进时间线以 gem_object 定址方式为线索relocation 时代i915execbuffer/execbuffer2gem_object地址不稳定内核改写命令流。softpinEXEC_OBJECT_PINNED用户态为gem_object固定地址relocation 退居其次。per-VM 稳定地址amdgpu 的amdgpu_vm/amdgpu_bo_va对象地址稳定但仍每次提交 BO-list 隐式同步。VM_BIND drm_gpuvmLinux 6.6 引入通用 GPUVM 框架详见 drm_gpuvmgem_object拥有由用户态预先绑定的持久 GPU VAbind/submit 解耦、显式同步、支持稀疏绑定。9. 小结与演进衔接从gem_object的视角看旧模型的本质是把对象的定址与同步责任压在每一次命令提交上早期i915 UMA对象地址不稳定还要靠 relocation 逐条改写命令流后期amdgpu虽借amdgpu_bo_va让对象拥有稳定 GPU VA但仍需每次提交整张 BO 列表、做整对象粒度的隐式同步。这在对象数量大、需要稀疏绑定、追求低提交开销的现代工作负载尤其 Vulkan下难以为继。于是演进的方向自然浮现——把gem_object 的定址从提交路径里拆出来做成持久、可查询、可增量维护的映射本文旧模型gem_object每次提交临时定址BO-list relocation 隐式同步。amdgpu 的amdgpu_bo_va对象获得持久 GPU VA但绑定仍与提交/BO-list 纠缠——是承上启下的一步。gpuva新模型用户态预先VM_BINDdrm_gpuvm把对象 → 持久 GPU VA 映射提取为通用框架提交只带命令 显式同步。gem_object_gpuva回到gem_object自身看它如何用gpuva字段反向记录指向自己的全部持久映射。给个一句话的总结gem_object被 GPU 访问的方式从每次提交临时定址演进到预先绑定持久 GPU VA。