AMDGPU 基于DRM SVM框架的新SVM功能实现 :attr_range 与 svm_range 的对应关系分析
AMD 正在使用 drm svm框架重构SVM的实现看来drm svm框架要进入大范围应用了。下面是在kernel社区上由AMD的开发人员提交的POC 验证版本的patches的技术方案实现。这里快速总结了实现以飨读者。因是POC版本设计可能会变动读者们慎重使用。本文仅用来跟踪前沿驱动技术的迭代发展现状。1. 结论amdgpu_svm_attr_range属性层与amdgpu_svm_range映射层不是一一对应的而是N:M 的解耦关系。两者有不同的拆分标准、不同的粒度、不同的生命周期通过amdgpu_svm_range_apply_attr_change()桥接。2. 两层架构概览┌─────────────────────────────────────────────────────────────────┐ │ 用户空间 ioctl │ │ amdgpu_svm_attr_set(start, size, attrs) │ └────────────────────────────┬────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 属性层 (Attribute Layer) │ │ │ │ amdgpu_svm_attr_range: 存储在 attr_tree-tree (interval tree) │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ range A │ │ range B │ │ range C │ ← 按属性边界拆分 │ │ │flagsRW │ │flagsRO │ │flagsRW │ │ │ │accessEN │ │accessEN │ │accessNO │ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ 职责记录用户设置的属性元数据纯数据无 GPU 映射状态 │ └────────────────────────────┬────────────────────────────────────┘ │ attr_change → trigger ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 映射层 (Mapping Layer) │ │ │ │ amdgpu_svm_range (wraps drm_gpusvm_range): │ │ 存储在 drm_gpusvm_notifier 内的 interval tree │ │ ┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐ │ │ │ r0 ││ r1 ││ r2 ││ r3 ││ r4 ││ r5 ││ r6 ││ r7 │ ← chunk对齐 │ │ └────┘└────┘└────┘└────┘└────┘└────┘└────┘└────┘ │ │ 职责持有实际 GPU 页表映射、DMA 状态、pages │ └─────────────────────────────────────────────────────────────────┘3. 两层对比维度amdgpu_svm_attr_range(属性层)amdgpu_svm_range/drm_gpusvm_range(映射层)存储位置attr_tree-tree(interval tree)drm_gpusvm_notifier内的 interval tree职责记录用户设置的属性元数据持有实际 GPU 页表映射、DMA 状态、pages拆分依据用户 ioctl 设置的属性边界chunk_size 对齐、VMA 边界、notifier 边界创建时机amdgpu_svm_attr_set_range()— ioctl 驱动drm_gpusvm_range_find_or_insert()— fault/map 驱动持有内容preferred_loc,flags,access,granularitygpu_mapped,pte_flags, pages, DMA mapping粒度任意大如用户对 1GB 区间设置属性 → 一个 attr_range较小受 chunk_sizes (4K/64K/2M)、VMA、notifier 约束生命周期由用户 ioctl 创建attr_clear_pages或进程退出时销毁由 fault/map 创建mmu_notifier invalidate 时可回收4. 拆分标准的差异4.1 attr_range 的拆分属性边界驱动amdgpu_svm_attr_set_existing()在用户对一个已有 attr_range 的子区间设置新属性时会 split 出 left/right 残留段初始状态: attr_range: [ A (flagsRW, accessENABLE) ] 0x1000 0x5000 用户 ioctl: 设置 [0x2000, 0x3000] flagsRO 拆分后: attr_range: [ A (RW) ][ B (RO) ][ A (RW) ] 0x1000 0x1FFF 0x2000 0x3000 0x3001 0x5000关键代码amdgpu_svm_attr_set_existing/* split head */if(startrange_start){leftattr_alloc_range(range_start,start-1,old_attrs);}/* split tail */if(lastrange_last){rightattr_alloc_range(last1,range_last,old_attrs);}4.2 svm_range 的拆分硬件约束驱动drm_gpusvm_range_chunk_size()根据以下约束确定每个 svm_range 的大小for(;igpusvm-num_chunks;i){startALIGN_DOWN(fault_addr,gpusvm-chunk_sizes[i]);endALIGN(fault_addr1,gpusvm-chunk_sizes[i]);if(startvas-vm_startendvas-vm_end// VMA 边界startdrm_gpusvm_notifier_start(notifier)// notifier 边界enddrm_gpusvm_notifier_end(notifier)startgpuva_startendgpuva_end)// GPUVA 边界break;}约束包括chunk_sizes 对齐预定义的 2 的幂数组如 2M, 64K, 4K从大到小尝试VMA 边界不能跨越 CPU VMAnotifier 边界不能跨越 mmu_interval_notifier已有 range 边界不能与已存在的 svm_range 重叠5. N:M 对应关系详解5.1 一个 attr_range 跨多个 svm_range1:N这是最常见的情况。用户通过 ioctl 设置大范围属性产生一个大的 attr_range而 GPU 映射时按 chunk_size 对齐创建多个小的 svm_rangeattr_range: [ 0x1000 - 0x10000 (accessenable) ] svm_ranges: [--64K--][--64K--][--64K--][--64K--][--64K--]... (chunk-aligned) 由 drm_gpusvm_range_find_or_insert() 逐个创建调用路径amdgpu_svm_attr_apply_change() → amdgpu_svm_range_map_interval() → amdgpu_svm_range_map() → while (addr end): drm_gpusvm_range_find_or_insert() ← 每次创建/复用一个 chunk 对齐的 svm_range drm_gpusvm_range_get_pages() amdgpu_svm_range_update_gpu_range()5.2 一个 svm_range 跨多个 attr_rangeM:1设计上会处理如果一个 64K 的 svm_range 恰好覆盖了两个不同属性的 attr_range 区域restore 路径中会按 attr 边界分段处理attr_ranges: [ A (RW) ][ B (RO) ] 0x1000 0x17FF 0x1800 0x1FFF svm_range: [ 64K ] 0x1000 0x1FFFamdgpu_svm_range_map_attr_ranges()通过amdgpu_svm_attr_lookup_page_locked()逐段查询属性按 attr 边界分段映射// amdgpu_svm_range_map_attr_ranges()while(cursorlast_page){// 查询当前页的属性及属性范围的结束位置mutex_lock(attr_tree-lock);amdgpu_svm_attr_lookup_page_locked(attr_tree,cursor,attrs,seg_last);mutex_unlock(attr_tree-lock);seg_lastmin(seg_last,last_page);if(range_has_access(attrs.access)){retamdgpu_svm_range_map_interval(svm,cursor,seg_last,attrs);}cursorseg_last1;}5.3 attr_range 空洞默认属性区域在 attr_tree 中不存在 attr_range 的区域使用默认属性。amdgpu_svm_attr_lookup_page_locked()在查询空洞时返回默认属性// 查询命中空洞时attr_set_default(attr_tree-svm,attrs);*range_lastULONG_MAX;// 查找下一个 attr_range 的起始位置来确定空洞的结束nodeinterval_tree_iter_first(attr_tree-tree,page1,ULONG_MAX);if(node){rangecontainer_of(node,structamdgpu_svm_attr_range,it_node);if(range-it_node.startpage)*range_lastrange-it_node.start-1;}6. 桥接机制属性变更如何传递到映射层6.1 完整调用链amdgpu_svm_attr_set() ← 用户 ioctl 入口 │ ├─ 验证 VMA 检查 │ └→ amdgpu_svm_attr_set_range() ← 遍历 [start, last]按 attr 段处理 │ │ while (cursor last): │ ┌─ cursor 命中已有 attr_range? │ │ YES → amdgpu_svm_attr_set_existing() ← 可能 split left/right │ │ NO → amdgpu_svm_attr_set_hole() ← 空洞区域创建新 range │ │ │ └→ 产生 attr_set_ctx { start, last, trigger, prev_attrs, new_attrs } │ └→ amdgpu_svm_attr_apply_change() ← 桥接属性层 → 映射层 │ │ 根据 trigger 类型决定操作 │ ├─ ACCESS_CHANGE new_access: │ → amdgpu_svm_range_map_interval() ← remap GPU 页表 │ ├─ PTE_FLAG_CHANGE / MAPPING_FLAG_CHANGE: │ → amdgpu_svm_range_map_interval() ← remap GPU 页表 │ └─ LOCATION_CHANGE: → amdgpu_svm_range_rebuild_locked() ← destroy recreate svm_ranges │ ├─ amdgpu_svm_range_remove_overlaps() ← 删除旧 svm_ranges └─ amdgpu_svm_range_map_attr_ranges() ← 按 attr 边界重建6.2 trigger 类型与映射层操作的对应trigger 类型映射层操作原因ATTR_TRIGGER_ACCESS_CHANGE(enable)map_interval→ remap需要建立新的 GPU 映射ATTR_TRIGGER_ACCESS_CHANGE(disable)无操作 (align with KFD)TODO: 应 unmapATTR_TRIGGER_PTE_FLAG_CHANGEmap_interval→ remapPTE flags 变了需要更新页表ATTR_TRIGGER_MAPPING_FLAG_CHANGEmap_interval→ remap映射策略变了需要更新ATTR_TRIGGER_LOCATION_CHANGErebuild_locked→ destroy recreatemigrate_devmemflag 是不可变的必须重建 rangeATTR_TRIGGER_GRANULARITY_CHANGE无操作粒度变化只影响后续 range 创建ATTR_TRIGGER_ATTR_ONLY无操作无实质变化6.3 LOCATION_CHANGE 需要 rebuild 的原因drm_gpusvm_range的migrate_devmemflag 在创建时就确定了来自gpusvm_ctx.devmem_possible之后不可变。如果用户改变了preferred_loc/prefetch_loc旧的 svm_range 仍然带着过时的 flagmigration 不会发生。因此必须删除旧的 svm_rangesamdgpu_svm_range_remove_overlaps重新创建amdgpu_svm_range_map_attr_ranges新 range 会从当前 attr 读取最新的devmem_possible7. svm_range 不直接存储属性amdgpu_svm_range只缓存属性的结果不存储属性本身structamdgpu_svm_range{structdrm_gpusvm_rangebase;bool gpu_mapped;// 是否已映射到 GPUuint64_tpte_flags;// 当前 GPU PTE flags属性的结果uint32_tattr_flags;// 当前属性 flags 的快照// ... 没有 preferred_loc, access 等属性字段};当 svm_range 需要 restore如 eviction 后恢复映射时会回查 attr_tree获取最新属性// amdgpu_svm_range_map_attr_ranges() — restore 路径amdgpu_svm_attr_lookup_page_locked(attr_tree,cursor,attrs,seg_last);// 用查到的 attrs 重新映射amdgpu_svm_range_map_interval(svm,cursor,seg_last,attrs);这确保了即使属性在 eviction 期间被修改restore 时也能使用最新的属性。8. 锁的协调两层使用不同的锁且有特定的获取顺序amdgpu_svm_attr_set_range() 中的锁序 mutex_lock(attr_tree-lock); ← 属性层锁 // 修改 attr_tree mutex_unlock(attr_tree-lock); down_write(svm-svm_lock); ← 映射层锁 amdgpu_svm_attr_apply_change(); // 操作 svm_ranges up_write(svm-svm_lock);不能同时持有两把锁因为amdgpu_svm_range_map_interval()内部需要获取mmap_read_lock如果持有attr_tree-lock可能导致死锁。因此设计上先释放 attr 锁再获取 svm 锁。9. 图示总结用户视角 (ioctl): [ 设置 accessENABLE, flagsRO ] start_page last_page 属性层 (attr_tree): [hole][ attr_A ][ hole ][ attr_B ][hole][attr_C] ↑ default attrs ↑ default ↑ default 各段按属性边界划分与 ioctl 历史相关 映射层 (gpusvm): [r0][r1] [r2][r3][r4] [r5][r6][r7][r8] [r9][r10] ↑ 按 chunk_size 对齐受 VMA/notifier 边界约束 各段独立持有 GPU 页表映射状态 对应关系: N : M 无固定对应通过 attr_lookup 动态查询桥接概念说明attr_range“这段 VA 有什么属性”面向用户ioctl 驱动纯元数据svm_range“这段 VA 有什么 GPU 映射”面向硬件fault/map 驱动持有页表状态对应关系 N:M属性边界 ≠ 映射边界两者独立拆分桥接 trigger → range remap/rebuildattr 层通知 range 层属性变了请更新映射这种解耦设计的好处是属性管理用户策略和 GPU 映射管理硬件约束各自独立演进互不干扰。10. 复杂性代价分析N:M 解耦设计引入了明显的复杂性需要客观评估其代价与收益。10.1 引入的具体复杂性(1) 锁序窗口期与 retry 机制因为两层各有独立的锁amdgpu_svm_attr_set_range()不得不采用释放 attr 锁 → 获取 svm 锁的模式mutex_lock(attr_tree-lock);// 修改 attr_tree属性已变mutex_unlock(attr_tree-lock);← 窗口期属性已改映射未更新down_write(svm-svm_lock);amdgpu_svm_attr_apply_change();// 更新映射up_write(svm-svm_lock);在这个窗口期内如果另一个线程读取 attr 并触发 restore可能看到属性与映射不一致的状态。代码中的-EAGAINretry 机制正是为了应对这种竞争// amdgpu_svm_attr_set() 中retry:ramdgpu_svm_attr_set_range(...);if(r-EAGAIN){amdgpu_svm_range_flush(svm);cond_resched();gotoretry;}如果是单层设计只需一把锁保护原子操作不需要 retry。(2) restore 路径必须逐段查属性因为svm_range不存储完整属性每次 restore 都要调用amdgpu_svm_attr_lookup_page_locked()逐段查询 attr_tree// restore 路径 — 每个 svm_range 都要回查 attr_treewhile(cursorlast_page){mutex_lock(attr_tree-lock);amdgpu_svm_attr_lookup_page_locked(attr_tree,cursor,attrs,seg_last);mutex_unlock(attr_tree-lock);amdgpu_svm_range_map_interval(svm,cursor,seg_last,attrs);cursorseg_last1;}如果是 1:1 对应svm_range直接内嵌属性即可restore 零查询开销。(3) LOCATION_CHANGE 的 destroy recreate 开销因为drm_gpusvm_range的migrate_devmemflag 在创建时不可变location 属性变更时必须销毁再重建所有重叠的 svm_range。这意味着丢弃已有的 GPU 映射和 DMA 状态重新走一遍完整的 map 流程。如果属性直接存在 svm_range 上且 flag 可变只需修改 flag 重新 migrate无需 destroy但这受限于drm_gpusvm框架设计。(4) 两棵独立 interval tree 的维护负担内存开销两棵树各自维护节点代码复杂度边界不对齐时的交叉查询逻辑容易出错调试难度排查问题需要同时 dump 两棵树的状态才能理清全貌10.2 为什么仍然采用这种设计核心原因是两层受不同的外部约束无法统一约束来源attr_rangesvm_range边界由谁决定用户 ioctl任意地址范围drm_gpusvm框架chunk 对齐 VMA notifier生命周期持久存在直到用户清除或进程退出可能被 mmu_notifier 随时回收再重建可控性amdgpu 驱动完全控制drm_gpusvm是 DRM 公共框架xe、amdgpu 共用不能改其 range 结构关键限制drm_gpusvm_range是 DRM 子系统的公共框架它的 range 拆分逻辑和生命周期不能为 amdgpu 的属性需求定制。所以 amdgpu 只能在外面再加一层属性管理。10.3 如果要简化的方向理论上如果drm_gpusvm_range能支持以下能力就可以去掉 attr_tree只维护一棵树需要的框架改动当前限制简化效果range 内嵌驱动私有属性drm_gpusvm_range无属性字段无需外部 attr_treemigrate_devmem可变 / 延迟决定创建时确定不可变无需 destroy recreaterange 拆分时感知属性边界chunk_size 纯硬件对齐减少 N:M 不对齐情况但这需要修改 DRM 公共框架影响面涉及 xe 等其他驱动短期内不现实。10.4 小结复杂性代价 设计收益 ───────────── ───────── 锁序窗口 retry 机制 属性层与映射层独立演进 restore 逐段查属性开销 svm_range 被回收后属性仍然保留 LOCATION_CHANGE destroyrecreate 不侵入 drm_gpusvm 公共框架 两棵 interval tree 维护成本 适配 KFD 属性语义兼容用户空间 API总结复杂性是真实的代价根本原因是 amdgpu 的属性管理需求与drm_gpusvm公共框架的 range 管理模型不匹配被迫用两层解耦来适配。如果未来drm_gpusvm框架能扩展属性支持能力这层复杂性可以消除。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2448403.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!