Linux 内存迁移Page Migration是内核在进程虚拟地址完全不变的前提下将物理页面从一个地址透明搬运到另一个地址的核心机制。它是CMA、内存规整Compaction、NUMA 优化、内存热插拔的基础。内存迁移定义虚拟地址 → 物理页的映射被修改数据被拷贝到新物理页旧页释放。进程完全无感知。核心用途CMA 连续内存分配你最关心回收被占用的 CMA 页腾出大块连续物理内存。内存规整Compaction解决外部碎片满足高阶页order3、巨页HugeTLB分配。NUMA 节点迁移把页移到本地节点降低访存延迟。内存热插拔下线物理内存前迁移走所有页。硬件场景GPU / 设备内存、TEE 安全内存、保留区复用。核心原理与流程页面迁移不改变进程虚拟地址仅调整物理页归属关键路径在mm/migrate.c核心步骤如下隔离页面通过folio_isolate_lru()从 LRU 链表移除目标页递增引用计数refcount防止迁移中被释放。锁定与同步锁定旧页与新页确保写回writeback完成避免数据不一致。页表转换将旧页所有页表项转为迁移条目migration entry此时访问者会阻塞等待若 mapcount 非零则放弃迁移。内容迁移拷贝旧页内容到新页更新页表指向新页释放旧页。恢复与统计解锁页面更新页框计数器更新迁移成功 / 失败统计。不可迁移页面内核态不可移动页MIGRATE_UNMOVABLE如固定映射的内核内存共享页需CAP_SYS_NICE权限否则跳过正在写回writeback或有外部引用的页。页面可迁移性MIGRATE_TYPES内核按能否迁移把页分成 4 类mm/page_alloc.c类型含义能否迁移典型页面MIGRATE_MOVABLE完全可迁移✅ 是用户页、页缓存、CMA 临时占用页MIGRATE_RECLAIMABLE可回收非迁移❌ 否slab 页可回收释放MIGRATE_UNMOVABLE不可迁移❌ 绝对否内核常驻、内核栈、固定映射页、部分驱动页MIGRATE_CMACMA 专用可迁移✅ 是CMA 区域页空闲时借给 MOVABLEMIGRATE_ISOLATE隔离态迁移中—被隔离、暂不分配CMA 关键CMA 区域页默认是MIGRATE_CMA空闲时允许MIGRATE_MOVABLE借用当驱动要分配时内核把这些被借用页迁移出去恢复连续。完整迁移流程内核态核心文件mm/migrate.c入口migrate_pages()/alloc_contig_range()CMA 用1. 页面隔离Isolate把目标页从 LRU 摘下 →isolate_lru_page()页引用计数 1防止释放标记为MIGRATE_ISOLATE禁止新分配2. 锁定与检查锁页lock_page()确保无写回、无并发访问检查是否正在写回、是否有永久引用、是否不可迁移3. 分配新页按旧页属性order、migratetype、nid分配新物理页CMA 场景新页从非 CMA 区域 MOVABLE分配4. 页表冻结Migration Entry把所有 PTE 替换为特殊迁移条目swap entry-like访问会阻塞直到迁移完成 → 保证原子性5. 数据拷贝 元数据迁移migrate_page_copy()拷贝内容、PG 标志、引用计数migrate_page_move_mapping()更新 address_space、radix tree、匿名页映射6. 页表修复 解锁用新页 PFN 替换迁移条目刷新 TLB解锁、释放旧页引用旧页放回伙伴系统CMA 页回到 CMA 池7. 失败回滚迁移失败恢复旧页映射、解除隔离、放回 LRUCMA 内存迁移CMA 分配连续内存的唯一核心动作就是迁移。CMA 分配流程含迁移cma_alloc()申请 N 个连续页alloc_contig_range()核心start_isolate_page_range()把目标范围设为MIGRATE_ISOLATE__alloc_contig_migrate_range()迁移所有被占用页范围全部空闲后从 buddy 系统摘除迁移成功 → 返回连续物理页失败 → 重试 / 报错CMA 迁移特点只迁移 MOVABLE/CMA 页不可迁移页存在 → CMA 分配直接失败最常见坑迁移对驱动完全透明dma_alloc_coherent()自动走 CMA三种迁移模式enum migrate_mode// include/linux/migrate.h enum migrate_mode { MIGRATE_ASYNC, // 异步不阻塞、不回收、快速失败规整用 MIGRATE_SYNC_LIGHT,// 同步轻度阻塞不回收脏页 MIGRATE_SYNC, // 完全同步CMA/热插拔用阻塞、回收、必成功 };CMA 强制用 MIGRATE_SYNC必须确保迁移成功才能腾出连续内存内核 API驱动 / 模块可用1. 通用页面迁移// 迁移一页 int migrate_page(struct address_space *mapping, struct page *newpage, struct page *page, enum migrate_mode mode); // 迁移一页folio 新版接口 int migrate_folio(struct address_space *mapping, struct folio *dst, struct folio *src, enum migrate_mode mode); // 批量迁移页链表 int migrate_pages(struct list_head *source, new_folio_t new, free_folio_t free, unsigned long private, enum migrate_mode mode, int reason, unsigned int *ret_succeeded);2. CMA 专用分配连续内存// 从 CMA 池分配连续页会触发迁移 struct page *cma_alloc(struct cma *cma, size_t count, unsigned int align, bool no_warn); // 释放回 CMA void cma_release(struct cma *cma, struct page *pages, size_t count);3. 保留内存映射no-map 静态区// 静态 reserved-memoryno-map需手动映射 void *vaddr memremap(phys_addr, size, MEMREMAP_WC); memunmap(vaddr);用户态接口 工具1. 系统调用mbind()MF_MOVE/MF_MOVE_ALL迁移进程内存到指定 NUMA 节点migrate_pages()跨进程迁移move_pages()单页迁移2. 命令行工具# 迁移进程页到 NUMA 节点 migratepages PID from-node to-node # 查看迁移状态 cat /proc/vmstat | grep pgmigrate # pgmigrate_success 成功数 # pgmigrate_fail 失败数 # 查看 CMA cat /proc/meminfo | grep Cma cat /sys/kernel/debug/cma/cma_0/used # 查看页迁移类型debug cat /sys/kernel/debug/page_ownerReserved Memory / 静态 / 动态 / CMA 迁移对比类型能否迁移迁移触发用途静态 reservedno-map❌ 绝对不可从不硬件固定地址、安全区、固件动态 reservedno-map❌ 不可从不驱动私有、非共享内存CMAreusable✅完全可迁移cma_alloc/alloc_contig_range大块连续 DMA、GPU/VPU/ 网卡普通用户页✅ 可迁移规整、NUMA、CMA 回收应用内存、页缓存性能与风险控制性能开销迁移涉及页表修改、内存拷贝与 TLB 失效高并发场景可能引入抖动建议在业务低峰期执行避免影响延迟敏感业务。失败排查检查PGMIGRATE_FAIL计数器/proc/vmstat确认目标节点有足够空闲内存free -m验证权限CAP_SYS_NICE与页面可迁移性numa_maps。内存规整Compaction目的合并碎片生成高阶连续页如大页依赖迁移触发时机高阶分配失败、手动触发、后台kcompactd定期执行监控/proc/buddyinfo查看高阶页数量/proc/pagetypeinfo查看迁移类型分布。扩展场景设备内存迁移GPU SVM 等场景通过migrate_vma_*/migrate_device_*API将页在主机内存与设备私有内存间迁移内存热插拔依赖迁移实现节点上线 / 下线时的页重定位。总结Linux 内存迁移是 NUMA 优化、碎片整理的核心机制核心依赖migrate_pages系统调用与numactl工具。实践中需关注权限、内存可用性与性能开销结合numastat、numa_maps等工具定位问题。若需细粒度控制可使用move_pages单页接口若需整理碎片手动触发compact_memory即可。