前文GPUVM:同一个灵魂,两副躯体——GPU 内存管理为何是 CPU MM 的“平行宇宙”给出了一个理解 GPUVM 的 viewpoint,本节开始进入这个新世界。首先要明确的是,这个新世界是因为GPU硬件的发展所带来的。
1. 新需求:从"内核代管映射"到 VM_BIND
1.1 旧模型的局限
传统的 GPU 内存提交模型是 BO-list + 隐式重定位(relocation):用户空间每次提交命令时,把用到的 BO 列成一个 list 交给内核,内核在提交时决定/校验这些 BO 的 GPU 虚拟地址,并在必要时回填命令流。这个模型有几个根本问题:
- 每次提交都要处理整张 BO 列表,地址绑定与命令提交强耦合,开销大。
- 无法表达"部分绑定":Vulkan 的稀疏资源(Sparse Resources)要求把一个巨大的虚拟地址区间的不同子区间,分别绑定到不同 BO 的不同偏移,甚至留空洞。
- 地址空间由内核隐式管理,用户态无法预先规划稳定的 GPU 虚拟地址布局。
1.2 新需求:显式的 GPU 地址空间管理(VM_BIND)
现代 Vulkan/计算驱动改用 VM_BIND 模型:
- 用户空间显式地把 [GPU_VA, GPU_VA+range) 绑定 / 解绑到某个 (GEM 对象, offset),与命令提交解耦。
- 支持稀疏绑定:任意子区间独立 map/unmap,允许空洞。
- 地址空间由用户态规划,内核只负责维护"当前有哪些映射"以及据此更新 GPU 页表。
这带来一个新的、所有支持 VM_BIND 的驱动都要解决的公共问题:
如何在内核里高效地表示、查询、以及增量维护"一个 GPU 虚拟地址空间中的全部映射",并在用户请求 map/unmap 时正确地对已有映射做拆分与合并?
在 GPUVM 出现前,nouveau、Xe 等驱动各自实现这套逻辑,代码高度重复且容易出错。drm_gpuvm 就是把它提取为 DRM core 的通用框架——定位与 drm_gem_object 提取"对象管理"完全对称,只不过这里提取的是"地址空间管理"。
2. 设计思考
2.1 三个核心抽象
框架用三个结构体分工,各自回答一个问题:
| drm_gpuvm | 一个 GPU 虚拟地址空间里有哪些映射? | struct mm_struct |
| drm_gpuva | 某个 VA 区间映射到哪个对象的哪段偏移? | struct vm_area_struct |
| drm_gpuvm_bo | 某个对象在某个 VM 中的全部映射((BO,VM) 组合) | 无直接对应,是反查加速结构 |
2.2 “只提供机制,不强加策略”
和 GEM 一样,GPUVM 坚持框架化设计:
- 不分配内存、不碰页表:框架只维护"软件视图"(哪些 VA 区间被占、指向谁)。真正的页表更新由驱动在回调里完成。drm_gpuvm / drm_gpuva 都被设计为嵌入驱动自己的结构体,框架自身不做 drm_gpuva 的分配。
- 可在 dma-fence 信号临界区安全运行:由于插入 drm_gpuva 所需的数据结构已内联在结构体里,驱动只要预分配 drm_gpuva,就能在 fence 信号路径(不允许分配内存)中安全地更新地址空间视图。
- 地址/范围单位无关:框架不假设单位是字节还是页,驱动自行约定。
2.3 拆分与合并(Split & Merge)——框架的核心价值
VM_BIND 的难点在于:一个新的 map/unmap 请求可能与已有映射任意重叠。框架把"如何把新请求干净地整合进现有地址空间"抽象成一个算法,输出一串操作序列:
- 对一个 map 请求,最多产生:任意数量的 unmap + 至多两个 remap(头尾各一个被切开的旧映射)+ 一个 map。
- 对一个 unmap 请求:区间内完全包含的旧映射产生 unmap;仅部分重叠的旧映射产生 remap(切开后保留未覆盖部分)。
驱动拿到这串操作后,据此做增量页表更新(delta update),而不必推倒重来。drm_gpuva_op_unmap.keep 字段还提示:被拆掉的映射与原请求物理连续,其 PTE 可以保留复用。
2.4 两种消费方式:回调 vs. 操作列表
框架提供两条使用路径,权衡"内存分配"与"可重复迭代":
- drm_gpuvm_sm_map() / drm_gpuvm_sm_unmap():直接回调驱动(drm_gpuvm_ops 的 sm_step_map/remap/unmap)。不额外分配,适合简单场景。
- drm_gpuvm_sm_map_ops_create() / …_unmap_ops_create():把操作序列物化成一个 drm_gpuva_ops 链表,可多次迭代。典型用法是在允许分配内存的上下文里迭代一次(分配页表、预分配 drm_gpuva),再在 dma-fence 信号临界区迭代一次(真正提交)。代价是要为 ops 分配内存。
2.5 锁与两种模式
- 默认(resv-protected):drm_gem_object.gpuva.list 由该 GEM 的 dma_resv 锁保护。VM 私有对象可共享一个 dma_resv(resv object),从而用一把锁 + drm_exec 高效锁住整组对象。
- DRM_GPUVM_IMMEDIATE_MODE(6.18 引入):为"在 fence 信号路径中修改 GPUVM"设计,此时 gpuva.list 由专门的 gpuva.lock mutex 保护(不能在持锁时分配内存)。
2.6 外部对象与被驱逐对象列表
drm_gpuvm_bo 因为对 (VM, BO) 唯一,被复用为两张加速列表的表项:
- 外部对象(extobj)列表:dma_resv 与 VM 公共 dma_resv 不同的对象(即共享/导入对象)。加锁时需要单独处理。
- 被驱逐对象(evicted)列表:被 TTM 驱逐、需要在下次提交前重新校验/回迁的对象。
drm_gpuvm_exec_lock() + drm_gpuvm_validate() 借助这两张表,能一次性锁住并校验一个 VM 关联的所有对象,避免遍历全部映射。
3. 数据结构与关系
3.1 drm_gpuva:一条映射
struct drm_gpuva {
struct drm_gpuvm *vm; // 所属地址空间
struct drm_gpuvm_bo *vm_bo; // (BO, VM) 组合抽象
enum drm_gpuva_flags flags; // INVALIDATED / SPARSE / USERBITS
struct { u64 addr; u64 range; } va; // GPU VA 区间 [addr, addr+range)
struct { u64 offset; struct drm_gem_object *obj; // 映射到哪个对象的哪段偏移
struct list_head entry; } gem; // 挂到 drm_gpuvm_bo 的链表
struct { struct rb_node node; // 插入区间树
struct list_head entry; u64 __subtree_last; } rb;
};
- va + rb:让映射能按地址被查找(drm_gpuva_find*)与遍历。
- gem:obj+offset 精确指向对象内一段;entry 让"从对象反查其全部映射"成为可能。
- flags:DRM_GPUVA_INVALIDATED 表示后端 BO 已失效(被驱逐,页表需重建);DRM_GPUVA_SPARSE 表示稀疏映射。
3.2 三层结构关系图
#mermaid-svg-XDNSCtn88uJ0YLJH{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-XDNSCtn88uJ0YLJH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XDNSCtn88uJ0YLJH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XDNSCtn88uJ0YLJH .error-icon{fill:#552222;}#mermaid-svg-XDNSCtn88uJ0YLJH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XDNSCtn88uJ0YLJH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XDNSCtn88uJ0YLJH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XDNSCtn88uJ0YLJH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XDNSCtn88uJ0YLJH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XDNSCtn88uJ0YLJH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XDNSCtn88uJ0YLJH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XDNSCtn88uJ0YLJH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XDNSCtn88uJ0YLJH .marker.cross{stroke:#333333;}#mermaid-svg-XDNSCtn88uJ0YLJH svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XDNSCtn88uJ0YLJH p{margin:0;}#mermaid-svg-XDNSCtn88uJ0YLJH .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-XDNSCtn88uJ0YLJH .cluster-label text{fill:#333;}#mermaid-svg-XDNSCtn88uJ0YLJH .cluster-label span{color:#333;}#mermaid-svg-XDNSCtn88uJ0YLJH .cluster-label span p{background-color:transparent;}#mermaid-svg-XDNSCtn88uJ0YLJH .label text,#mermaid-svg-XDNSCtn88uJ0YLJH span{fill:#333;color:#333;}#mermaid-svg-XDNSCtn88uJ0YLJH .node rect,#mermaid-svg-XDNSCtn88uJ0YLJH .node circle,#mermaid-svg-XDNSCtn88uJ0YLJH .node ellipse,#mermaid-svg-XDNSCtn88uJ0YLJH .node polygon,#mermaid-svg-XDNSCtn88uJ0YLJH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XDNSCtn88uJ0YLJH .rough-node .label text,#mermaid-svg-XDNSCtn88uJ0YLJH .node .label text,#mermaid-svg-XDNSCtn88uJ0YLJH .image-shape .label,#mermaid-svg-XDNSCtn88uJ0YLJH .icon-shape .label{text-anchor:middle;}#mermaid-svg-XDNSCtn88uJ0YLJH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XDNSCtn88uJ0YLJH .rough-node .label,#mermaid-svg-XDNSCtn88uJ0YLJH .node .label,#mermaid-svg-XDNSCtn88uJ0YLJH .image-shape .label,#mermaid-svg-XDNSCtn88uJ0YLJH .icon-shape .label{text-align:center;}#mermaid-svg-XDNSCtn88uJ0YLJH .node.clickable{cursor:pointer;}#mermaid-svg-XDNSCtn88uJ0YLJH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XDNSCtn88uJ0YLJH .arrowheadPath{fill:#333333;}#mermaid-svg-XDNSCtn88uJ0YLJH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XDNSCtn88uJ0YLJH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XDNSCtn88uJ0YLJH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XDNSCtn88uJ0YLJH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XDNSCtn88uJ0YLJH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XDNSCtn88uJ0YLJH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XDNSCtn88uJ0YLJH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XDNSCtn88uJ0YLJH .cluster text{fill:#333;}#mermaid-svg-XDNSCtn88uJ0YLJH .cluster span{color:#333;}#mermaid-svg-XDNSCtn88uJ0YLJH 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-XDNSCtn88uJ0YLJH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XDNSCtn88uJ0YLJH rect.text{fill:none;stroke-width:0;}#mermaid-svg-XDNSCtn88uJ0YLJH .icon-shape,#mermaid-svg-XDNSCtn88uJ0YLJH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XDNSCtn88uJ0YLJH .icon-shape p,#mermaid-svg-XDNSCtn88uJ0YLJH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XDNSCtn88uJ0YLJH .icon-shape .label rect,#mermaid-svg-XDNSCtn88uJ0YLJH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XDNSCtn88uJ0YLJH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XDNSCtn88uJ0YLJH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XDNSCtn88uJ0YLJH :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
按地址组织 (区间树)
rb-tree / maple-tree
gem.entry
gem.entry
gem.entry
引用/挂载
引用/挂载
extobj / evicted 列表
drm_gpuvm(一个 GPU 虚拟地址空间)
drm_gpuva #1[a, +r) → obj_A@off0
drm_gpuva #2[b, +r) → obj_B@off0
drm_gpuva #3[c, +r) → obj_A@off1
drm_gpuvm_bo(obj_A, vm)
drm_gpuvm_bo(obj_B, vm)
drm_gem_object A(gpuva.list)
drm_gem_object B(gpuva.list)
- drm_gpuvm:整个地址空间,内部用区间树(新版用 maple tree)索引全部 drm_gpuva;含一个代表内核保留区的特殊 drm_gpuva 节点。
- drm_gpuvm_bo:(BO, VM) 组合的唯一抽象,聚合该对象在该 VM 中的所有映射,同时充当 extobj/evicted 列表表项。通过 drm_gpuvm_bo_obtain() 获取(存在则复用,否则新建)。
- drm_gpuva:一条映射,同时挂在 drm_gpuvm(按 VA 查)和 drm_gpuvm_bo(按对象查)两处。
4. Split & Merge 操作模型
enum drm_gpuva_op_type { DRM_GPUVA_OP_MAP, REMAP, UNMAP, PREFETCH, DRIVER };
struct drm_gpuva_op_map { struct { u64 addr, range; } va;
struct { u64 offset; struct drm_gem_object *obj; } gem; };
struct drm_gpuva_op_unmap { struct drm_gpuva *va; bool keep; };
struct drm_gpuva_op_remap { struct drm_gpuva_op_map *prev, *next; // 头尾保留段
struct drm_gpuva_op_unmap *unmap; }; // 被切开的旧映射
一个 map 请求整合进现有空间时,典型形态(新映射覆盖并切开一个更大的旧映射):
#mermaid-svg-4YfvIGf9Zm0lV4HL{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-4YfvIGf9Zm0lV4HL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4YfvIGf9Zm0lV4HL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4YfvIGf9Zm0lV4HL .error-icon{fill:#552222;}#mermaid-svg-4YfvIGf9Zm0lV4HL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4YfvIGf9Zm0lV4HL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4YfvIGf9Zm0lV4HL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4YfvIGf9Zm0lV4HL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4YfvIGf9Zm0lV4HL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4YfvIGf9Zm0lV4HL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4YfvIGf9Zm0lV4HL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4YfvIGf9Zm0lV4HL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4YfvIGf9Zm0lV4HL .marker.cross{stroke:#333333;}#mermaid-svg-4YfvIGf9Zm0lV4HL svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4YfvIGf9Zm0lV4HL p{margin:0;}#mermaid-svg-4YfvIGf9Zm0lV4HL .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-4YfvIGf9Zm0lV4HL .cluster-label text{fill:#333;}#mermaid-svg-4YfvIGf9Zm0lV4HL .cluster-label span{color:#333;}#mermaid-svg-4YfvIGf9Zm0lV4HL .cluster-label span p{background-color:transparent;}#mermaid-svg-4YfvIGf9Zm0lV4HL .label text,#mermaid-svg-4YfvIGf9Zm0lV4HL span{fill:#333;color:#333;}#mermaid-svg-4YfvIGf9Zm0lV4HL .node rect,#mermaid-svg-4YfvIGf9Zm0lV4HL .node circle,#mermaid-svg-4YfvIGf9Zm0lV4HL .node ellipse,#mermaid-svg-4YfvIGf9Zm0lV4HL .node polygon,#mermaid-svg-4YfvIGf9Zm0lV4HL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4YfvIGf9Zm0lV4HL .rough-node .label text,#mermaid-svg-4YfvIGf9Zm0lV4HL .node .label text,#mermaid-svg-4YfvIGf9Zm0lV4HL .image-shape .label,#mermaid-svg-4YfvIGf9Zm0lV4HL .icon-shape .label{text-anchor:middle;}#mermaid-svg-4YfvIGf9Zm0lV4HL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4YfvIGf9Zm0lV4HL .rough-node .label,#mermaid-svg-4YfvIGf9Zm0lV4HL .node .label,#mermaid-svg-4YfvIGf9Zm0lV4HL .image-shape .label,#mermaid-svg-4YfvIGf9Zm0lV4HL .icon-shape .label{text-align:center;}#mermaid-svg-4YfvIGf9Zm0lV4HL .node.clickable{cursor:pointer;}#mermaid-svg-4YfvIGf9Zm0lV4HL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4YfvIGf9Zm0lV4HL .arrowheadPath{fill:#333333;}#mermaid-svg-4YfvIGf9Zm0lV4HL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4YfvIGf9Zm0lV4HL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4YfvIGf9Zm0lV4HL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4YfvIGf9Zm0lV4HL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4YfvIGf9Zm0lV4HL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4YfvIGf9Zm0lV4HL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4YfvIGf9Zm0lV4HL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4YfvIGf9Zm0lV4HL .cluster text{fill:#333;}#mermaid-svg-4YfvIGf9Zm0lV4HL .cluster span{color:#333;}#mermaid-svg-4YfvIGf9Zm0lV4HL 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-4YfvIGf9Zm0lV4HL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4YfvIGf9Zm0lV4HL rect.text{fill:none;stroke-width:0;}#mermaid-svg-4YfvIGf9Zm0lV4HL .icon-shape,#mermaid-svg-4YfvIGf9Zm0lV4HL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4YfvIGf9Zm0lV4HL .icon-shape p,#mermaid-svg-4YfvIGf9Zm0lV4HL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4YfvIGf9Zm0lV4HL .icon-shape .label rect,#mermaid-svg-4YfvIGf9Zm0lV4HL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4YfvIGf9Zm0lV4HL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4YfvIGf9Zm0lV4HL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4YfvIGf9Zm0lV4HL :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
请求后 = remap(prev)+unmap(old)+map+remap(next)
请求
请求前
old: [0 ………. 3) → obj@n
req: [1 .. 2) → obj2@m
[0 .. 1) → obj@n (prev, keep)
[1 .. 2) → obj2@m (new map)
[2 .. 3) → obj@n+2 (next, keep)
要点:一次 map 最多切开头尾两个旧映射,因此至多两个 remap;keep 提示中间被切下来的连续 PTE 可保留,驱动只需做增量更新。
5. API 接口速览
5.1 生命周期与地址空间
| drm_gpuvm_init() | 初始化一个地址空间(传入名字、范围、resv object、ops) |
| drm_gpuvm_get() / drm_gpuvm_put() | 引用计数 |
| drm_gpuvm_resv_object_alloc() | 分配一个"哑" resv object(当没有现成的根页表 BO 时) |
| drm_gpuvm_range_valid() / drm_gpuvm_interval_empty() | 范围合法性 / 区间是否空 |
5.2 映射的增删查
| drm_gpuva_insert() / drm_gpuva_remove() | 把一条(预分配的)drm_gpuva 插入/移出地址空间视图 |
| drm_gpuva_link() / drm_gpuva_unlink() | 把映射挂到/摘离 drm_gpuvm_bo(即 GEM 的 gpuva.list) |
| drm_gpuva_find() / _find_first / _find_prev / _find_next | 按地址查找映射 |
| drm_gpuva_invalidate() / drm_gpuva_invalidated() | 设置/查询失效标志 |
| drm_gpuvm_for_each_va[_range][_safe]() | 遍历映射的迭代宏 |
5.3 Split & Merge
| drm_gpuvm_sm_map() / drm_gpuvm_sm_unmap() | 回调式:直接回调驱动执行 map/remap/unmap 步骤 |
| drm_gpuvm_sm_map_ops_create() / …_unmap_ops_create() | 物化式:生成可重复迭代的 drm_gpuva_ops 列表 |
| drm_gpuvm_prefetch_ops_create() / drm_gpuvm_bo_unmap_ops_create() | 生成预取 / 解绑某 BO 全部映射的操作列表 |
| drm_gpuva_map() / drm_gpuva_remap() / drm_gpuva_unmap() | 在回调里更新框架视图的便捷函数 |
| drm_gpuva_for_each_op[_safe/_reverse]() | 遍历操作列表 |
| drm_gpuva_ops_free() | 释放物化的操作列表 |
5.4 (BO, VM) 组合与加锁校验
| drm_gpuvm_bo_obtain() / _obtain_prealloc() | 获取/新建 drm_gpuvm_bo |
| drm_gpuvm_bo_get() / _put() / _put_deferred() / _deferred_cleanup() | 引用计数与延迟释放 |
| drm_gpuvm_bo_evict() / drm_gpuvm_bo_extobj_add() | 维护 evicted / extobj 列表 |
| drm_gpuvm_prepare_vm() / _prepare_objects() / _prepare_range() | drm_exec 加锁准备 |
| drm_gpuvm_exec_lock() / _lock_array() / _lock_range() | 一次性锁住 VM 关联对象 |
| drm_gpuvm_validate() / drm_gpuvm_resv_add_fence() | 校验被驱逐对象 / 给所有 resv 添加 fence |
6. 典型使用流程(VM_BIND)
6.1 一次 map 请求的处理时序
GPU 页表
drm_gpuvm (框架)
驱动
用户态 (VM_BIND ioctl)
GPU 页表
drm_gpuvm (框架)
驱动
用户态 (VM_BIND ioctl)
#mermaid-svg-8G0E3BYEauJJyDHS{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-8G0E3BYEauJJyDHS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8G0E3BYEauJJyDHS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8G0E3BYEauJJyDHS .error-icon{fill:#552222;}#mermaid-svg-8G0E3BYEauJJyDHS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8G0E3BYEauJJyDHS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8G0E3BYEauJJyDHS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8G0E3BYEauJJyDHS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8G0E3BYEauJJyDHS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8G0E3BYEauJJyDHS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8G0E3BYEauJJyDHS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8G0E3BYEauJJyDHS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8G0E3BYEauJJyDHS .marker.cross{stroke:#333333;}#mermaid-svg-8G0E3BYEauJJyDHS svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8G0E3BYEauJJyDHS p{margin:0;}#mermaid-svg-8G0E3BYEauJJyDHS .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-8G0E3BYEauJJyDHS text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-8G0E3BYEauJJyDHS .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-8G0E3BYEauJJyDHS .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-8G0E3BYEauJJyDHS .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-8G0E3BYEauJJyDHS .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-8G0E3BYEauJJyDHS #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-8G0E3BYEauJJyDHS .sequenceNumber{fill:white;}#mermaid-svg-8G0E3BYEauJJyDHS #sequencenumber{fill:#333;}#mermaid-svg-8G0E3BYEauJJyDHS #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-8G0E3BYEauJJyDHS .messageText{fill:#333;stroke:none;}#mermaid-svg-8G0E3BYEauJJyDHS .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-8G0E3BYEauJJyDHS .labelText,#mermaid-svg-8G0E3BYEauJJyDHS .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-8G0E3BYEauJJyDHS .loopText,#mermaid-svg-8G0E3BYEauJJyDHS .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-8G0E3BYEauJJyDHS .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-8G0E3BYEauJJyDHS .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-8G0E3BYEauJJyDHS .noteText,#mermaid-svg-8G0E3BYEauJJyDHS .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-8G0E3BYEauJJyDHS .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8G0E3BYEauJJyDHS .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8G0E3BYEauJJyDHS .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8G0E3BYEauJJyDHS .actorPopupMenu{position:absolute;}#mermaid-svg-8G0E3BYEauJJyDHS .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-8G0E3BYEauJJyDHS .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-8G0E3BYEauJJyDHS .actor-man circle,#mermaid-svg-8G0E3BYEauJJyDHS line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-8G0E3BYEauJJyDHS :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
loop
[允许分配的上下文]
进入 dma-fence 信号临界区(不可分配内存)
alt
[MAP]
[REMAP]
[UNMAP]
loop
[drm_gpuva_for_each_op]
bind(gpu_va, range, bo, offset)
drm_gpuvm_bo_obtain(vm, bo) (复用/新建 vm_bo)
drm_exec + drm_gpuvm_exec_lock() (锁住相关 resv)
drm_gpuvm_sm_map_ops_create(req)
drm_gpuva_ops (unmap/remap/map 序列)
为每个 map/remap 预分配 drm_gpuva + 页表内存
写入新 PTE
drm_gpuva_map() (插入视图 + link 到 vm_bo)
增量更新 PTE (利用 keep)
drm_gpuva_remap()
清除 PTE
drm_gpuva_unmap() (从视图移除 + unlink)
drm_gpuvm_resv_add_fence()
完成 (返回 fence)
6.2 驱动侧集成清单
7. 与 drm_gem_object 的关系
- drm_gem_object 通过 gpuva.list(+ 可选 gpuva.lock)持有"指向自己的全部映射"的反向链表;这条链表上的表项经由 drm_gpuvm_bo 聚合到具体 drm_gpuva。
- 单向关联:一个 GEM 对象可被多条 drm_gpuva(同一或不同 VM)映射;每条 drm_gpuva 只指向一个 GEM 对象。
- 与 CPU 侧对称:drm_gem_object.vma_node 管 CPU mmap 偏移,drm_gpuva 管 GPU 地址映射——一个对象同时活在两个地址空间里。
8. 版本时间线
| 6.6 | 框架首次合入(当时名为 drm_gpuva_manager);drm_gem_object 新增 gpuva 反向链表 |
| 6.7 | struct drm_gpuva_manager 更名为 struct drm_gpuvm |
| 6.8 | 新增 drm_gpuvm_bo((BO, VM) 组合)抽象及 extobj/evicted 列表 |
| 6.18 | drm_gem_object.gpuva 新增独立 lock mutex,配合 DRM_GPUVM_IMMEDIATE_MODE |
9. 框架的采用现状(生态视角)
drm_gpuvm 作为 DRM core 提取的通用地址空间管理框架,其采用是渐进式的——它诞生于 VM_BIND 类驱动的共同需求,因此最早由这些驱动落地,其它驱动则按自身节奏对照/迁移。可以把生态分成三档:
| 原生采用 | nouveau、Xe、panthor 等 | 框架的早期使用者与共同设计者,VM_BIND 路径直接构建在 drm_gpuvm 上 |
| 自研为主 | amdgpu | 仍使用自研的 amdgpu_vm / amdgpu_bo_va 管理 GPU VA,尚未整体迁移到 drm_gpuvm |
| 无此需求 | 传统显示/简单 2D 驱动 | 不涉及用户态显式 VA 规划,无需该框架 |
因此看待某个具体驱动(如 amdgpu)时,应把它理解为这条采用曲线上的一个当前状态点,而非框架的适用性判断:
- 本文描述的是 DRM core 的通用框架语义,以原生采用者的实践为参照。
- amdgpu 的自研实现与本框架在要解决的问题上高度同构(地址空间视图、映射的拆分合并、驱逐/失效、按需建页表),因此理解 drm_gpuvm 有助于对照阅读 amdgpu 自有实现。
- 是否迁移取决于收益权衡(代码复用 vs. 迁移成本与既有特性),这类演进通常与 SVM、recoverable page fault 等新特性的推进相互牵引,可作为跟进方向。

