drm_pagemap — drm提供的新迁移框架设计分析
本文详细分析
drm_pagemap的设计目的、原理和实现。
本文技术建立在前面的migrate_vma_*和ZONE_DEVICE技术上。如需理解这两个技术,请移步:
一、设计目的:为什么设计出来drm_pagemap?
1.1 问题背景
GPU 要实现统一虚拟内存(SVM),需要在系统内存和设备内存(VRAM)之间透明迁移数据。Linux 内核提供了 migrate_vma_* API 和 ZONE_DEVICE(MEMORY_DEVICE_PRIVATE)来支持这种迁移,但这些原始 API 非常底层且复杂。下面是直接使用内核原始 API 实现迁移数据的过程:
- devm_memremap_pages() → 注册 ZONE_DEVICE,需要正确设置 dev_pagemap_ops
- migrate_vma_setup() → 收集源页面,准备迁移数据结构
- 手动分配目标页面 + 填充 PFN
- 手动 DMA map 源/目标页面
- 调用驱动的 DMA 拷贝引擎
- migrate_vma_pages() → 原子交换页表
- migrate_vma_finalize() → 清理
- 处理反方向回迁(CPU fault 时自动触发)
每个 GPU 驱动(Intel Xe、AMD、NVIDIA Nouveau 等)如果各自实现这套逻辑,会有大量重复代码,且容易出错。
1.2 drm_pagemap 的定位
drm_pagemap 是 DRM 子系统的 设备内存迁移抽象层,位于 Linux mm 层和 GPU 驱动之间:
┌──────────────────────────────────────────────────────────┐
│ GPU 驱动 (Xe / AMDGPU / Nouveau) │
│ - 分配 VRAM (BO) │
│ - 提供 VRAM PFN │
│ - 使用 DMA 引擎拷贝数据 │
├──────────────────────────────────────────────────────────┤
│ drm_pagemap (DRM 子系统) ← 本文分析对象 │
│ - 统一的 ZONE_DEVICE ops │
│ - 封装 migrate_vma_* 四步迁移协议 │
│ - 管理 zone_device_data (zdd) │
│ - 处理 DMA 映射/解映射 │
│ - 处理 CPU fault 回迁 │
│ - timeslice 防抖机制 │
├──────────────────────────────────────────────────────────┤
│ Linux mm (migrate_vma_*, HMM, ZONE_DEVICE) │
│ - 页表操作 │
│ - 页面生命周期管理 │
│ - MMU notifier │
└──────────────────────────────────────────────────────────┘
一句话总结:drm_pagemap 让 GPU 驱动只需要关心"分配 VRAM"和"DMA 拷贝",其余的迁移协议、页面生命周期、zdd 管理、CPU 回迁全部由框架处理。这个就是drm_pagemap的职责和功能。从其名称上理解,就是设备页面映射。把设备内存映射为page,然后用来支持迁移。
接下来从具体的数据结构和API来理解该框架的实现。
二、核心概念与数据结构
2.1 三层对象模型
drm_pagemap 定义了三个层次的对象:
┌─────────────────────────────────────────────────────────────┐
│ 第1层: drm_pagemap (设备级,每个 VRAM 区域一个) │
│ - 对应一块可迁移的设备内存区域 │
│ - 包含 dev_pagemap (ZONE_DEVICE 注册) + drm_pagemap_ops │
│ - 生命周期 = 设备生命周期 │
├─────────────────────────────────────────────────────────────┤
│ 第2层: drm_pagemap_devmem (每次迁移一个) │
│ - 代表一次从 RAM 到 VRAM 的迁移动作 │
│ - 关联一个 VRAM allocation (如 BO) │
│ - 包含 drm_pagemap_devmem_ops (拷贝回调) │
│ - 生命周期 = 设备页面在 mm 中的生命周期 │
├─────────────────────────────────────────────────────────────┤
│ 第3层: drm_pagemap_zdd (每个设备页面一个) │
│ - zone_device_data,挂在 page->zone_device_data │
│ - 引用计数,引用 devmem_allocation │
│ - 负责异步释放(page_free 回调) │
│ - 记录 device_private_page_owner │
└─────────────────────────────────────────────────────────────┘
一句话总结:drm_pagemap = 给 VRAM 穿上 struct page 的衣服 + 在 RAM 和 VRAM 之间搬运数据 + 管理这些设备页面的生死。
2.2 关键数据结构
drm_pagemap — 设备内存管理器
struct drm_pagemap {
const struct drm_pagemap_ops *ops; // 驱动回调
struct device *dev; // 所属设备
};
驱动需要实现的操作:
struct drm_pagemap_ops {
// 将设备页面映射为可被 GPU 访问的地址
// 当 HMM 返回 device_private 页面时调用
struct drm_pagemap_addr (*device_map)(dpagemap, dev, page, order, dir);
// 解除 device_map 的映射
void (*device_unmap)(dpagemap, dev, addr);
// 将 mm 的虚拟地址范围迁移到设备内存
// 框架负责获取 mmap_lock,驱动负责分配 VRAM + 调用框架迁移函数
int (*populate_mm)(dpagemap, start, end, mm, timeslice_ms);
};
drm_pagemap_devmem — 一次迁移的上下文
struct drm_pagemap_devmem {
struct device *dev; // 所属设备
struct mm_struct *mm; // 目标地址空间
struct completion detached; // 所有设备页面已脱离
const struct drm_pagemap_devmem_ops *ops; // 拷贝回调
struct drm_pagemap *dpagemap; // 所属 drm_pagemap
size_t size; // 迁移大小
u64 timeslice_expiration; // 最早允许回迁的时间
};
驱动需要实现的操作:
struct drm_pagemap_devmem_ops {
// 释放 VRAM allocation(所有设备页面不再被使用时调用)
void (*devmem_release)(devmem_allocation);
// 填充 VRAM PFN 数组(从 VRAM offset → struct page PFN)
int (*populate_devmem_pfn)(devmem_allocation, npages, pfn[]);
// DMA 拷贝: 系统 RAM → VRAM
int (*copy_to_devmem)(pages[], pagemap_addr[], npages);
// DMA 拷贝: VRAM → 系统 RAM
int (*copy_to_ram)(pages[], pagemap_addr[], npages);
};
drm_pagemap_zdd — 设备页面的元数据
struct drm_pagemap_zdd {
struct kref refcount; // 引用计数
struct drm_pagemap_devmem *devmem_allocation; // 关联的迁移上下文
void *device_private_page_owner; // 页面所有者标识
};
安装在每个设备页面的 page->zone_device_data 中,当所有引用释放时:
- 通过
devmem_allocation->ops->devmem_release()释放 VRAM - 通过
complete_all(&devmem->detached)通知等待者
drm_pagemap_addr — 地址编码
struct drm_pagemap_addr {
dma_addr_t addr; // DMA 地址 或 驱动自定义地址
u64 proto : 54; // 互连协议 (SYSTEM / DRIVER / 自定义)
u64 order : 8; // 页面 order (2^order 个页面)
u64 dir : 2; // DMA 方向
};
proto 字段是 drm_pagemap 的核心抽象之一:
DRM_INTERCONNECT_SYSTEM:标准 DMA 地址,通过 PCIe 访问系统内存DRM_INTERCONNECT_DRIVER:驱动自定义地址(如 VRAM 物理地址),通过本地总线访问- 驱动可以定义更多协议值(如 Xe 的
XE_INTERCONNECT_VRAM)
三、迁移协议详解
3.1 RAM → VRAM 迁移(drm_pagemap_migrate_to_devmem)
这是最复杂的路径,完整流程如下:
drm_pagemap_migrate_to_devmem(devmem_allocation, mm, start, end, ...)
│
├── 1. 前置检查
│ ├── VMA 必须存在且完整覆盖 [start, end]
│ ├── VMA 必须是匿名映射 (vma_is_anonymous)
│ └── 分配工作缓冲区 (src[] + dst[] + pagemap_addr[] + pages[])
│
├── 2. 分配 zdd (zone_device_data)
│ └── drm_pagemap_zdd_alloc(pgmap_owner)
│
├── 3. 收集源页面 — migrate_vma_setup()
│ │ flags = MIGRATE_VMA_SELECT_SYSTEM (只收集系统页面)
│ │
│ │ 内核做的事:
│ │ ├── 遍历 [start, end] 的页表
│ │ ├── 对每个有效系统页面: 标记 MIGRATE_PFN_MIGRATE
│ │ ├── 从原页表中 unmap (获得独占控制)
│ │ └── 填充 migrate.src[] 数组
│ │
│ ├── 检查: cpages == npages? (必须全部可迁移)
│ │ └── 如果部分不可迁移 → 返回 -EBUSY
│ │
│ ├── 4. 填充目标 PFN — ops->populate_devmem_pfn()
│ │ │ 驱动做的事:
│ │ │ ├── 遍历 VRAM BO 的 TTM resource (buddy blocks)
│ │ │ ├── 将 VRAM 物理偏移转换为 struct page PFN
│ │ │ │ PFN = (vram_offset + pgmap.range.start) >> PAGE_SHIFT
│ │ │ └── 填充 migrate.dst[] 数组
│ │ │
│ │ └── 框架将每个目标页面关联到 zdd:
│ │ page->zone_device_data = zdd (通过 drm_pagemap_get_devmem_page)
│ │
│ ├── 5. DMA 映射源页面 — drm_pagemap_migrate_map_pages()
│ │ │ 对每个源系统页面:
│ │ │ ├── dma_map_page(dev, page, ..., DMA_TO_DEVICE)
│ │ │ └── pagemap_addr[i] = encode(dma_addr, SYSTEM, order, dir)
│ │ │
│ │ └── 目的: GPU 的 DMA 引擎需要通过 DMA 地址读取系统页面
│ │
│ ├── 6. 数据拷贝 — ops->copy_to_devmem(pages[], pagemap_addr[], npages)
│ │ │ 驱动做的事:
│ │ │ ├── pages[i] = VRAM 目标页面 (可转换为 VRAM 物理地址)
│ │ │ ├── pagemap_addr[i].addr = 系统源页面的 DMA 地址
│ │ │ └── 使用 GPU SDMA/Copy Engine:
│ │ │ src = pagemap_addr[i].addr (系统 DMA 地址, 通过 GART)
│ │ │ dst = page_to_vram_addr(pages[i])
│ │ │
│ │ └── 关键: 这一步的顺序是 RAM → VRAM
│ │
│ ├── 7. 原子页表交换 — migrate_vma_pages()
│ │ │ 内核做的事:
│ │ │ ├── 将 CPU 页表从指向旧系统页面改为指向设备页面
│ │ │ ├── 设备页面 PTE 类型为 device_private swap entry
│ │ │ └── CPU 后续访问会触发 page fault → migrate_to_ram 回调
│ │ │
│ │ └── 同时设置 timeslice 和 zdd 关联:
│ │ devmem_allocation->timeslice_expiration = jiffies + timeslice
│ │ zdd->devmem_allocation = devmem_allocation
│ │
│ └── 8. 完成 — migrate_vma_finalize()
│ │ 内核做的事:
│ │ ├── 释放旧系统页面引用
│ │ └── 完成迁移状态清理
│ │
│ └── DMA 解映射源页面
│ drm_pagemap_migrate_unmap_pages(dev, pagemap_addr, npages)
│
└── 清理工作缓冲区, 释放 zdd 引用
3.2 VRAM → RAM 回迁(CPU fault 触发)
当 CPU 访问一个已迁移到 VRAM 的虚拟地址时:
CPU 访问虚拟地址
│
▼
CPU 页表中是 device_private swap entry
│ → 触发 page fault
▼
Linux mm: handle_pte_fault → do_swap_page
│ → 识别为 device_private 页面
│ → 调用 dev_pagemap_ops->migrate_to_ram(vmf)
▼
drm_pagemap_migrate_to_ram(vmf) ← 框架提供的统一回调
│
├── timeslice 检查
│ if (jiffies < zdd->devmem_allocation->timeslice_expiration)
│ return 0; // 数据还在 timeslice 内,不迁移
│
├── __drm_pagemap_migrate_to_ram(vas, owner, page, fault_addr, size)
│ │
│ ├── migrate_vma_setup(MIGRATE_VMA_SELECT_DEVICE_PRIVATE)
│ │ 收集 [start, end] 中的设备页面到 src[]
│ │
│ ├── drm_pagemap_migrate_populate_ram_pfn()
│ │ 分配系统 RAM 页面作为目标,填充 dst[]
│ │ ├── 使用 vma_alloc_folio() 分配(支持 THP)
│ │ └── 确保 src 和 dst 页面 order 匹配
│ │
│ ├── drm_pagemap_migrate_map_pages(dev, pagemap_addr, dst, DMA_FROM_DEVICE)
│ │ DMA 映射目标 RAM 页面
│ │
│ ├── ops->copy_to_ram(pages[], pagemap_addr[], npages)
│ │ 驱动执行 SDMA 拷贝: VRAM → RAM
│ │
│ ├── migrate_vma_pages() → 原子交换页表
│ ├── migrate_vma_finalize() → 完成
│ └── drm_pagemap_migrate_unmap_pages() → DMA 解映射
│
└── 返回 0 或 VM_FAULT_SIGBUS
3.3 设备页面释放(page_free 回调)
当最后一个引用设备页面的进程退出或 unmap 时:
CPU 页表中的 device_private swap entry 被清除
│
▼
Linux mm: put_page(device_page)
│ → 引用计数归零
│ → 调用 dev_pagemap_ops->page_free(page)
▼
drm_pagemap_page_free(page)
│
├── zdd = page->zone_device_data
├── drm_pagemap_zdd_put(zdd)
│ → kref_put → drm_pagemap_zdd_destroy()
│
└── drm_pagemap_zdd_destroy(ref)
├── devmem = zdd->devmem_allocation
├── complete_all(&devmem->detached) // 通知等待者
└── devmem->ops->devmem_release(devmem) // 驱动释放 VRAM
└── 例如: xe_svm_devmem_release()
└── xe_bo_put_async(bo) // 释放 VRAM BO
关键设计:page_free 可能在 IRQ 上下文中被调用,但释放 VRAM(释放 BO)通常需要 sleeping lock。因此 zdd 的引用计数 + 异步释放模式非常重要。
3.4 Eviction 路径(drm_pagemap_evict_to_ram)
与 CPU fault 回迁不同,eviction 不需要 mmap lock,使用 migrate_device_* API:
内存压力 / 设备解绑
│
▼
drm_pagemap_evict_to_ram(devmem_allocation)
│
├── ops->populate_devmem_pfn() → 填充源 VRAM PFN
├── migrate_device_pfns(src, npages) → 收集设备页面(不需要 mmap lock)
├── drm_pagemap_migrate_populate_ram_pfn() → 分配 RAM 目标页面
├── drm_pagemap_migrate_map_pages() → DMA 映射 RAM 页面
├── ops->copy_to_ram() → SDMA 拷贝
├── migrate_device_pages() → 页表交换
└── migrate_device_finalize() → 完成
└── 如果 completion 未完成,重试最多 2 次
四、drm_pagemap_zdd 的设计精髓
zdd(zone_device_data)是理解 drm_pagemap 的关键。每个 device_private 页面通过 page->zone_device_data 指向一个 zdd:
page (device_private)
├── zone_device_data ──→ drm_pagemap_zdd
│ ├── refcount
│ ├── devmem_allocation ──→ drm_pagemap_devmem
│ │ ├── ops (copy/release 回调)
│ │ ├── dpagemap ──→ drm_pagemap
│ │ ├── size
│ │ └── timeslice_expiration
│ └── device_private_page_owner
└── _refcount
4.1 引用计数语义
zdd 引用增加:
- drm_pagemap_get_devmem_page(page, zdd) → 迁移时,每个设备页面 +1
zdd 引用减少:
- drm_pagemap_page_free(page) → page 的 refcount 归零时 → zdd put
zdd refcount 归零:
- drm_pagemap_zdd_destroy()
→ complete_all(&devmem->detached) // 信号
→ ops->devmem_release(devmem) // 释放 VRAM
→ kfree(zdd)
4.2 一个 devmem_allocation 对应多个 zdd?
不是。一个 devmem_allocation 对应 一个 zdd,但这个 zdd 被同一次迁移中的所有设备页面共享引用:
迁移 N 个页面到 VRAM:
1. 创建 1 个 zdd
2. N 个设备页面都指向同一个 zdd → refcount = N
3. 当所有 N 个页面都被 page_free → refcount 归零 → 释放 VRAM BO
这确保了: VRAM BO 在任何一个设备页面还被使用时都不会被释放
4.3 owner 字段的作用
device_private_page_owner 用于区分不同驱动/设备的设备页面。在 HMM fault 时:
hmm_range.dev_private_owner = ctx->device_private_page_owner;
- 如果遇到的 device_private 页面 owner 等于 自己 → 视为"本地"设备页面
- 如果遇到的 device_private 页面 owner 不等于 自己 → 内核自动触发 migrate_to_ram 迁回
这使得多设备场景下,GPU A 不会直接访问 GPU B 的设备页面,而是先迁移回 RAM。
五、timeslice 防抖机制
5.1 问题
如果一个页面频繁在 CPU 和 GPU 之间来回迁移(“ping-pong”),性能会严重下降。
5.2 解决方案
drm_pagemap 引入了 timeslice_expiration:
// 迁移到 VRAM 时设置
devmem_allocation->timeslice_expiration = get_jiffies_64() +
msecs_to_jiffies(timeslice_ms);
// CPU fault 回迁时检查
if (time_before64(get_jiffies_64(), zdd->devmem_allocation->timeslice_expiration))
return 0; // 还在 timeslice 内,不允许回迁
这意味着数据迁移到 VRAM 后,在 timeslice_ms 毫秒内即使 CPU 访问也不会回迁,CPU 的 fault handler 直接返回 0(让 CPU 等待)。这给了 GPU 足够的时间完成计算。
六、与 drm_gpusvm 的协作
drm_pagemap 和 drm_gpusvm 是两个互补的框架:
drm_gpusvm drm_pagemap
├── 管理 GPU 虚拟地址空间 ├── 管理设备物理内存迁移
├── MMU notifier 管理 ├── ZONE_DEVICE 注册
├── range 区间树 ├── migrate_vma_* 封装
├── HMM fault + DMA 映射 ├── CPU fault 回迁
└── GPU 页表映射 (通过驱动) └── 设备页面生命周期
协作流程:
1. drm_gpusvm_range_get_pages() 调用 hmm_range_fault()
→ 如果返回 device_private 页面
→ 调用 drm_pagemap->ops->device_map() 获取 VRAM DMA 地址
→ 存入 range->pages.dma_addr[].proto = DRM_INTERCONNECT_DRIVER
2. 驱动的 GPU PTE 更新代码检查 proto:
- SYSTEM → 标准 DMA 地址, 通过 PCIe 访问
- DRIVER → VRAM 地址, 本地总线直接访问
3. drm_pagemap_populate_mm() → 迁移数据到 VRAM
→ 成功后, hmm_range_fault() 返回 device_private 页面
→ 走上面 device_map 路径
七、Xe 驱动参考实现剖析
Xe(Intel)是 drm_pagemap 的第一个用户,展示了完整的使用模式:
7.1 设备注册(xe_devm_add)
int xe_devm_add(struct xe_tile *tile, struct xe_vram_region *vr)
{
// 1. 在 iomem_resource 中分配一段伪物理地址空间
res = devm_request_free_mem_region(dev, &iomem_resource, vr->usable_size);
// 2. 注册 ZONE_DEVICE
vr->pagemap.type = MEMORY_DEVICE_PRIVATE;
vr->pagemap.range.start = res->start;
vr->pagemap.range.end = res->end;
vr->pagemap.ops = drm_pagemap_pagemap_ops_get(); // ← 框架统一 ops
vr->pagemap.owner = xe_svm_devm_owner(xe);
devm_memremap_pages(dev, &vr->pagemap);
// 3. 初始化 drm_pagemap
vr->dpagemap.dev = dev;
vr->dpagemap.ops = &xe_drm_pagemap_ops;
// 4. 记录 PFN 基址 (用于 VRAM offset → PFN 转换)
vr->hpa_base = res->start;
}
关键:pagemap.ops 使用的是 drm_pagemap_pagemap_ops_get() 返回的框架统一 ops,而不是驱动自己的 ops。这样 page_free 和 migrate_to_ram 回调由框架统一处理。
7.2 populate_mm 实现
static int xe_drm_pagemap_populate_mm(struct drm_pagemap *dpagemap,
unsigned long start, unsigned long end,
struct mm_struct *mm, unsigned long timeslice_ms)
{
// 1. 分配 VRAM BO
bo = xe_bo_create_locked(xe, ..., end - start, XE_BO_FLAG_VRAM(...));
// 2. 初始化 devmem_allocation(嵌入在 BO 中)
drm_pagemap_devmem_init(&bo->devmem_allocation, dev, mm,
&dpagemap_devmem_ops, dpagemap, end - start);
// 3. 将 buddy block 的 private 指向 vram_region(用于 PFN 计算)
list_for_each_entry(block, blocks, link)
block->private = vr;
// 4. 执行迁移
err = drm_pagemap_migrate_to_devmem(&bo->devmem_allocation, mm,
start, end, timeslice_ms, owner);
if (err)
xe_svm_devmem_release(&bo->devmem_allocation);
}
7.3 PFN 填充
static int xe_svm_populate_devmem_pfn(struct drm_pagemap_devmem *devmem,
unsigned long npages, unsigned long *pfn)
{
struct xe_bo *bo = to_xe_bo(devmem);
struct list_head *blocks = &to_vram_resource(bo->ttm.resource)->blocks;
// 遍历 buddy allocator 分配的物理块
list_for_each_entry(block, blocks, link) {
struct xe_vram_region *vr = block->private;
u64 block_pfn = (drm_buddy_block_offset(block) + vr->hpa_base)
>> PAGE_SHIFT;
for (i = 0; i < block_size >> PAGE_SHIFT; ++i)
pfn[j++] = block_pfn + i;
}
}
公式:PFN = (VRAM 物理偏移 + pgmap.range.start) >> PAGE_SHIFT
7.4 device_map 实现
static struct drm_pagemap_addr
xe_drm_pagemap_device_map(struct drm_pagemap *dpagemap, struct device *dev,
struct page *page, unsigned int order,
enum dma_data_direction dir)
{
if (pgmap_dev == dev) {
// 同一设备: 将 device_private page 转为 VRAM 物理地址
addr = xe_vram_region_page_to_dpa(page_to_vr(page), page);
prot = XE_INTERCONNECT_VRAM;
} else {
addr = DMA_MAPPING_ERROR; // 不同设备: 不支持直接访问
}
return drm_pagemap_addr_encode(addr, prot, order, dir);
}
这个 device_map 被 drm_gpusvm_range_get_pages() 调用,当 HMM fault 返回的页面是 device_private 时,框架通过此回调获取 GPU 可用的地址。
八、设计模式总结
8.1 策略模式(Strategy Pattern)
drm_pagemap 通过两组 ops 将变化点交给驱动:
框架不变的部分: 驱动变化的部分:
├── zdd 生命周期管理 ├── drm_pagemap_ops
├── migrate_vma_* 协议编排 │ ├── device_map (地址转换)
├── DMA 映射/解映射 │ └── populate_mm (分配+迁移)
├── CPU fault 回迁流程 │
├── timeslice 检查 └── drm_pagemap_devmem_ops
└── page_free 异步释放 ├── devmem_release (释放 VRAM)
├── populate_devmem_pfn (PFN)
├── copy_to_devmem (RAM→VRAM)
└── copy_to_ram (VRAM→RAM)
8.2 容器模式
drm_pagemap_devmem 设计为可嵌入驱动结构体:
// Xe 的做法: devmem 嵌入 BO
struct xe_bo {
struct ttm_buffer_object ttm;
struct drm_pagemap_devmem devmem_allocation; // 嵌入
...
};
// 通过 container_of 回溯
static struct xe_bo *to_xe_bo(struct drm_pagemap_devmem *devmem)
{
return container_of(devmem, struct xe_bo, devmem_allocation);
}
8.3 统一 dev_pagemap_ops
框架提供的 drm_pagemap_pagemap_ops_get() 返回统一的 ops:
static const struct dev_pagemap_ops drm_pagemap_pagemap_ops = {
.page_free = drm_pagemap_page_free, // 统一的页面释放
.migrate_to_ram = drm_pagemap_migrate_to_ram, // 统一的回迁处理
};
驱动 不需要 自己实现这两个回调。框架通过 page->zone_device_data (zdd) → devmem_allocation → ops 链条自动路由到正确的驱动回调。
九、完整对象生命周期
驱动初始化:
xe_devm_add()
├── devm_request_free_mem_region() → 分配伪物理地址空间
├── devm_memremap_pages() → 注册 ZONE_DEVICE, 创建 struct page
└── 初始化 drm_pagemap → 设置 ops + dev
迁移 RAM → VRAM:
populate_mm()
├── 创建 BO (VRAM) → 分配 VRAM 物理空间
├── drm_pagemap_devmem_init() → 初始化迁移上下文
└── drm_pagemap_migrate_to_devmem()
├── 创建 zdd → 关联 owner
├── migrate_vma_setup() → 收集源页面
├── populate_devmem_pfn() → 驱动提供 VRAM PFN
├── 设备页面关联 zdd → page->zone_device_data = zdd
├── copy_to_devmem() → SDMA 拷贝 RAM → VRAM
├── migrate_vma_pages() → 原子交换页表
└── 设置 timeslice → 防抖
稳态运行:
GPU 通过 device_map() 获取 VRAM 地址, 直接访问
CPU 访问触发 fault → 检查 timeslice → 到期则回迁
页面回收:
CPU 最后引用释放 → page_free()
→ zdd_put() → refcount 归零
→ devmem_release() → 释放 VRAM BO
→ VRAM 空间返回 buddy allocator
驱动卸载:
devm_memremap_pages 的 devm 资源自动清理
所有 struct page 失效
十、关键设计决策与权衡
| 设计决策 | 理由 |
|---|---|
page_free 由框架统一处理 |
避免每个驱动重复实现 zdd 管理和异步释放 |
migrate_to_ram 由框架统一处理 |
回迁逻辑(分配 RAM 页 + DMA map + 拷贝)是通用的 |
populate_mm 由驱动实现 |
VRAM 分配策略是驱动特定的(BO 大小、placement 策略) |
| zdd 引用计数 | 处理 IRQ 上下文中的 page_free,避免在中断中持有 sleeping lock |
| timeslice 机制 | 框架级防抖,避免驱动各自实现 |
drm_pagemap_addr.proto 字段 |
统一表示系统 DMA 地址和设备本地地址,GPU PTE 更新可以统一处理 |
devmem_allocation 可嵌入 |
避免额外分配,与 BO 生命周期自然绑定 |
cpages == npages 要求 |
简化实现,全有或全无迁移,避免部分迁移的复杂状态管理 |
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)