ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

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

drm_gpuvm / drm_gpuva:GPU 虚拟地址空间管理框架概述 1. 新需求从内核代管映射到 VM_BIND1.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_VArange)绑定 / 解绑到某个(GEM 对象, offset)与命令提交解耦。支持稀疏绑定任意子区间独立 map/unmap允许空洞。地址空间由用户态规划内核只负责维护当前有哪些映射以及据此更新 GPU 页表。这带来一个新的、所有支持 VM_BIND 的驱动都要解决的公共问题如何在内核里高效地表示、查询、以及增量维护一个 GPU 虚拟地址空间中的全部映射并在用户请求 map/unmap 时正确地对已有映射做拆分与合并在 GPUVM 出现前nouveau、Xe 等驱动各自实现这套逻辑代码高度重复且容易出错。drm_gpuvm就是把它提取为 DRM core 的通用框架——定位与drm_gem_object提取对象管理完全对称只不过这里提取的是地址空间管理。2. 设计思考2.1 三个核心抽象框架用三个结构体分工各自回答一个问题结构体回答的问题类比 CPU mmdrm_gpuvm一个 GPU 虚拟地址空间里有哪些映射struct mm_structdrm_gpuva某个 VA 区间映射到哪个对象的哪段偏移struct vm_area_structdrm_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-protecteddrm_gem_object.gpuva.list由该 GEM 的dma_resv锁保护。VM 私有对象可共享一个dma_resvresv object从而用一把锁 drm_exec高效锁住整组对象。DRM_GPUVM_IMMEDIATE_MODE6.18 引入为在 fence 信号路径中修改 GPUVM设计此时gpuva.list由专门的gpuva.lockmutex 保护不能在持锁时分配内存。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一条映射structdrm_gpuva{structdrm_gpuvm*vm;// 所属地址空间structdrm_gpuvm_bo*vm_bo;// (BO, VM) 组合抽象enumdrm_gpuva_flagsflags;// INVALIDATED / SPARSE / USERBITSstruct{u64 addr;u64 range;}va;// GPU VA 区间 [addr, addrrange)struct{u64 offset;structdrm_gem_object*obj;// 映射到哪个对象的哪段偏移structlist_headentry;}gem;// 挂到 drm_gpuvm_bo 的链表struct{structrb_nodenode;// 插入区间树structlist_headentry;u64 __subtree_last;}rb;};varb让映射能按地址被查找drm_gpuva_find*与遍历。gemobjoffset精确指向对象内一段entry让从对象反查其全部映射成为可能。flagsDRM_GPUVA_INVALIDATED表示后端 BO 已失效被驱逐页表需重建DRM_GPUVA_SPARSE表示稀疏映射。3.2 三层结构关系图按地址组织 (区间树)rb-tree / maple-treegem.entrygem.entrygem.entry引用/挂载引用/挂载extobj / evicted 列表drm_gpuvm(一个 GPU 虚拟地址空间)drm_gpuva #1[a, r) → obj_Aoff0drm_gpuva #2[b, r) → obj_Boff0drm_gpuva #3[c, r) → obj_Aoff1drm_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 操作模型enumdrm_gpuva_op_type{DRM_GPUVA_OP_MAP,REMAP,UNMAP,PREFETCH,DRIVER};structdrm_gpuva_op_map{struct{u64 addr,range;}va;struct{u64 offset;structdrm_gem_object*obj;}gem;};structdrm_gpuva_op_unmap{structdrm_gpuva*va;bool keep;};structdrm_gpuva_op_remap{structdrm_gpuva_op_map*prev,*next;// 头尾保留段structdrm_gpuva_op_unmap*unmap;};// 被切开的旧映射一个 map 请求整合进现有空间时典型形态新映射覆盖并切开一个更大的旧映射请求后 remap(prev)unmap(old)mapremap(next)请求请求前old: [0 .......... 3) → objnreq: [1 .. 2) → obj2m[0 .. 1) → objn (prev, keep)[1 .. 2) → obj2m (new map)[2 .. 3) → objn2 (next, keep)要点一次 map 最多切开头尾两个旧映射因此至多两个 remapkeep提示中间被切下来的连续 PTE 可保留驱动只需做增量更新。5. API 接口速览5.1 生命周期与地址空间接口作用drm_gpuvm_init()初始化一个地址空间传入名字、范围、resv object、opsdrm_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.listdrm_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_bodrm_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 添加 fence6. 典型使用流程VM_BIND6.1 一次 map 请求的处理时序GPU 页表drm_gpuvm (框架)驱动用户态 (VM_BIND ioctl)GPU 页表drm_gpuvm (框架)驱动用户态 (VM_BIND ioctl)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 页表内存写入新 PTEdrm_gpuva_map() (插入视图 link 到 vm_bo)增量更新 PTE (利用 keep)drm_gpuva_remap()清除 PTEdrm_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_managerdrm_gem_object新增gpuva反向链表6.7struct drm_gpuva_manager更名为struct drm_gpuvm6.8新增drm_gpuvm_bo(BO, VM) 组合抽象及 extobj/evicted 列表6.18drm_gem_object.gpuva新增独立lockmutex配合DRM_GPUVM_IMMEDIATE_MODE9. 与 AMD amdgpu 的关系现状说明需要注意amdgpu 目前仍使用自研的amdgpu_vm/amdgpu_bo_va做 GPU VA 管理尚未整体迁移到drm_gpuvm。因此本文描述的是 DRM core 的通用框架与 nouveau/Xe/panthor 等驱动的实践理解它有助于对照 amdgpu 自有实现以及跟进未来可能的迁移与 SVM 相关工作。
返回列表