当前位置: 首页 > news >正文

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

1. 背景

在 DRM 子系统中,设备内存(VRAM)的管理涉及两个核心框架:

框架职责操作对象
drm_pagemap设备内存的分配/释放、页面迁移(migrate_vma_*mm_struct、虚拟地址区间、物理页
drm_gpusvmGPU 虚拟地址映射管理(DMA 映射、PTE 更新)drm_gpusvm_rangemmu_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。现有的驱逐方式有两个:

方式 A:drm_gpusvm_range_evict()— 需要 Range
intdrm_gpusvm_range_evict(structdrm_gpusvm*gpusvm,structdrm_gpusvm_range*range){// 从 range 获取 mmu_interval_notifierstructmmu_interval_notifier*notifier=&range->notifier->notifier;structhmm_rangehmm_range={.notifier=notifier,// ← 必须有 notifier.dev_private_owner=NULL,// ← NULL 触发 migrate_to_ram.start=range_start,.end=range_end,};hmm_range_fault(&hmm_range);// ← hmm_range_fault 需要 notifier}

工作原理:

  1. hmm_range_fault()遍历虚拟地址区间中的每个页
  2. 遇到device_private页且dev_private_owner == NULL(不匹配任何 owner)
  3. 触发该页的migrate_to_ram()回调
  4. 回调函数执行migrate_device_*将页从 VRAM 搬回系统内存

限制:hmm_range_fault()要求mmu_interval_notifier做序列号一致性检查(mmu_interval_read_begin/ seq 比较),而这个 notifier 只能从drm_gpusvm_range获取。

方式 B:drm_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 时必须遍历 Range:

staticintdriver_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_range,DMA 映射 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、部分在 RAM
  • Range 的边界由 VMA 和 fault 粒度决定,与迁移区间不一定对齐
  • 遍历 Range 引入了不必要的框架耦合

5. 优化方案:添加区间级驱逐 API

5.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;intret=0;for(addr=start;addr<end;addr+=chunk_size){unsignedlongchunk_end=min(addr+MIGRATE_CHUNK_SIZE,end);unsignedlongnpages=(chunk_end-addr)>>PAGE_SHIFT;unsignedlong*src,*dst;/* 1. 扫描页表,收集 device_private PFN */structmigrate_vmaargs={.vma=find_vma(mm,addr),.src=src,.dst=dst,.start=addr,.end=chunk_end,.pgmap_owner=dpagemap->owner,// 只迁移属于本设备的页.flags=MIGRATE_VMA_SELECT_DEVICE_PRIVATE,};ret=migrate_vma_setup(&args);if(ret)continue;/* 2. 分配系统内存目标页 */for(i=0;i<npages;i++){if(src[i]&MIGRATE_PFN_MIGRATE)dst[i]=alloc_page_for_migration(...);}/* 3. 从 VRAM 复制到 RAM(DMA) */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. 总结

  1. drm_pagemapdrm_gpusvm是完全独立的框架层,前者操作 mm/page 级别的物理内存迁移,后者管理 GPU 端的 DMA 映射和 PTE。

  2. 当前迁移 API 存在不对称性:到 VRAM 的迁移有区间级 API(populate_mm),但从 VRAM 的驱逐只有 Range 级 API(range_evict),导致驱逐路径被迫依赖drm_gpusvm_range

  3. 根本原因drm_gpusvm_range_evict()使用hmm_range_fault()间接触发驱逐,而hmm_range_fault()需要mmu_interval_notifier(来自 Range)。这是一种实现选择而非架构必要性——migrate_vma_*可以直接完成同样的工作。

  4. 建议添加drm_pagemap_depopulate_mm(),使用migrate_vma_*(带MIGRATE_VMA_SELECT_DEVICE_PRIVATE)直接在 mm 层完成驱逐,消除对drm_gpusvm_range的不必要依赖,实现与populate_mm的完全对称。

http://www.jsqmd.com/news/579516/

相关文章:

  • 2026年口碑好的减震气囊空气弹簧/座椅空气弹簧/农用车空气弹簧/汽车空气弹簧可靠供应商推荐 - 品牌宣传支持者
  • 实战分享:如何用GeoTools 28.2在Java项目中高效解析多种地理数据格式
  • Windows 11 家庭版安装 WSL + Docker 踩坑记:从 Store 地狱到 --web-download 救赎
  • Ostrakon-VL终端入门必看:双传感器模式切换原理与异常处理机制
  • 《AI 小游戏开发(5)|零基础复刻经典贪吃蛇!AI 生成完整代码,支持难度切换》
  • OpenClaw版本升级:Qwen3-4B兼容性测试与迁移方案
  • [已解决]Splunk agent 不向outputs 发送log
  • 2026年比较好的座椅空气弹簧/浙江空气弹簧/半挂空气弹簧推荐厂家精选 - 品牌宣传支持者
  • 别再盲目调大`--max-memory`!Python服务成本失控的真正元凶藏在这3个被忽略的`__slots__`陷阱里
  • Vue3路由缓存优化指南:用keep-alive的include+max实现淘宝级页面保活
  • 云端书库革命:利用Docker部署calibre-web实现跨设备阅读自由
  • Scikit-learn的随机SVD真的能“超快”降维吗?先看清代价
  • 极客车影|BOP保镖工厂直营店全维度品牌介绍
  • pip install -e . 解析
  • Flutter鸿蒙化适配中遇到的问题
  • 2026年靠谱的冷库提升门/铝合金提升门优质厂家推荐榜 - 品牌宣传支持者
  • 舵机PWM脉宽与角度换算公式
  • 一个关键词的SEO优化过程中需要注意什么
  • 微前端进阶:WuJie + Vite + Vue3 的无界架构性能优化全攻略
  • 【窝炉】流化床窝炉【含Matlab源码 15270期】
  • 2.3: Java的基础概念(变量)
  • OpenClaw移动办公:通过钉钉调用Qwen3.5-9B处理紧急任务
  • LLVM Loop循环的中间代码生成
  • OpenClaw配置备份指南:Qwen3-4B模型参数迁移方案
  • Electron实战:将你的网页应用打包成桌面客户端
  • 【C++27 constexpr革命性突破】:5大新增约束与3类不可逆性能跃迁,资深编译器工程师亲授落地实践
  • Qwen-Image-2512部署案例:某游戏工作室用该镜像将像素图产出周期缩短70%
  • 连国家药监局都重磅发文!AI + 药品监管落地方向,学AI刻不容缓!
  • 开源CLAP音频分类实战案例:上传MP3/WAV即得语义标签
  • OpenClaw备份方案:Qwen2.5-VL-7B技能与配置的定期同步