1. 概述
当 CPU 访问一个已迁移到设备内存(如 GPU VRAM)的虚拟地址时,由于该页面在CPU 页表中是 DEVICE_PRIVATE swap entry(非 present),硬件触发 page fault,内核通过两级回调机制将数据从设备内存拷回系统 RAM:
- 第一级 :
dev_pagemap_ops.migrate_to_ram--- DRM pagemap 框架处理通用逻辑 - 第二级 :
drm_pagemap_devmem_ops.copy_to_ram--- GPU 驱动提供硬件特定的数据拷贝
这种设计将迁移流程的编排 (页面收集、RAM 分配、DMA mapping、页表更新)与数据搬运(硬件 DMA 引擎拷贝)解耦,使不同 GPU 驱动共享同一套迁移框架。
2. 完整调用链
CPU 访问虚拟地址 (该地址对应 DEVICE_PRIVATE 页面)
│
▼
① 硬件 page fault (PTE 中是 swap entry, 非 present)
│
▼
② do_swap_page() [mm/memory.c]
│ 检测到 softleaf_is_device_private(entry)
│ 从 swap entry 取出 vmf->page
│ 获取 pgmap = page_pgmap(vmf->page)
│
▼
③ pgmap->ops->migrate_to_ram(vmf) [mm/memory.c]
│
│ pgmap->ops 由驱动初始化时设置:
│ pgmap->ops = drm_pagemap_pagemap_ops_get()
│ 即 drm_pagemap_pagemap_ops = {
│ .migrate_to_ram = drm_pagemap_migrate_to_ram,
│ .folio_free = drm_pagemap_folio_free,
│ }
│
▼
④ drm_pagemap_migrate_to_ram(vmf) [drm_pagemap.c]
│ 从 vmf->page->zone_device_data 取 zdd
│ zdd->devmem_allocation->size 决定迁移粒度
│
▼
⑤ __drm_pagemap_migrate_to_ram(vma, page, fault_addr, size)
│ [drm_pagemap.c]
│
│ a) timeslice 检查 --- 若页面刚迁移到设备不久,跳过迁回(防止抖动)
│ b) migrate_vma_setup() --- 收集需要迁移的源页面(设备页面)
│ c) 从 zdd 获取驱动 ops:
│ ops = zdd->devmem_allocation->ops (驱动注册的 devmem_ops)
│ dev = zdd->devmem_allocation->dev
│ d) drm_pagemap_migrate_populate_ram_pfn() --- 分配目标 RAM 页面
│ e) drm_pagemap_migrate_map_pages() --- DMA map 目标 RAM 页面
│ f) 构建 pages[] 数组(源设备页面)
│
▼
⑥ ops->copy_to_ram(pages, pagemap_addr, npages, NULL)
│ [drm_pagemap.c]
│ 调用驱动注册的 copy_to_ram 回调
│ 驱动通过硬件 DMA 引擎将数据从设备内存拷贝到 RAM
│
▼
⑦ 驱动 copy_to_ram 实现 [GPU 驱动]
│ a) 从 pages[0] 获取驱动上下文(设备、地址基准等)
│ b) 构建源地址(设备内存偏移)和目标地址(DMA 地址)数组
│ c) 将连续页面合并为批次,提交硬件 DMA 拷贝
│ d) 等待 DMA 完成
│
▼
⑧ 返回 __drm_pagemap_migrate_to_ram():
migrate_vma_pages() --- 更新页表,swap entry → 新 RAM page
migrate_vma_finalize() --- 清理旧设备页面引用
返回 0(成功)→ CPU 重新执行访问指令
3.关键数据结构的关联
ZONE_DEVICE page
│
├── page_pgmap(page) ──→ dev_pagemap
│ └── ops = drm_pagemap_pagemap_ops (第一级: 框架回调)
│ ├── .migrate_to_ram
│ └── .folio_free
│
└── page->zone_device_data ──→ drm_pagemap_zdd
├── dpagemap ──→ struct drm_pagemap
│ ├── ops (drm_pagemap_ops)
│ ├── drm (struct drm_device)
│ └── pagemap
│
└── devmem_allocation ──→ struct drm_pagemap_devmem
├── ops (第二级: 驱动回调)
│ ├── .copy_to_ram
│ ├── .copy_to_devmem
│ ├── .populate_devmem_pfn
│ └── .devmem_release
├── dev
├── dpagemap
├── size (迁移粒度)
└── timeslice_expiration
4. 两级回调设计
4.1 第一级:dev_pagemap_ops(内核 mm → DRM 框架)
| 回调 | 实现 | 触发时机 | 职责 |
|---|---|---|---|
migrate_to_ram |
drm_pagemap_migrate_to_ram() |
CPU page fault | 编排整个迁移流程 |
folio_free |
drm_pagemap_folio_free() |
页面释放 | 释放 zdd 引用计数 |
这一级由 DRM pagemap 框架统一提供,所有使用该框架的 GPU 驱动共享同一实现。
驱动在初始化 dev_pagemap 时调用:
c
pgmap->ops = drm_pagemap_pagemap_ops_get();
4.2 第二级:drm_pagemap_devmem_ops(DRM 框架 → GPU 驱动)
| 回调 | 触发时机 | 职责 |
|---|---|---|
copy_to_ram |
设备页面迁回 RAM | 硬件 DMA 拷贝:设备内存 → RAM |
copy_to_devmem |
RAM 页面迁移到设备 | 硬件 DMA 拷贝:RAM → 设备内存 |
populate_devmem_pfn |
迁移到设备前 | 将设备内存分配转为 PFN 数组 |
devmem_release |
所有页面迁回后 | 释放设备内存分配 |
这一级由各 GPU 驱动各自实现,通过 drm_pagemap_devmem_init() 注册:
c
drm_pagemap_devmem_init(&devmem, dev, mm, &driver_devmem_ops, dpagemap, size, fence);
5. 设计优势
┌─────────────────────────────────────────────────────────────┐
│ 内核 mm 子系统 │
│ do_swap_page() → pgmap->ops->migrate_to_ram() │
└──────────────────────────┬──────────────────────────────────┘
│ 第一级回调
▼
┌─────────────────────────────────────────────────────────────┐
│ DRM pagemap 框架 │
│ 页面收集 → RAM 分配 → DMA mapping → 调用驱动 → 页表更新 │
│ │
│ 统一处理: migrate_vma, timeslice, zdd 生命周期管理 │
└──────────────────────────┬──────────────────────────────────┘
│ 第二级回调
┌────────────┼────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 驱动 A │ │ 驱动 B │ │ 驱动 C │
│ (SDMA) │ │ (CE) │ │ (...) │
│copy_to_ │ │copy_to_ │ │copy_to_ │
│ ram() │ │ ram() │ │ ram() │
└──────────┘ └──────────┘ └──────────┘
- 关注点分离:框架管编排,驱动管拷贝
- 代码复用:页面收集、RAM 分配、DMA mapping、页表更新等通用逻辑只写一次
- 防抖动:timeslice 机制由框架统一实现,驱动无需关心
- 生命周期:zdd 引用计数和 devmem_release 回调保证资源正确释放
6. 迁移流程中的地址空间
设备内存偏移 [0, device_mem_size) ← 设备内存管理器(buddy/slab 等)
+ hpa_base
HPA / PFN [hpa_base, hpa_base+..) ← ZONE_DEVICE struct page 管理
(devm_memremap_pages 注册)
驱动在 copy_to_ram / copy_to_devmem 中需要完成:
设备页面 PFN → HPA → 设备内存偏移 → 硬件可用的 DMA 源地址
7. copy_to_ram 全部调用站点
框架中有三处调用 ops->copy_to_ram(),对应不同的迁移场景:
| 调用站点 | 场景 | 触发条件 |
|---|---|---|
__drm_pagemap_migrate_to_ram() |
CPU page fault 迁回 | CPU 访问 DEVICE_PRIVATE 页面 |
drm_pagemap_evict_to_ram() |
主动驱逐 | 设备内存压力或进程退出 |
drm_pagemap_migrate_remote_to_local() |
P2P 迁移中间步骤 | 跨设备迁移,先从源设备迁回 RAM |
所有路径共享同一个驱动回调,驱动实现只需关心"把这些设备页面的数据拷到这些 DMA 地址",不需要区分是哪种场景触发的迁移。