欢迎光临
我们一直在努力

第八章:GPUVM:drm_gpuvm / drm_gpuva:GPU 虚拟地址空间管理框架概述

前文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 三个核心抽象

框架用三个结构体分工,各自回答一个问题:

结构体回答的问题类比 CPU mm
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 驱动侧集成清单

  • 在驱动的 VM 结构体里嵌入 drm_gpuvm,在驱动的映射结构体里嵌入 drm_gpuva。
  • 实现 drm_gpuvm_ops:至少 sm_step_map / sm_step_remap / sm_step_unmap(回调式)或直接消费 ops 列表;以及 vm_free 等。
  • 建 VM 时 drm_gpuvm_init(),提供 resv object(可用根页表 BO 或哑对象)。
  • bind/unbind 路径:drm_gpuvm_bo_obtain() → drm_gpuvm_exec_lock() → sm_map/unmap[_ops_create] → 遍历 ops 更新页表与框架视图 → 加 fence。
  • 驱逐/失效路径:TTM 驱逐时 drm_gpuvm_bo_evict() + drm_gpuva_invalidate();下次提交前 drm_gpuvm_validate() 回迁并重建 PTE。
  • 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 等新特性的推进相互牵引,可作为跟进方向。
    赞(0)
    未经允许不得转载:171主机测评 » 第八章:GPUVM:drm_gpuvm / drm_gpuva:GPU 虚拟地址空间管理框架概述
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址