资讯动态

第三章:GEM分析:gem_object 如何被 GPU 访问(下):gpuva 字段与映射反向索引

发布时间:2026/8/13 10:39:00 来源:尧图企业网站定制
先说下要理解本节分析要按如下的的顺序gem_object 如何被 GPU 访问上旧模型 BO-list Relocation 隐式同步旧模型对象每次提交临时定址→ gpuva新模型drm_gpuvm把“对象 → 持久 GPU VA 映射”提取为通用框架→本文回到drm_gem_object自身看它如何持久记录“指向自己的全部映射”。因此本文只分析drm_gem_object自身携带的gpuva字段——它的设计目的、相关 API 与使用方法。至于drm_gpuvm/drm_gpuva框架的整体设计新需求、Split Merge、VM_BIND 流程等见 gpuva。1. 这个字段是什么drm_gem_object里有这样一小块struct{structlist_headlist;// 该对象参与的所有 (对象, VM) 组合链表structmutexlock;// 仅 IMMEDIATE_MODE 下保护 list6.18 引入}gpuva;它的作用只有一个让内核能从一个 GEM 对象出发反向找到这个对象被映射到了哪些 GPU 地址空间、以及每个空间里的哪些具体映射。注意一个极易误解的点gpuva.list挂的不是drm_gpuva而是drm_gpuvm_bo。它是一个两级链表结构见第 3 节。2. 从旧模型接续为什么现在对象需要这条反向链表回顾 gem_object 如何被 GPU 访问上旧模型里“一个gem_object参与了哪些提交、在哪些地址上被访问”是临时的——每次提交靠 BO-list 现场重建提交一结束就不再保留。因此旧模型里对象不需要持久记录“谁在用我”。到了新模型gpuva 的 VM_BIND映射变成持久的用户态预先把对象 bind 到稳定 GPU VA这些drm_gpuva映射会长期存在、跨越多次提交。于是产生一个旧模型没有的新需求——对象必须持久地知道“有哪些映射指向我、分布在哪些 VM”否则驱逐、销毁、校验时无从下手。gpuva字段就是这条持久反向索引它在语义上取代了旧模型里“每次提交临时重建的 BO-list 成员关系”。这条链表的出现本质上是一系列模型转变共同倒逼的结果。把两代模型的关键差异摆在一起看就能明白为什么“反向索引”从可有可无变成了必需维度旧模型BO-list Relocation 隐式同步新模型VM_BIND /drm_gpuvmGPU 地址稳定性早期 relocation 下地址不稳定每次提交可能重定位i915/UMAamdgpu 靠amdgpu_bo_va才有 per-VM 稳定地址用户态显式 bind 到稳定 GPU VA跨多次提交长期不变地址由谁决定内核在提交时隐式决定/修补地址relocation 由内核回填用户态显式分配并控制 VAVM_BIND ioctl映射生命周期临时随单次提交建立、提交结束即失效持久bind 后长期存在与提交解耦同步方式隐式同步内核依据 BO-list 自动串联依赖显式同步用户态用 fence/syncobj 自己表达依赖“谁在用我”如何表达每次提交靠 BO-list现场重建无持久结构靠对象自带的gpuva.list持久记录枚举对象映射的代价无需持久枚举提交上下文里就有完整 BO-list若无反向索引需遍历每个 VM 的整棵区间树 → 故必须内联反向链表控制权归属偏内核托管定址、同步、校验都在内核提交路径偏用户态自管定址、同步显式化内核只维护结构与一致性一句话概括这次演进从“内核隐式处理、地址临时不稳定”走向“用户态显式控制、地址持久稳定”——正是后者让“对象需要持久知道谁在映射自己”成为硬需求gpuva字段应运而生。换个角度看正向问题“某个 VA 映射到谁”由drm_gpuvm的区间树回答见 gpuva 第 3 节反向问题“某个对象参与了哪些映射”就靠本字段的链表。典型的反向操作它们在旧模型里本是每次提交的 BO-list 校验的一部分现在改由持久反向索引驱动对象被驱逐evictBO 从显存被换出后所有指向它的映射都要标记失效、下次使用前重建 PTE。必须能枚举“指向本对象的全部映射”。对象被销毁 / 强制解绑释放前要把它在各个 VM 里的映射全部拆掉。重新校验validate提交前确认对象已回迁并刷新相关映射。如果没有这条反向链表就只能遍历每个 VM 的整棵区间树去找匹配的对象代价高昂。因此框架把反向索引直接内联到 GEM 对象上——这正是gpuva字段存在的理由。它与对象里另一个方向的字段形成对称也正好对应新旧两个阶段对“对象被访问”的不同管理方式字段管的方向回答vma_nodeCPU 侧对象被哪些进程 mmapCPU 地址空间gpuva.listGPU 侧对象被绑定到哪些 GPU 地址空间及其映射对照旧模型旧模型里“GPU 侧”的关系是靠 BO-list 每次提交临时表达的无持久结构新模型把它固化为gpuva.list这条持久链表。3. 两级链表结构gpuva.list的组织是对象 → 每个 VM 一个drm_gpuvm_bo→ 该 VM 内的每条drm_gpuvaVM_2 中该对象的映射VM_1 中该对象的映射第一级: 每个 VM 一个 drm_gpuvm_bolist.entry.gemlist.entry.gemlist.gpuva / gem.entrylist.gpuva / gem.entrydrm_gem_objectgpuva.listdrm_gpuvm_bo (obj, VM_1)drm_gpuvm_bo (obj, VM_2)drm_gpuva [a,r)off0drm_gpuva [b,r)off1drm_gpuva [c,r)off0第一级drm_gem_object.gpuva.list串起若干drm_gpuvm_bo每个代表本对象 × 某个 VM这一唯一组合链接字段是drm_gpuvm_bo.list.entry.gem。第二级每个drm_gpuvm_bo.list.gpuva串起本对象在该 VM 内的全部drm_gpuva链接字段是drm_gpuva.gem.entry。为什么要中间加一层drm_gpuvm_bo而不是让对象直接挂一堆drm_gpuva因为同一对象在同一个 VM里可能有多条映射不同 offset → 不同 VA。用(对象,VM)组合作为聚合点既能按 VM 分组枚举映射又能让 extobj/evicted 等 per-VM 列表复用同一个drm_gpuvm_bo表项。4. 相关 API4.1 启用与初始化API / 标志作用DRIVER_GEM_GPUVAdrm_driver.driver_features驱动声明支持用户自定义 GPU VA 绑定。使用本字段的前提。drm_gem_gpuva_init(obj)初始化gpuva.listINIT_LIST_HEAD。在驱动创建 GEM 对象时调用。staticinlinevoiddrm_gem_gpuva_init(structdrm_gem_object*obj){INIT_LIST_HEAD(obj-gpuva.list);}4.2 遍历宏遍历对象说明drm_gem_for_each_gpuvm_bo(vm_bo, obj)第一级所有drm_gpuvm_bo即对象被绑定的每个 VMdrm_gem_for_each_gpuvm_bo_safe(vm_bo, next, obj)同上可在遍历中删除drm_gpuvm_bo_for_each_va(va, vm_bo)第二级某vm_bo下所有drm_gpuva需持有 GEM 的 gpuva 锁drm_gpuvm_bo_for_each_va_safe(va, next, vm_bo)同上可安全删除嵌套两层即可枚举本对象的全部映射structdrm_gpuvm_bo*vm_bo;structdrm_gpuva*va;drm_gem_for_each_gpuvm_bo(vm_bo,obj)drm_gpuvm_bo_for_each_va(va,vm_bo)do_something(va);// va-vm 是所属 VMva-va.addr/range 是映射区间4.3 锁API作用drm_gem_gpuva_assert_lock_held(gpuvm, obj)lockdep 断言按模式检查持有正确的锁锁规则由 GPUVM 的模式决定默认模式gpuva.list由GEM 的dma_resv锁obj-resv保护。DRM_GPUVM_IMMEDIATE_MODE6.18改由内置的gpuva.lockmutex保护以便在 dma-fence 信号临界路径中修改。在该锁下不得分配内存。断言宏正是据此二选一#definedrm_gem_gpuva_assert_lock_held(gpuvm,obj)\lockdep_assert(drm_gpuvm_immediate_mode(gpuvm)?\lockdep_is_held((obj)-gpuva.lock):\dma_resv_held((obj)-resv))提示头文件里drm_gem_gpuva_init()注释的 “See also drm_gem_gpuva_set_lock()” 是过时引用——早期版本用drm_gem_gpuva_set_lock()设置外部锁现已改为内置gpuva.lockmutex 上述模式判定。4.4 建立/摘除链接跨到框架侧对象侧链表的表项增删由框架侧函数完成这里列出交界处的常用者API作用drm_gpuvm_bo_obtain()/_obtain_locked()/_obtain_prealloc()获取/新建(obj, vm)的drm_gpuvm_bo并挂入对象的gpuva.listdrm_gpuvm_bo_find(vm, obj)查找已存在的drm_gpuvm_bodrm_gpuva_link(va, vm_bo)/drm_gpuva_unlink(va)把一条drm_gpuva挂入/摘出vm_bo即二级链表drm_gpuvm_bo_gem_evict(obj, evict)遍历对象的所有vm_bo批量置驱逐标志5. 使用方法5.1 驱动接入的最小步骤在drm_driver.driver_features里置上DRIVER_GEM_GPUVA。在创建 GEM 对象的路径里调用drm_gem_gpuva_init(obj)。bind 时拿到/新建drm_gpuvm_bodrm_gpuvm_bo_obtain*再对每条映射drm_gpuva_link()unbind 时对应drm_gpuva_unlink()。需要枚举对象映射时驱逐/销毁/校验用 4.2 的两级遍历宏并按 4.3 持好锁。5.2 典型场景 1对象被驱逐时标记全部映射失效/* 持有 obj-resv默认模式或 gpuva.lockimmediate 模式 */voiddriver_bo_evicted(structdrm_gem_object*obj){structdrm_gpuvm_bo*vm_bo;structdrm_gpuva*va;drm_gem_for_each_gpuvm_bo(vm_bo,obj){drm_gem_gpuva_assert_lock_held(vm_bo-vm,obj);/* 该对象在这个 VM 里进入 evicted 列表 */drm_gpuvm_bo_evict(vm_bo,true);/* 逐条映射标记失效下次提交前重建 PTE */drm_gpuvm_bo_for_each_va(va,vm_bo)drm_gpuva_invalidate(va,true);}}上例中drm_gpuvm_bo_gem_evict(obj, true)已封装了遍历 vm_bo 置驱逐标志的部分。5.3 典型场景 2销毁对象前解绑所有映射drm_gem_for_each_gpuvm_bo_safe(vm_bo,next_bo,obj){structdrm_gpuva*va,*next_va;drm_gpuvm_bo_for_each_va_safe(va,next_va,vm_bo){drm_gpuva_unlink(va);// 摘出二级链表drm_gpuva_remove(va);// 从 VM 区间树移除driver_free_pt_and_va(va);// 驱动清理 PTE 与 drm_gpuva 内存}/* vm_bo 的引用释放后会自动摘出对象的 gpuva.list */}6. 小结从演进线看旧模型用临时 BO-list表达“谁在用我”新模型映射持久化后对象改用gpuva字段持久记录这个关系。drm_gem_object.gpuva是对象侧的反向索引从对象快速找到“它参与的所有 GPU 映射”服务于驱逐、销毁、校验等需要按对象枚举映射的场景。它是两级链表对象 →drm_gpuvm_bo每 VM 一个→drm_gpuva每条映射一个。别把gpuva.list当成drm_gpuva的直接列表。用法要点DRIVER_GEM_GPUVAdrm_gem_gpuva_init()初始化drm_gem_for_each_gpuvm_bo()套drm_gpuvm_bo_for_each_va()遍历按模式持obj-resv或gpuva.lock。它与vma_node对称一个记录对象在 CPU 地址空间的映射一个记录在 GPU 地址空间的映射。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价