ZONE_DEVICE 介绍

原始问题:ZONE_DEVICE 是什么,有什么用 · 2026-08-05

ZONE_DEVICE 是什么,有什么用

📋 问题定位

🎯 原理说明

1. 它是什么:一个”只挂名、不参与分配”的 zone

在内核的 zone 体系中,ZONE_DEVICE 排在最末位(include/linux/mmzone.h:957):

enum zone_type {
    ZONE_DMA,
    ZONE_DMA32,
    ZONE_NORMAL,
    ZONE_MOVABLE,
#ifdef CONFIG_ZONE_DEVICE
    ZONE_DEVICE,
#endif
    __MAX_NR_ZONES
};

但它和其他 zone 有本质区别:ZONE_DEVICE 中的页面永远不进入 buddy 分配器、不出现在 LRU 链表中。用 Dan Williams 在 2017 LSFMM 的话说,它是”内存热插拔机制的上半部分”——它完成 struct page 的建立和映射,但不把页面”上线”给内核做普通分配。所以设备内存无法被 alloc_pages() 拿到,普通内存也无法被迁移进去(除了显式的设备迁移路径)。

它的存在意义只有一个:给非 CPU 侧内存(设备内存)分配 struct page,使它们能参与内核的统一内存管理基础设施(DMA 映射、迁移、页表映射、引用计数等)。

2. 五种设备内存类型(include/linux/memremap.h:68)

类型 CPU 可访问性 典型硬件 关键约束
MEMORY_DEVICE_PRIVATE 不可访问(非 cache-coherent) GPU 显存(Nouveau/AMDGPU) 必须提供 migrate_to_ram、folio_free 回调;CPU 访问触发 fault 迁回
MEMORY_DEVICE_COHERENT 可访问且 cache-coherent CXL 内存、CAPI 允许进程页迁入;禁止 pin(必须可逐出)
MEMORY_DEVICE_FS_DAX 可访问 持久内存上的 DAX 文件系统 页被 pin 后需 wakeup 协调 truncate/hole punch
MEMORY_DEVICE_GENERIC 可访问 DAX 字符设备暴露的内存 类似 System RAM 语义
MEMORY_DEVICE_PCI_P2PDMA 可访问(non-cached) PCIe BAR 内存 专用于设备间 DMA,pgprot_noncached

3. 注册流程:memremap_pages()(mm/memremap.c:266)

设备驱动通过 devm_memremap_pages() 把一段设备物理地址范围注册为 ZONE_DEVICE:

dev_pagemap 描述(type/ops/owner/ranges)
        │
        ▼
memremap_pages(pgmap, nid)
   ├─ 按 type 校验(PRIVATE 必须有 migrate_to_ram + folio_free + owner)
   ├─ 初始化 percpu_ref(引用计数,防止驱动卸载时页仍被使用)
   └─ pagemap_range() 逐个 range:
        ├─ 建立 vmemmap(struct page 数组,可用 altmap 放在设备内存自身)
        └─ 将 section 标记为 SECTION_TAINT_ZONE_DEVICE

struct dev_pagemap(include/linux/memremap.h:133)是这套机制的元数据结构:

struct dev_pagemap {
    struct vmem_altmap altmap;      /* vmemmap 元数据可下沉到设备内存 */
    struct percpu_ref ref;          /* 引用计数,防止 use-after-free */
    enum memory_type type;          /* 上表五种类型 */
    unsigned long vmemmap_shift;    /* 大 folio 化的 vmemmap(>0 表示 compound pages) */
    const struct dev_pagemap_ops *ops;
    void *owner;                    /* 驱动标识,防止跨驱动误访问 */
    ...
};

4. 核心工作流:以 GPU 私有内存为例(HMM/migrate_vma)

CPU 视角下,设备私有内存相当于”换出到显存的页”:

进程访问 GPU 页 → CPU page fault
        │
        ▼
do_handle_mm_fault → migrate_to_ram 回调(驱动实现)
        │  把设备页数据迁回系统 RAM
        ▼
migrate_vma_setup()  (mm/migrate_device.c:729)
   ├─ migrate_vma_collect()  收集 src/dst PFN 数组
   ├─ migrate_vma_unmap()    锁页 + 解除映射,检查是否被 pin
   ├─ 驱动分配目的内存并拷贝
   ├─ migrate_vma_pages()    迁移 struct page 状态
   └─ migrate_vma_finalize() 更新 CPU 页表指向新页

这正是 Popple 在 2025 LSFMM 提到的痛点:migrate_vma 以 PFN 数组为粒度工作,页面变大后(huge page)接口变得笨重,他希望支持迁移时的大页拆分/合并。

💡 适用场景(结合你提供的 LWN 素材)

场景 类型 现状/进展
GPU 异构计算(CUDA/OpenCL 统一地址空间) PRIVATE HMM 自 4.14 合入,Nouveau/AMDGPU 使用
CXL 内存池化/内存扩展 COHERENT 内核热插拔 + devm_memremap_pages,可迁移但不可 pin
持久内存 DAX FS_DAX / GENERIC struct page 引用计数 off-by-one 问题已在 6.15 修复(32 个 patch)
NVMe/GPU 间 P2P DMA PCI_P2PDMA 避免数据绕经 CPU 内存
AI 训练数据设备侧本地化 PRIVATE(file-backed 设想中) Popple 提议让 device-private 页进入 page cache;社区共识:只支持只读共享,共享可写映射被 Wilcox/Bacik 明确反对(”terrible programming model”)

与你 OLK 工作的强相关点:

💡 收益效果

💡 应用前景

参考来源

⚠️ 免责声明

本站内容均由 AI 基于公开知识辅助生成,仅供学习参考,请勿直接引用作为依据。作者不对信息的准确性、完整性及适用性作保证,亦不对因使用本站内容产生的任何损失承担责任。