
本文详细分析drm_pagemap的设计目的、原理和实现。本分析基于Linux-6.18的内核。今天下了Linux-7.0-rc4的内核发现该对象又扩展了。实在跟不上变化啊恰恰也说明内核对SVM的支持在快速迭代中。不过还好新的设计让结构体含有一个指向dev_pagemap的对象我觉得本来就应该这样设计而且最好dev_pagemap的初始化都放在drm_pagemap的初始化中直接放一个对象就行完全不用指针。看后面社区会不会按我的想法走。所以下面开头的两个技术点要先去理解好。本文技术建立在前面的migrate_vma_*和ZONE_DEVICE技术上。如需理解这两个技术请移步mm子系统中的ZONE_DEVICE 设计Device Memory Migration 机制一、设计目的为什么设计出来drm_pagemap1.1 问题背景GPU 要实现统一虚拟内存SVM需要在系统内存和设备内存VRAM之间透明迁移数据。Linux 内核提供了migrate_vma_*API 和ZONE_DEVICEMEMORY_DEVICE_PRIVATE来支持这种迁移但这些原始 API 非常底层且复杂。下面是直接使用内核原始 API 实现迁移数据的过程:devm_memremap_pages() → 注册 ZONE_DEVICE需要正确设置 dev_pagemap_opsmigrate_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的职责和功能。从其名称上理解就是设备页面映射把mm中的一段VMA用设备内存来填充然后用来支持迁移。英文版注释populate the part of mm with dpagemap memory.我举起的大拇指精辟。接下来从具体的数据结构和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 — 设备内存管理器structdrm_pagemap{conststructdrm_pagemap_ops*ops;// 驱动回调structdevice*dev;// 所属设备};驱动需要实现的操作structdrm_pagemap_ops{// 将设备页面映射为可被 GPU 访问的地址// 当 HMM 返回 device_private 页面时调用structdrm_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 — 一次迁移的上下文structdrm_pagemap_devmem{structdevice*dev;// 所属设备structmm_struct*mm;// 目标地址空间structcompletiondetached;// 所有设备页面已脱离conststructdrm_pagemap_devmem_ops*ops;// 拷贝回调structdrm_pagemap*dpagemap;// 所属 drm_pagemapsize_tsize;// 迁移大小u64 timeslice_expiration;// 最早允许回迁的时间};驱动需要实现的操作structdrm_pagemap_devmem_ops{// 释放 VRAM allocation所有设备页面不再被使用时调用void(*devmem_release)(devmem_allocation);// 填充 VRAM PFN 数组从 VRAM offset → struct page PFNint(*populate_devmem_pfn)(devmem_allocation,npages,pfn[]);// DMA 拷贝: 系统 RAM → VRAMint(*copy_to_devmem)(pages[],pagemap_addr[],npages);// DMA 拷贝: VRAM → 系统 RAMint(*copy_to_ram)(pages[],pagemap_addr[],npages);};drm_pagemap_zdd — 设备页面的元数据structdrm_pagemap_zdd{structkrefrefcount;// 引用计数structdrm_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 — 地址编码structdrm_pagemap_addr{dma_addr_taddr;// 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_SIGBUS3.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 的设计精髓zddzone_device_data是理解 drm_pagemap 的关键。每个 device_private 页面通过page-zone_device_data指向一个 zddpage (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 └── _refcount4.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_ownerctx-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_expirationget_jiffies_64()msecs_to_jiffies(timeslice_ms);// CPU fault 回迁时检查if(time_before64(get_jiffies_64(),zdd-devmem_allocation-timeslice_expiration))return0;// 还在 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 驱动参考实现剖析XeIntel是 drm_pagemap 的第一个用户展示了完整的使用模式7.1 设备注册xe_devm_addintxe_devm_add(structxe_tile*tile,structxe_vram_region*vr){// 1. 在 iomem_resource 中分配一段伪物理地址空间resdevm_request_free_mem_region(dev,iomem_resource,vr-usable_size);// 2. 注册 ZONE_DEVICEvr-pagemap.typeMEMORY_DEVICE_PRIVATE;vr-pagemap.range.startres-start;vr-pagemap.range.endres-end;vr-pagemap.opsdrm_pagemap_pagemap_ops_get();// ← 框架统一 opsvr-pagemap.ownerxe_svm_devm_owner(xe);devm_memremap_pages(dev,vr-pagemap);// 3. 初始化 drm_pagemapvr-dpagemap.devdev;vr-dpagemap.opsxe_drm_pagemap_ops;// 4. 记录 PFN 基址 (用于 VRAM offset → PFN 转换)vr-hpa_baseres-start;}关键pagemap.ops使用的是drm_pagemap_pagemap_ops_get()返回的框架统一 ops而不是驱动自己的 ops。这样page_free和migrate_to_ram回调由框架统一处理。7.2 populate_mm 实现staticintxe_drm_pagemap_populate_mm(structdrm_pagemap*dpagemap,unsignedlongstart,unsignedlongend,structmm_struct*mm,unsignedlongtimeslice_ms){// 1. 分配 VRAM BOboxe_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-privatevr;// 4. 执行迁移errdrm_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 填充staticintxe_svm_populate_devmem_pfn(structdrm_pagemap_devmem*devmem,unsignedlongnpages,unsignedlong*pfn){structxe_bo*boto_xe_bo(devmem);structlist_head*blocksto_vram_resource(bo-ttm.resource)-blocks;// 遍历 buddy allocator 分配的物理块list_for_each_entry(block,blocks,link){structxe_vram_region*vrblock-private;u64 block_pfn(drm_buddy_block_offset(block)vr-hpa_base)PAGE_SHIFT;for(i0;iblock_sizePAGE_SHIFT;i)pfn[j]block_pfni;}}公式PFN (VRAM 物理偏移 pgmap.range.start) PAGE_SHIFT7.4 device_map 实现staticstructdrm_pagemap_addrxe_drm_pagemap_device_map(structdrm_pagemap*dpagemap,structdevice*dev,structpage*page,unsignedintorder,enumdma_data_directiondir){if(pgmap_devdev){// 同一设备: 将 device_private page 转为 VRAM 物理地址addrxe_vram_region_page_to_dpa(page_to_vr(page),page);protXE_INTERCONNECT_VRAM;}else{addrDMA_MAPPING_ERROR;// 不同设备: 不支持直接访问}returndrm_pagemap_addr_encode(addr,prot,order,dir);}这个device_map被drm_gpusvm_range_get_pages()调用当 HMM fault 返回的页面是 device_private 时框架通过此回调获取 GPU 可用的地址。八、设计模式总结8.1 策略模式Strategy Patterndrm_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 嵌入 BOstructxe_bo{structttm_buffer_objectttm;structdrm_pagemap_devmemdevmem_allocation;// 嵌入...};// 通过 container_of 回溯staticstructxe_bo*to_xe_bo(structdrm_pagemap_devmem*devmem){returncontainer_of(devmem,structxe_bo,devmem_allocation);}8.3 统一 dev_pagemap_ops框架提供的drm_pagemap_pagemap_ops_get()返回统一的 opsstaticconststructdev_pagemap_opsdrm_pagemap_pagemap_ops{.page_freedrm_pagemap_page_free,// 统一的页面释放.migrate_to_ramdrm_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 locktimeslice 机制框架级防抖避免驱动各自实现drm_pagemap_addr.proto字段统一表示系统 DMA 地址和设备本地地址GPU PTE 更新可以统一处理devmem_allocation可嵌入避免额外分配与 BO 生命周期自然绑定cpages npages要求简化实现全有或全无迁移避免部分迁移的复杂状态管理