一个 drm_gem_object 要被 GPU 访问,它的 GPU 地址从哪来、何时确定、如何保持有效?
这是理解gem_object设计的一个很好角度,接下来我们就看下上述问题所涉及的技术进化,共分两部分:
-
- 本文(旧模型):地址在每次命令提交时,通过 BO 列表临时确定/校验,必要时靠 relocation 回填到命令流;提交结束后这套定址关系并不持久保留。
-
- GPUVM(新模型 VM_BIND):地址由用户态预先 bind 成持久的
drm_gpuva映射,提交时不再重复定址。
- GPUVM(新模型 VM_BIND):地址由用户态预先 bind 成持久的
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 阶段 (早期 i915,UMA):
gem_object的 GPU 地址不稳定,内核必须逐条改写命令流里对它的地址引用。 - BO-list 校验 + 隐式同步阶段 (amdgpu,及后期 i915 softpin):
gem_object的地址已稳定 ,不再改写命令流,但每次提交仍要提交整张 BO 列表做校验与同步。
下面按"一个 gem_object 从被列入 BO-list 到被 GPU 访问"的路径展开。
2. 为什么 gem_object 的地址会"不稳定"
一个 gem_object 在被 GPU 访问前,其后端存储必须位于 GPU 可寻址处(显存 / GTT 映射的系统内存)。早期模型里:
- 对象可能被换入换出、在显存与系统内存间迁移,每次提交时它的 GPU 地址可能都不一样。
- 命令流里引用这个对象时写的是具体地址。地址一变,命令流里的引用就失效了。
于是内核需要一种机制,在提交时把命令流里"对某个 gem_object 的地址引用"改写成它当前的真实地址------这就是 relocation(重定位)。
#mermaid-svg-Bby4nPVJ1PuVgOje{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Bby4nPVJ1PuVgOje .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Bby4nPVJ1PuVgOje .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Bby4nPVJ1PuVgOje .error-icon{fill:#552222;}#mermaid-svg-Bby4nPVJ1PuVgOje .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Bby4nPVJ1PuVgOje .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Bby4nPVJ1PuVgOje .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Bby4nPVJ1PuVgOje .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Bby4nPVJ1PuVgOje .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Bby4nPVJ1PuVgOje .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Bby4nPVJ1PuVgOje .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Bby4nPVJ1PuVgOje .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Bby4nPVJ1PuVgOje .marker.cross{stroke:#333333;}#mermaid-svg-Bby4nPVJ1PuVgOje svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Bby4nPVJ1PuVgOje p{margin:0;}#mermaid-svg-Bby4nPVJ1PuVgOje .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Bby4nPVJ1PuVgOje .cluster-label text{fill:#333;}#mermaid-svg-Bby4nPVJ1PuVgOje .cluster-label span{color:#333;}#mermaid-svg-Bby4nPVJ1PuVgOje .cluster-label span p{background-color:transparent;}#mermaid-svg-Bby4nPVJ1PuVgOje .label text,#mermaid-svg-Bby4nPVJ1PuVgOje span{fill:#333;color:#333;}#mermaid-svg-Bby4nPVJ1PuVgOje .node rect,#mermaid-svg-Bby4nPVJ1PuVgOje .node circle,#mermaid-svg-Bby4nPVJ1PuVgOje .node ellipse,#mermaid-svg-Bby4nPVJ1PuVgOje .node polygon,#mermaid-svg-Bby4nPVJ1PuVgOje .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Bby4nPVJ1PuVgOje .rough-node .label text,#mermaid-svg-Bby4nPVJ1PuVgOje .node .label text,#mermaid-svg-Bby4nPVJ1PuVgOje .image-shape .label,#mermaid-svg-Bby4nPVJ1PuVgOje .icon-shape .label{text-anchor:middle;}#mermaid-svg-Bby4nPVJ1PuVgOje .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Bby4nPVJ1PuVgOje .rough-node .label,#mermaid-svg-Bby4nPVJ1PuVgOje .node .label,#mermaid-svg-Bby4nPVJ1PuVgOje .image-shape .label,#mermaid-svg-Bby4nPVJ1PuVgOje .icon-shape .label{text-align:center;}#mermaid-svg-Bby4nPVJ1PuVgOje .node.clickable{cursor:pointer;}#mermaid-svg-Bby4nPVJ1PuVgOje .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Bby4nPVJ1PuVgOje .arrowheadPath{fill:#333333;}#mermaid-svg-Bby4nPVJ1PuVgOje .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Bby4nPVJ1PuVgOje .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Bby4nPVJ1PuVgOje .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Bby4nPVJ1PuVgOje .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Bby4nPVJ1PuVgOje .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Bby4nPVJ1PuVgOje .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Bby4nPVJ1PuVgOje .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Bby4nPVJ1PuVgOje .cluster text{fill:#333;}#mermaid-svg-Bby4nPVJ1PuVgOje .cluster span{color:#333;}#mermaid-svg-Bby4nPVJ1PuVgOje div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Bby4nPVJ1PuVgOje .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Bby4nPVJ1PuVgOje rect.text{fill:none;stroke-width:0;}#mermaid-svg-Bby4nPVJ1PuVgOje .icon-shape,#mermaid-svg-Bby4nPVJ1PuVgOje .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Bby4nPVJ1PuVgOje .icon-shape p,#mermaid-svg-Bby4nPVJ1PuVgOje .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Bby4nPVJ1PuVgOje .icon-shape .label rect,#mermaid-svg-Bby4nPVJ1PuVgOje .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Bby4nPVJ1PuVgOje .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Bby4nPVJ1PuVgOje .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Bby4nPVJ1PuVgOje :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 内核 execbuffer 时
用户态构建命令流
batch buffer
...
LOAD_ADDR <占位> ← 指向 texture gem_object
...
relocation 表
{ offset=命令流内偏移,
target=texture 的 handle,
delta }
校验/换入 gem_object
确定各对象当前 GPU 地址
按 reloc 表回填:
命令流offset = 地址(target)+delta
提交给 GPU 执行
2.2 根因在硬件:GPU 有没有页表
地址"不稳定"并不是软件设计失误,而是早期 GPU 硬件没有独立页表的直接后果。这条软件演进线,本质上是"硬件先具备某种寻址能力、软件模型才随之改变"。按 GPU MMU(GPU 页表)能力可分三档:
| 硬件档位 | GPU 寻址能力 | 对应的软件模型 |
|---|---|---|
| 无 GPU 页表(早期消费级 GPU / 固定管线) | 引擎发出的基本是物理地址;至多有 GART/AGP 这种单级、全局的地址重映射(粒度粗,非每进程) | 只能物理/固定定址,对象迁移后地址变 → 必须 relocation(本文第 3 节) |
| 每进程 GPU 页表(GCN / Fermi-Kepler / Gen 之后) | 每个上下文有独立 GPU 虚拟地址空间,对象迁移只需更新 PTE,虚拟地址不变 | 对象获得稳定 per-VM GPU VA ,不再需要 relocation (amdgpu amdgpu_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 可寻址(i915)
3.1 一个 gem_object 如何进入一次提交
一次提交(DRM_IOCTL_I915_GEM_EXECBUFFER2)携带一个 exec object 数组 ------数组里每个元素就代表"本次要让 GPU 访问的一个 gem_object",每个对象再挂一个 relocation 数组:
c
struct drm_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 / ...
...
};
struct drm_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 softpin:relocation 的落幕
后来引入 EXEC_OBJECT_PINNED(softpin):用户态自己选定并固定 BO 的 GPU 地址,内核不再改写命令流。这实际上是"稳定地址"思路的雏形,也是通往 VM_BIND 的过渡。至此,relocation 对新用户态基本退役(DRM_IOCTL_I915_GEM_EXECBUFFER 老版在 5.13 被移除)。
4. 阶段二:地址已稳定------amdgpu 用 BO-list + 隐式同步访问 gem_object
amdgpu 从一开始就给每个 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 列表交给内核校验与同步:
c
struct drm_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 |
显式依赖的 fence |
AMDGPU_CHUNK_ID_SYNCOBJ_IN/OUT |
输入/输出 syncobj |
AMDGPU_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) #mermaid-svg-8k54mNCgoZMzI8uy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8k54mNCgoZMzI8uy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8k54mNCgoZMzI8uy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8k54mNCgoZMzI8uy .error-icon{fill:#552222;}#mermaid-svg-8k54mNCgoZMzI8uy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8k54mNCgoZMzI8uy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8k54mNCgoZMzI8uy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8k54mNCgoZMzI8uy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8k54mNCgoZMzI8uy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8k54mNCgoZMzI8uy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8k54mNCgoZMzI8uy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8k54mNCgoZMzI8uy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8k54mNCgoZMzI8uy .marker.cross{stroke:#333333;}#mermaid-svg-8k54mNCgoZMzI8uy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8k54mNCgoZMzI8uy p{margin:0;}#mermaid-svg-8k54mNCgoZMzI8uy .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-8k54mNCgoZMzI8uy text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-8k54mNCgoZMzI8uy .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-8k54mNCgoZMzI8uy .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-8k54mNCgoZMzI8uy .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-8k54mNCgoZMzI8uy .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-8k54mNCgoZMzI8uy #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-8k54mNCgoZMzI8uy .sequenceNumber{fill:white;}#mermaid-svg-8k54mNCgoZMzI8uy #sequencenumber{fill:#333;}#mermaid-svg-8k54mNCgoZMzI8uy #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-8k54mNCgoZMzI8uy .messageText{fill:#333;stroke:none;}#mermaid-svg-8k54mNCgoZMzI8uy .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-8k54mNCgoZMzI8uy .labelText,#mermaid-svg-8k54mNCgoZMzI8uy .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-8k54mNCgoZMzI8uy .loopText,#mermaid-svg-8k54mNCgoZMzI8uy .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-8k54mNCgoZMzI8uy .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-8k54mNCgoZMzI8uy .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-8k54mNCgoZMzI8uy .noteText,#mermaid-svg-8k54mNCgoZMzI8uy .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-8k54mNCgoZMzI8uy .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8k54mNCgoZMzI8uy .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8k54mNCgoZMzI8uy .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8k54mNCgoZMzI8uy .actorPopupMenu{position:absolute;}#mermaid-svg-8k54mNCgoZMzI8uy .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-8k54mNCgoZMzI8uy .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-8k54mNCgoZMzI8uy .actor-man circle,#mermaid-svg-8k54mNCgoZMzI8uy line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-8k54mNCgoZMzI8uy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} loop 每条 relocation 构建 batch,遇跨对象引用处留占位并记 reloc EXECBUFFER2(exec_object\[\] + reloc\[\]) 校验/换入所有 gem_object,确定各自 GPU 地址 各 gem_object 当前地址 命令流offset = 地址(target)+delta 依据 read/write domain 做 cache flush/invalidate 按各 gem_object 的 dma_resv 建立隐式同步 提交 batch(GPU 此时才真正访问这些 gem_object) 回填各 gem_object offset(供下次 presumed_offset) 完成 fence
6. 从 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. 新旧模型对照
#mermaid-svg-tVCHWkmFNZl5xND1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tVCHWkmFNZl5xND1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tVCHWkmFNZl5xND1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tVCHWkmFNZl5xND1 .error-icon{fill:#552222;}#mermaid-svg-tVCHWkmFNZl5xND1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tVCHWkmFNZl5xND1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tVCHWkmFNZl5xND1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tVCHWkmFNZl5xND1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tVCHWkmFNZl5xND1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tVCHWkmFNZl5xND1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tVCHWkmFNZl5xND1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tVCHWkmFNZl5xND1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tVCHWkmFNZl5xND1 .marker.cross{stroke:#333333;}#mermaid-svg-tVCHWkmFNZl5xND1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tVCHWkmFNZl5xND1 p{margin:0;}#mermaid-svg-tVCHWkmFNZl5xND1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tVCHWkmFNZl5xND1 .cluster-label text{fill:#333;}#mermaid-svg-tVCHWkmFNZl5xND1 .cluster-label span{color:#333;}#mermaid-svg-tVCHWkmFNZl5xND1 .cluster-label span p{background-color:transparent;}#mermaid-svg-tVCHWkmFNZl5xND1 .label text,#mermaid-svg-tVCHWkmFNZl5xND1 span{fill:#333;color:#333;}#mermaid-svg-tVCHWkmFNZl5xND1 .node rect,#mermaid-svg-tVCHWkmFNZl5xND1 .node circle,#mermaid-svg-tVCHWkmFNZl5xND1 .node ellipse,#mermaid-svg-tVCHWkmFNZl5xND1 .node polygon,#mermaid-svg-tVCHWkmFNZl5xND1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tVCHWkmFNZl5xND1 .rough-node .label text,#mermaid-svg-tVCHWkmFNZl5xND1 .node .label text,#mermaid-svg-tVCHWkmFNZl5xND1 .image-shape .label,#mermaid-svg-tVCHWkmFNZl5xND1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-tVCHWkmFNZl5xND1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tVCHWkmFNZl5xND1 .rough-node .label,#mermaid-svg-tVCHWkmFNZl5xND1 .node .label,#mermaid-svg-tVCHWkmFNZl5xND1 .image-shape .label,#mermaid-svg-tVCHWkmFNZl5xND1 .icon-shape .label{text-align:center;}#mermaid-svg-tVCHWkmFNZl5xND1 .node.clickable{cursor:pointer;}#mermaid-svg-tVCHWkmFNZl5xND1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tVCHWkmFNZl5xND1 .arrowheadPath{fill:#333333;}#mermaid-svg-tVCHWkmFNZl5xND1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tVCHWkmFNZl5xND1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tVCHWkmFNZl5xND1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tVCHWkmFNZl5xND1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tVCHWkmFNZl5xND1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tVCHWkmFNZl5xND1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tVCHWkmFNZl5xND1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tVCHWkmFNZl5xND1 .cluster text{fill:#333;}#mermaid-svg-tVCHWkmFNZl5xND1 .cluster span{color:#333;}#mermaid-svg-tVCHWkmFNZl5xND1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tVCHWkmFNZl5xND1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tVCHWkmFNZl5xND1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-tVCHWkmFNZl5xND1 .icon-shape,#mermaid-svg-tVCHWkmFNZl5xND1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tVCHWkmFNZl5xND1 .icon-shape p,#mermaid-svg-tVCHWkmFNZl5xND1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tVCHWkmFNZl5xND1 .icon-shape .label rect,#mermaid-svg-tVCHWkmFNZl5xND1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tVCHWkmFNZl5xND1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tVCHWkmFNZl5xND1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tVCHWkmFNZl5xND1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 演进
新模型: VM_BIND + drm_gpuvm
bind/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_gpuva |
8. 演进时间线(以 gem_object 定址方式为线索)
- relocation 时代 :i915
execbuffer/execbuffer2,gem_object地址不稳定,内核改写命令流。 - softpin :
EXEC_OBJECT_PINNED,用户态为gem_object固定地址,relocation 退居其次。 - per-VM 稳定地址 :amdgpu 的
amdgpu_vm/amdgpu_bo_va,对象地址稳定但仍每次提交 BO-list + 隐式同步。 - VM_BIND + drm_gpuvm :Linux 6.6 引入通用 GPUVM 框架(详见 drm_gpuvm),
gem_object拥有由用户态预先绑定的持久 GPU VA,bind/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_BIND,drm_gpuvm把"对象 → 持久 GPU VA 映射"提取为通用框架,提交只带命令 + 显式同步。 - gem_object_gpuva :回到
gem_object自身,看它如何用gpuva字段反向记录"指向自己的全部持久映射"。
给个一句话的总结:gem_object 被 GPU 访问的方式,从"每次提交临时定址"演进到"预先绑定持久 GPU VA"。