一个真实的困境

假设你是一个 GPU 计算框架的开发者。用户写了这样一段代码:

float *data = malloc(1GB);
// ... 填充数据 ...
gpu_kernel<<<grid, block>>>(data);  // 希望 GPU 直接访问 data

在传统编程模型下,这不可能工作——GPU 有自己的显存(VRAM),CPU 的 malloc 返回的指针对 GPU 毫无意义。程序员必须手动管理数据搬移:

//h:host, d:device. 就是我们常说的 h2d,d2h
float *h_data = malloc(1GB);           // CPU 内存
float *d_data = gpu_malloc(1GB);       // GPU 显存
memcpy_to_gpu(d_data, h_data, 1GB);   // 显式拷贝
gpu_kernel<<<grid, block>>>(d_data);   // 用 GPU 指针
memcpy_from_gpu(h_data, d_data, 1GB); // 拷贝回来

这套"显式拷贝"模型有几个致命问题:

  1. 编程复杂度高 — 程序员必须手动管理两套指针和数据一致性
  2. 无法处理指针追踪 — 如果数据结构包含指针(链表、树),拷贝后指针全部失效
  3. 过度拷贝 — 无法知道 GPU 实际会访问哪些页面,只能全量拷贝
  4. 与系统接口不兼容fork()mmap()、信号处理等都可能修改地址空间,驱动无从得知

理想状态是:CPU 和 GPU 共享同一个虚拟地址空间,指针在两边通用,数据按需自动迁移

这就是 HMM 要解决的问题。


什么是 HMM

HMM(Heterogeneous Memory Management) 是 Linux 内核内存管理子系统的一组扩展,它让设备(GPU、FPGA、SmartNIC 等)能够:

  1. 镜像进程页表 — 设备维护一份与 CPU 一致的地址映射,进程用同一个虚拟地址在 CPU 和设备间通信
  2. 感知页表变化 — CPU 侧的 munmapmremap、COW 等操作会自动通知设备更新映射
  3. 双向迁移页面 — 页面可以在 CPU RAM 和设备内存之间按需迁移,对应用透明
  4. 让设备内存参与内核框架 — 设备内存拥有 struct page,可以被内核的迁移、回收等框架管理

HMM 不是一个独立的子系统,而是对现有 MM 机制的一系列精准扩展。它的代码量很小(核心仅 ~700 行),但它依赖的基础设施横跨整个 MM。


为什么必须从 MM 理解 HMM

很多开发者试图直接阅读 mm/hmm.c,然后迅速迷失——因为 HMM 的每一行代码都在调用 MM 的底层接口:

HMM 做的事 依赖的 MM 基础设施
遍历进程页表获取物理地址 五级页表结构、walk_page_range() 框架
解码"页面在设备内存中" 非驻留 PTE 编码(device private entry)
保持设备映射与 CPU 一致 MMU Notifier 序列号协议
迁移页面到设备内存 migrate_vma*() 三阶段迁移框架
让设备内存有 struct page ZONE_DEVICE、dev_pagemap
代替设备触发缺页 handle_mm_fault() + FAULT_FLAG_REMOTE

如果你不理解这些基础设施,HMM 的代码就是一堆无法解读的函数调用。 反过来,如果你沿着 MM 的进化脉络学习,HMM 的每个设计决策都变得顺理成章。


MM 的进化脉络

Linux MM 并非一开始就具备管理设备内存的能力。它是随着硬件需求的变化,一步步进化而来的:

在这里插入图片描述

注意每一步进化都是在前一步的基础上扩展,而非推倒重来:

  • mmu_notifier 最初是为 KVM 设计的,HMM 直接复用它来通知设备
  • migrate_pages() 最初是为 NUMA 均衡设计的,HMM 扩展出 migrate_vma*() 支持设备迁移
  • swap entry 编码最初只有 swap 和 migration 两种,HMM 新增了 device private/exclusive entry

HMM 的设计哲学就是"复用而非重造"。 这也是为什么理解 MM 基础是掌握 HMM 的必经之路。


硬件背景:谁在用 HMM

GPU(主要消费者)

厂商 驱动 HMM 使用方式
AMD amdgpu / KFD hmm_range_fault() + migrate_vma*() 实现 SVM(ROCm)
Intel Xe 通过 drm_gpusvm 框架使用 HMM
NVIDIA Nouveau(开源) nouveau_svm 使用 HMM 做 SVM

CXL 设备

CXL(Compute Express Link)设备提供 CPU 可直接访问的扩展内存。内核用 DEVICE_COHERENT 类型的 ZONE_DEVICE 管理,未来可能成为 HMM 最大的应用场景。

其他

  • FPGA — 可通过 HMM 共享进程地址空间
  • SmartNIC / DPU — RDMA + 设备内存管理
  • 持久化内存(PMEM) — 虽然不用 HMM,但共享 ZONE_DEVICE 基础设施

本专栏的学习路径

我们把 HMM 的知识体系分为 8 层,沿进化脉络从底向上:

在这里插入图片描述

每一层我们都会:

  1. 讲清经典 MM 是怎么做的 — 建立基础心智模型
  2. 指出"不够"在哪里 — 面对设备内存时的局限
  3. 展示如何扩展 — 内核社区的解决方案

这样当你最终读到 mm/hmm.c 时,每一行代码都不再陌生。


前置知识

本专栏假设你具备:

  • C 语言基础 — 能读懂内核代码(指针、位操作、宏)
  • 操作系统概念 — 虚拟内存、页表、中断等基本概念
  • 基本的内核阅读能力 — 知道如何浏览内核源码树

不需要你已经精通 MM 或 GPU 驱动——这些正是本专栏要教的。


关键源码版本

本专栏基于 Linux 6.x 内核源码。HMM 相关代码在近几年持续演进,核心文件包括:

文件 内容
mm/hmm.c HMM 核心实现(~700 行)
include/linux/hmm.h HMM 公共 API
mm/migrate_device.c 设备迁移框架
mm/memremap.c ZONE_DEVICE 实现
lib/test_hmm.c HMM 测试模块(最佳学习参考)

下篇预告

第 1 篇:虚拟地址空间与页表——每个进程的私有世界

我们将从 MM 最基础的概念开始:进程如何拥有自己的虚拟地址空间?页表如何将虚拟地址翻译为物理地址?五级页表的结构是什么样的?

这些看似"老生常谈"的基础,恰恰是 HMM hmm_range_fault() 遍历页表时的核心路径。打好这个基础,后面的一切才能事半功倍。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐