概述
Linux HMM(Heterogeneous Memory Management)横跨内核内存管理的多个子系统。本文档按由底层到顶层的顺序组织所需的基础知识,并给出推荐的学习计划。
第一层:虚拟内存基础
| 主题 | 关键概念 | 为什么 HMM 需要 |
|---|---|---|
| 页表结构 | PGD → P4D → PUD → PMD → PTE 五级页表 | hmm_range_fault() 的核心就是 walk_page_range() 遍历所有层级 |
| struct page / folio | 物理页帧元数据、引用计数、flags | HMM 输出的 PFN 最终指向 struct page,设备页面也有对应的 struct page |
| PFN (Page Frame Number) | 物理页帧号、PFN ↔ 页面转换 | HMM 的整个输出就是 PFN 数组 + 高位标志位 |
| VMA | vm_area_struct、vm_flags (VM_READ/WRITE/IO/PFNMAP) |
HMM 通过 VMA 判断虚拟地址范围的权限和类型 |
| 缺页处理 | handle_mm_fault()、FAULT_FLAG_REMOTE |
HMM 可以代替设备触发远程缺页(用 FAULT_FLAG_REMOTE) |
| mmap_lock | 读写信号量,保护 mm_struct | HMM 所有操作都需要持有 mmap_lock |
推荐资料
- Understanding the Linux Virtual Memory Manager (Mel Gorman)
Documentation/mm/page_tables.rstinclude/linux/mm_types.h
第二层:页表遍历框架
| 主题 | 关键概念 |
|---|---|
| walk_page_range() | 通用页表遍历器,HMM 用它遍历进程页表 |
| mm_walk_ops | 回调表:pud_entry、pmd_entry、pte_hole、hugetlb_entry |
| PGWALK_RDLOCK | 遍历时持有 mmap_lock 的模式 |
HMM 在 mm/hmm.c 中注册了自己的 mm_walk_ops,在每一级页表回调中提取 PFN 和权限标志。
推荐资料
mm/pagewalk.cinclude/linux/pagewalk.h
第三层:非驻留 PTE 编码(Swap/Migration/Device Entries)
| 编码类型 | 用途 | HMM 如何处理 |
|---|---|---|
| Swap entry | 页面被换出 | HMM 根据 fault 策略决定是否换入 |
| Migration entry | 页面正在迁移中 | HMM 等待迁移完成或返回 -EBUSY |
| Device private entry | 页面在设备私有内存中 | HMM 通过 dev_private_owner 判断是否是自己的页面 |
| Device exclusive entry | 页面被设备独占 | HMM 需要解码并正确报告 |
| UFFD WP marker | Userfaultfd 写保护标记 | HMM 需要避免与 UFFD 冲突 |
这是 HMM 中最容易混淆的部分------非驻留 PTE 有多种编码,hmm_range_fault() 必须正确解码每一种。
推荐资料
include/linux/swapops.hmm/memory.c中的do_swap_page()
第四层:MMU Notifier 子系统
| 主题 | 关键概念 |
|---|---|
| mmu_interval_notifier | HMM 实际使用的接口,基于区间树的页表变化通知 |
| invalidate 回调 | CPU 页表变化时通知设备驱动 |
| 序列号协议 | mmu_interval_read_begin() → 操作 → mmu_interval_read_retry() |
| MMU_NOTIFY_ 事件* | UNMAP、MIGRATE、EXCLUSIVE 等事件类型 |
| 阻塞性判定 | mmu_notifier_range_blockable() 决定回调中能否睡眠 |
这是 HMM 保持设备与 CPU 页表一致性的核心机制。理解序列号协议是掌握 HMM 的关键。
关键协议流程
seq = mmu_interval_read_begin()
mmap_read_lock()
hmm_range_fault() ← 获取 PFN 快照
mmap_read_unlock()
driver_lock()
if (mmu_interval_read_retry(seq))
goto retry ← 快照期间有失效,必须重试
update_device_page_table() ← 安全地更新设备页表
driver_unlock()
推荐资料
include/linux/mmu_notifier.hDocumentation/mm/hmm.rst"Address Space Mirroring" 章节
第五层:ZONE_DEVICE 与 dev_pagemap
| 主题 | 关键概念 |
|---|---|
| ZONE_DEVICE | 特殊内存 zone,设备内存有对应的 struct page |
| memory_type | DEVICE_PRIVATE(不可 CPU 访问)vs DEVICE_COHERENT(可 CPU 访问) |
| dev_pagemap | ZONE_DEVICE 区域的元数据:类型、范围、ops |
| dev_pagemap_ops | migrate_to_ram()(CPU fault 回调)、folio_free()(释放回调) |
| memremap_pages() | 热插拔 ZONE_DEVICE 内存 |
| zone_device_data | struct page 中的 void 指针,驱动自定义元数据 |
ZONE_DEVICE 是 HMM 设备内存迁移的基础------没有 struct page 就无法使用内核的 migrate 框架。
五种 memory_type
| 类型 | 说明 |
|---|---|
MEMORY_DEVICE_PRIVATE |
不可 CPU 访问(如 GPU VRAM),CPU 访问触发 fault + 迁移 |
MEMORY_DEVICE_COHERENT |
CPU 可访问的一致性设备内存(如 CXL/CAPI) |
MEMORY_DEVICE_FS_DAX |
持久化内存(filesystem DAX) |
MEMORY_DEVICE_GENERIC |
通用 DAX 设备 |
MEMORY_DEVICE_PCI_P2PDMA |
PCI BAR 内存(P2P DMA) |
推荐资料
include/linux/memremap.hmm/memremap.c
第六层:页面迁移框架
| 主题 | 关键概念 |
|---|---|
| migrate_vma 三阶段 | setup() → pages() → finalize() |
| migrate_vma_setup() | 遍历页表、收集源页面、解除映射、安装迁移 PTE |
| migrate_vma_pages() | 提交迁移,转移页面元数据 |
| migrate_vma_finalize() | 用最终 PTE 替换迁移 PTE,解锁页面 |
| MIGRATE_PFN_ 标志* | VALID、MIGRATE、WRITE、COMPOUND |
| migrate_device_*() | 不需要 VMA/mmap_lock 的设备级迁移(用于 evict) |
迁移过程中的锁
| 锁 | 用途 |
|---|---|
| mmap_lock (read) | 贯穿整个迁移过程 |
| PTE spinlock | 修改页表条目 |
| folio_lock | 锁定被迁移的页面 |
| mmu_notifier | 在收集阶段发出失效通知 |
推荐资料
mm/migrate_device.cinclude/linux/migrate.h
第七层:HMM 核心
在掌握前六层后,HMM 的代码量其实很小(~700 行),核心就是:
| 组件 | 文件 | 功能 |
|---|---|---|
| hmm_range_fault() | mm/hmm.c |
页表遍历 + PFN 输出 |
| hmm_pfn_flags | include/linux/hmm.h |
PFN 编码规范 |
| HMM DMA helpers | mm/hmm.c |
DMA 地址映射辅助函数 |
推荐资料
mm/hmm.c(逐行阅读)include/linux/hmm.hDocumentation/mm/hmm.rst
第八层:辅助/进阶主题
| 主题 | 与 HMM 的关系 |
|---|---|
| Reverse Mapping (rmap) | 迁移时需要找到所有映射了该页面的进程 |
| THP (透明大页) | HMM 需要处理 PMD/PUD 级别的大页映射 |
| HugeTLB | 独立的大页子系统,有自己的锁和遍历方式 |
| Memory cgroup | 设备页面也需要记账到 cgroup |
| LRU / 页面回收 | 迁移过程涉及 LRU 隔离和回插 |
| DMA Mapping | 设备需要将 PFN 转换为总线地址 |
| PCI P2PDMA | 设备间直接 DMA(HMM 支持 P2PDMA 标志) |
| NUMA / 内存热插拔 | ZONE_DEVICE 通过热插拔机制注册 |
| make_device_exclusive() | 页面独占访问 API |
| Userfaultfd (UFFD) | HMM 必须处理 UFFD 写保护标记,避免冲突 |
| TLB Flushing | 页表修改后需要刷新 TLB |
推荐学习计划
第 1-2 周:VM 基础 + 页表遍历
- 阅读
mm/memory.c(do_page_fault、handle_pte_fault) - 阅读
mm/pagewalk.c - 练习:用
/proc/pid/pagemap理解页表映射
第 3 周:非驻留 PTE + MMU Notifier
- 阅读
include/linux/swapops.h(各种 entry 编码) - 阅读
include/linux/mmu_notifier.h - 阅读
Documentation/mm/hmm.rst
第 4 周:ZONE_DEVICE + 迁移
- 阅读
include/linux/memremap.h+mm/memremap.c - 阅读
mm/migrate_device.c - 分析
lib/test_hmm.c(HMM 使用的最佳实践参考)
第 5 周:HMM 核心
- 逐行阅读
mm/hmm.c(仅约 700 行) - 阅读
include/linux/hmm.h - 编译运行
tools/testing/selftests/mm/hmm-tests.c
第 6 周+:实际驱动
- 阅读
drivers/gpu/drm/nouveau/nouveau_svm.c(相对简单的 HMM 使用示例) - 阅读
drivers/gpu/drm/drm_pagemap.c(现代的 HMM 封装框架) - 阅读
drivers/gpu/drm/xe/中的 SVM 代码(生产级实现)
关键源码文件索引
| 文件 | 内容 |
|---|---|
include/linux/hmm.h |
HMM 公共 API 和数据结构 |
mm/hmm.c |
HMM 核心实现 |
Documentation/mm/hmm.rst |
官方文档 |
include/linux/mmu_notifier.h |
MMU Notifier 接口 |
include/linux/memremap.h |
dev_pagemap / ZONE_DEVICE |
mm/memremap.c |
ZONE_DEVICE 实现 |
include/linux/migrate.h |
页面迁移 API |
mm/migrate_device.c |
设备迁移实现 |
include/linux/swapops.h |
Swap/Migration/Device entry 编码 |
mm/pagewalk.c |
页表遍历框架 |
lib/test_hmm.c |
HMM 测试内核模块 |
lib/test_hmm_uapi.h |
测试模块用户态接口 |
tools/testing/selftests/mm/hmm-tests.c |
HMM 用户态自测 |