drm_pagemap 与 drm_gpusvm 的层次分离与迁移 API 不对称性分析

张开发
2026/5/21 17:36:00 15 分钟阅读
drm_pagemap 与 drm_gpusvm 的层次分离与迁移 API 不对称性分析
1. 背景在 DRM 子系统中设备内存VRAM的管理涉及两个核心框架框架职责操作对象drm_pagemap设备内存的分配/释放、页面迁移migrate_vma_*mm_struct、虚拟地址区间、物理页drm_gpusvmGPU 虚拟地址映射管理DMA 映射、PTE 更新drm_gpusvm_range、mmu_interval_notifier本文分析这两个框架的依赖关系指出当前迁移 API 的不对称性并提出改进方案。2. drm_pagemap 是否依赖 drm_gpusvm_range结论完全不依赖drm_pagemap在头文件和实现中均未引用drm_gpusvm的任何类型# 在 drm_pagemap.c 和 drm_pagemap.h 中搜索 drm_gpusvm $ grep -r drm_gpusvm\|gpusvm_range drm_pagemap.c include/drm/drm_pagemap.h 无匹配结果层次关系┌─────────────────────────────────────────────────────────┐ │ 驱动层 (Driver) │ │ 调用 drm_pagemap drm_gpusvm 实现 SVM 功能 │ └──────────┬──────────────────────────────┬───────────────┘ │ │ ▼ ▼ ┌─────────────────────┐ ┌──────────────────────────┐ │ drm_pagemap │ │ drm_gpusvm │ │ │ │ │ │ • populate_mm() │ │ • range_find_or_insert()│ │ • evict_to_ram() │ │ • range_get_pages() │ │ • migrate_to_ram() │ │ • range_evict() │ │ │ │ • range_unmap_pages() │ │ 操作: mm/page 层 │ │ 操作: GPU mapping 层 │ └──────────┬──────────┘ └──────────┬───────────────┘ │ │ ▼ ▼ ┌─────────────────────┐ ┌─────────────────────────┐ │ Linux MM 子系统 │ │ mmu_interval_notifier │ │ migrate_vma_* │ │ hmm_range_fault │ │ device_private page│ │ DMA mapping │ └─────────────────────┘ └─────────────────────────┘关键要点drm_pagemap直接使用migrate_vma_setup/pages/finalize操作进程页表只需要mm_struct和虚拟地址区间drm_gpusvm管理 GPU 端的 DMA 映射和 PTE通过mmu_interval_notifier跟踪页表变化两者是平行的框架由驱动层协调调用互不依赖3. 迁移 API 的不对称性3.1 迁移到 VRAM区间级 API ✅drm_pagemap_populate_mm()提供了区间级的迁移接口intdrm_pagemap_populate_mm(structdrm_pagemap*dpagemap,unsignedlongstart,// VA 起始地址unsignedlongend,// VA 结束地址structmm_struct*mm,// 进程地址空间unsignedlongtimeslice_ms);特征输入虚拟地址区间[start, end)mm_struct实现dpagemap-ops-populate_mm()→migrate_vma_setup/pages/finalize不需要drm_gpusvm_range可以在创建 GPU mapping 之前调用3.2 从 VRAM 驱逐仅有 Range 级 API ❌框架中没有对应的区间级驱逐 API。现有的驱逐方式有两个方式 Adrm_gpusvm_range_evict()— 需要 Rangeintdrm_gpusvm_range_evict(structdrm_gpusvm*gpusvm,structdrm_gpusvm_range*range){// 从 range 获取 mmu_interval_notifierstructmmu_interval_notifier*notifierrange-notifier-notifier;structhmm_rangehmm_range{.notifiernotifier,// ← 必须有 notifier.dev_private_ownerNULL,// ← NULL 触发 migrate_to_ram.startrange_start,.endrange_end,};hmm_range_fault(hmm_range);// ← hmm_range_fault 需要 notifier}工作原理hmm_range_fault()遍历虚拟地址区间中的每个页遇到device_private页且dev_private_owner NULL不匹配任何 owner触发该页的migrate_to_ram()回调回调函数执行migrate_device_*将页从 VRAM 搬回系统内存限制hmm_range_fault()要求mmu_interval_notifier做序列号一致性检查mmu_interval_read_begin/ seq 比较而这个 notifier 只能从drm_gpusvm_range获取。方式 Bdrm_pagemap_evict_to_ram()— 设备级intdrm_pagemap_evict_to_ram(structdrm_pagemap_devmem*devmem_allocation);这个 API 按VRAM allocation粒度驱逐不是按虚拟地址区间驱逐用于设备内存回收场景不适用于用户态 prefetch-to-SYSMEM 请求。3.3 不对称性总结方向API操作粒度需要 Range?RAM → VRAMdrm_pagemap_populate_mm()虚拟地址区间❌VRAM → RAMdrm_gpusvm_range_evict()单个 Range✅VRAM → RAMdrm_pagemap_evict_to_ram()设备分配块❌但不适用于 VA 级驱逐4. 当前驱逐实现的局限性由于缺乏区间级驱逐 API驱动层实现 prefetch-to-SYSMEM 时必须遍历 Rangestaticintdriver_evict_interval(structsvm*svm,unsignedlongstart,unsignedlonglast){// 必须遍历所有 notifier → 所有 rangefor_each_notifier(notifier,gpusvm,start,end){for_each_range(range,notifier,start,end){if(!range_in_vram(range))continue;drm_gpusvm_range_evict(gpusvm,range);// 逐个驱逐}}}这带来两个问题问题 1遗漏页面如果某些 VRAM 页面存在于进程页表中作为device_private页但对应的drm_gpusvm_range已经被 MMU notifier 回收了这些页面不会被驱逐。时序示例1. prefetch to VRAM → populate_mm() 将页面迁移到 VRAM 2. GPU 访问 → 创建 drm_gpusvm_rangeDMA 映射 VRAM 页 3. CPU 端 munmap/remap → MMU notifier 触发drm_gpusvm_range 被删除 4. mmap 新 VMA → 新 VMA 覆盖同一地址继承 VRAM device_private 页 5. prefetch to SYSMEM → 遍历 range... 但 range 已不存在 → 遗漏注意在实践中步骤 3 通常会触发migrate_to_ram通过 MMU notifier 的 invalidate 路径所以步骤 4 的 VRAM 页面大概率已经回到系统内存。但这依赖于具体的 notifier 处理逻辑理论上不是完全安全的。问题 2粒度不匹配drm_gpusvm_range的粒度与 mm 层的页面粒度不一致一个 Range 可能覆盖多个页面其中部分在 VRAM、部分在 RAMRange 的边界由 VMA 和 fault 粒度决定与迁移区间不一定对齐遍历 Range 引入了不必要的框架耦合5. 优化方案添加区间级驱逐 API5.1 提议接口在drm_pagemap层添加drm_pagemap_depopulate_mm()与populate_mm()对称/** * drm_pagemap_depopulate_mm() - Evict device pages back to system memory * dpagemap: Device pagemap * start: Start virtual address (inclusive) * end: End virtual address (exclusive) * mm: Process address space * * Walk the [start, end) virtual address range in mm. For each page * that is currently backed by device_private memory belonging to * dpagemap, migrate it back to system memory using migrate_vma_*. * * This is the reverse of drm_pagemap_populate_mm(). It operates at * the mm/page level and does NOT require any drm_gpusvm_range objects. * * Return: 0 on success, negative errno on failure. */intdrm_pagemap_depopulate_mm(structdrm_pagemap*dpagemap,unsignedlongstart,unsignedlongend,structmm_struct*mm);5.2 实现思路intdrm_pagemap_depopulate_mm(structdrm_pagemap*dpagemap,unsignedlongstart,unsignedlongend,structmm_struct*mm){unsignedlongaddr;intret0;for(addrstart;addrend;addrchunk_size){unsignedlongchunk_endmin(addrMIGRATE_CHUNK_SIZE,end);unsignedlongnpages(chunk_end-addr)PAGE_SHIFT;unsignedlong*src,*dst;/* 1. 扫描页表收集 device_private PFN */structmigrate_vmaargs{.vmafind_vma(mm,addr),.srcsrc,.dstdst,.startaddr,.endchunk_end,.pgmap_ownerdpagemap-owner,// 只迁移属于本设备的页.flagsMIGRATE_VMA_SELECT_DEVICE_PRIVATE,};retmigrate_vma_setup(args);if(ret)continue;/* 2. 分配系统内存目标页 */for(i0;inpages;i){if(src[i]MIGRATE_PFN_MIGRATE)dst[i]alloc_page_for_migration(...);}/* 3. 从 VRAM 复制到 RAMDMA */ops-copy_to_ram(pages,dma_addrs,npages,...);/* 4. 完成迁移 */migrate_vma_pages(args);migrate_vma_finalize(args);}returnret;}该实现还没有看到有人去upstream感兴趣的朋友们可以发力尝试一下哦。说不定内核提交史中留下了你的大名呢。5.3 对比特性当前方案遍历 Range优化方案depopulate_mm依赖 drm_gpusvm_range✅ 是❌ 否遗漏无 Range 的 VRAM 页可能不会与 populate_mm 对称❌✅实现复杂度低复用现有 API中需新增 pagemap ops需要 mmu_interval_notifier✅ 是hmm_range_fault❌ 否migrate_vma_*5.4 驱动层调用简化优化后驱动层的 prefetch-to-SYSMEM 实现从遍历 Range 简化为/* Before: 遍历 range */for_each_notifier(notifier,gpusvm,start,end)for_each_range(range,notifier,start,end)if(range_in_vram(range))drm_gpusvm_range_evict(gpusvm,range);/* After: 区间级 API与 populate_mm 对称 */drm_pagemap_depopulate_mm(dpagemap,start,end,mm);6. 总结drm_pagemap和drm_gpusvm是完全独立的框架层前者操作 mm/page 级别的物理内存迁移后者管理 GPU 端的 DMA 映射和 PTE。当前迁移 API 存在不对称性到 VRAM 的迁移有区间级 APIpopulate_mm但从 VRAM 的驱逐只有 Range 级 APIrange_evict导致驱逐路径被迫依赖drm_gpusvm_range。根本原因是drm_gpusvm_range_evict()使用hmm_range_fault()间接触发驱逐而hmm_range_fault()需要mmu_interval_notifier来自 Range。这是一种实现选择而非架构必要性——migrate_vma_*可以直接完成同样的工作。建议添加drm_pagemap_depopulate_mm()使用migrate_vma_*带MIGRATE_VMA_SELECT_DEVICE_PRIVATE直接在 mm 层完成驱逐消除对drm_gpusvm_range的不必要依赖实现与populate_mm的完全对称。

更多文章