当前位置: 首页 > news >正文

NUMA-03 页是怎么在 node 间搬家的:内核 NUMA 与页迁移机制

NUMA:一段内存到底在哪个 node:用户态 NUMA 编程接口在用户态调move_pages把页迁到某个 node,一行就返回了。但内核里到底发生了什么?“目标 node” 这个参数又是怎么一路传下去、最终决定新页落在哪的?这一章把这条链路走一遍。


1. 内核怎么描述 NUMA 拓扑

1.1 每个 node 一个pg_data_t

内核给每个 NUMA node 维护一个pg_data_t(typedef 自struct pglist_data),里面挂着这个 node 的所有物理页、空闲链表、回收水位线等。通过NODE_DATA(nid)就能拿到第nid个 node 的这个结构。可以把它理解为“每个内存孤岛的户口本”。

1.2 CPU 属于哪个 node:cpu_to_node()

用户态的numa_node_of_cpu(),在内核对应的是cpu_to_node(),声明在include/linux/topology.h

// include/linux/topology.h#ifndefcpu_to_nodestaticinlineintcpu_to_node(intcpu){returnper_cpu(numa_node,cpu);}#endif

每个 CPU 有一个 per-cpu 的numa_node变量,启动时由固件(ACPI SRAT/SLIT)填好。numa_node_id()则是“当前正在执行的 CPU 属于哪个 node”,first-touch 分配就是靠它决定新页落在哪。

1.3 node 之间有多远:node_distance()与那个 “10”

在前面文章中反复出现的 distance10,它的定义就在内核里include/linux/topology.h):

// include/linux/topology.h#defineLOCAL_DISTANCE10#defineREMOTE_DISTANCE20#ifndefnode_distance#definenode_distance(from,to)((from)==(to)?LOCAL_DISTANCE:REMOTE_DISTANCE)#endif

这就是为什么cat node0/distance永远从10起步——LOCAL_DISTANCE硬编码为 10。真实机器上,架构相关的node_distance()(x86 是__node_distance(),读固件 SLIT 表)会给出更细的值,比如同 socket 内12、跨 socket32

固件 ACPI 表
SRAT(谁属于谁)+ SLIT(两两距离)

内核启动解析

每 node 一个 pg_data_t

每 CPU 的 numa_node

距离矩阵
node_distance(a,b)

内核 API:NODE_DATA / cpu_to_node / node_distance

一句话:用户态在 sysfs 看到的那些 node 与 distance,本质是内核把固件表解析成pg_data_t+ 距离矩阵后暴露出来的。


2. 普通页迁移主流程:migrate_pages()

2.1 从 syscall 到核心函数

用户态move_pages(2)进内核后的调用链(均在mm/migrate.c):

SYSCALL_DEFINE6(move_pages, ...) // mm/migrate.c └─ do_pages_move() // 逐页处理,把要迁的页收集成 pagelist └─ migrate_pages(pagelist, alloc_migration_target, ...) // 迁移引擎 └─ alloc_migration_target() // 在“目标 node”上分配新页

migrate_pages()是内核统一的迁移引擎(页面规整、内存热插拔、NUMA balancing、move_pages都复用它)。它本身不决定“迁到哪”,而是把“怎么分配目标页”交给一个回调——这正是目标 node 进入的入口。

2.2 目标 node 是怎么“决定页落点”的

看回调alloc_migration_target()mm/migrate.c)的关键几行*:

// mm/migrate.c alloc_migration_target()structmigration_target_control*mtc=(void*)private;intnid=mtc->nid;if(nid==NUMA_NO_NODE)nid=folio_nid(src);// 没指定就落回“源页所在 node”// ... 之后用 nid 去对应 node 上分配新 folio

mtc->nid就是“目标 node”。它有值,新页就在那个 node 上分配;它是NUMA_NO_NODE,就退回源页所在 node。用户态传的nodes[i]=k,一路封装进mtc->nid,最终在这里把页落到 node k。

这条线索至关重要:要让“迁回 CPU 落到最近 node”,本质就是让某个nid/mtc->nid等于那个最近 node。

2.3 迁移的四步骨架

不论哪条路径,migrate_pages()内部都是同一套四步:

① 隔离
把源页从 LRU 摘下、锁定

② 分配目标页
在目标 node 上(alloc_migration_target)

③ 拷贝+重映射
复制内容、迁移页表项、更新引用

④ 收尾
释放源页 / 失败则回滚

关键点:迁移期间要冻结对该页的访问(通过锁和页表失效),拷贝完成后把指向源页的 PTE 改指向新页。对使用者透明——虚拟地址不变,物理页换了个 node。


3. 设备内存迁移:migrate_vma_*三段式

普通migrate_pages()处理的是“普通 CPU 页 ↔ 普通 CPU 页”。但 GPU 显存是以ZONE_DEVICE形式存在的“设备私有页”(下一章展开),CPU 不能直接访问,普通迁移引擎搬不动它。于是内核提供了一套专门的migrate_vma三段式(声明在include/linux/migrate.h,实现在mm/migrate_device.c)。

3.1 三个函数

// include/linux/migrate.hintmigrate_vma_setup(structmigrate_vma*args);// 实现在 mm/migrate_device.c:735voidmigrate_vma_pages(structmigrate_vma*migrate);voidmigrate_vma_finalize(structmigrate_vma*migrate);

调用顺序固定:setup → 驱动填 dst → pages → finalize

migrate_vma_setup()
收集源页、隔离、冻结映射
填 src[] 数组

驱动侧:为每个源页
分配目标页,填入 dst[]

migrate_vma_pages()
逐页拷贝内容、建立新映射

migrate_vma_finalize()
释放源页、解冻、收尾

“分配目标页、填dst[]”这一步在驱动手里——CPU→GPU 时驱动分配显存页,GPU→CPU 时驱动(或内核 helper)分配普通 CPU 页“迁回 CPU 该落哪个 node”正是在这一步、由分配目标页时选的 node 决定的。

3.2struct migrate_vma:迁移的“工单”

真实结构(include/linux/migrate.h):

// include/linux/migrate.hstructmigrate_vma{structvm_area_struct*vma;unsignedlong*dst;// 目标页 pfn 数组(驱动填)unsignedlong*src;// 源页 pfn 数组(setup 填)unsignedlongcpages;// 收集到的可迁移页数unsignedlongnpages;// 地址范围内的总页数unsignedlongstart,end;// 迁移的虚拟地址范围void*pgmap_owner;// 设备私有内存的 owner,用于识别“是不是我的页”unsignedlongflags;// 方向选择,见下structpage*fault_page;};

flagsenum migrate_vma_direction指定迁移方向(include/linux/migrate.h):

enummigrate_vma_direction{MIGRATE_VMA_SELECT_SYSTEM=1<<0,// 选普通系统内存页MIGRATE_VMA_SELECT_DEVICE_PRIVATE=1<<1,// 选设备私有页(GPU→CPU 用)MIGRATE_VMA_SELECT_DEVICE_COHERENT=1<<2,MIGRATE_VMA_SELECT_COMPOUND=1<<3,};

GPU 显存迁回系统内存时,setupMIGRATE_VMA_SELECT_DEVICE_PRIVATE把设备页挑出来,驱动再为它们分配普通页填进dst[]——dst[]时用哪个 node 分配,就是本系列要改对的那个点。


4. 内存策略在内核的落地:struct mempolicy

用户态的set_mempolicy/mbind,在内核对应struct mempolicymm/mempolicy.c+include/linux/mempolicy.h),它记录mode(BIND/PREFERRED/INTERLEAVE 等)和一个nodemask。分配路径(alloc_pages_*)会读当前生效的 policy,决定从哪个/哪些 node 拿页。

mbindMPOL_MF_MOVE时的迁移,同样走migrate_pages(),只是目标 node 由 policy 的nodemask给出(回调alloc_migration_target_by_mpol()mm/mempolicy.c)。所以“策略”和“迁移”在内核是解耦的:策略负责算出目标 node,迁移引擎负责把页搬过去。

set_mempolicy / mbind

struct mempolicy
(mode + nodemask)

move_pages(nodes=[k])

mtc->nid = k

migrate_pages() 引擎

在目标 node 分配新页
页落点 = 传入的 nid

把三层串起来看“目标 node”这个参数的旅程:

谁指定目标 node代码锚点
用户态move_pagesnodes[]/ mempolicy 的 nodemask第 02 章
内核迁移引擎mtc->nid(普通页)/ 驱动填的dst[](设备页)mm/migrate.cmm/migrate_device.c
分配落点nid == NUMA_NO_NODE ? folio_nid(src) : nidalloc_migration_target()

结论:页落到哪个 node,从头到尾由一个显式的 node id 决定;没人显式指定时,才退回“源页所在 node”这个默认。GPU 显存迁回 CPU 属于设备页路径(第 3 节三段式),落点由驱动填dst[]时选的 node 决定——这正是下一章(HMM/ZONE_DEVICE)和后续 KFD/DRM 分析时要深入的地方。


5. 本章小结

  • 内核把固件 ACPI 表解析成pg_data_t(每 node)+cpu_to_node()(每 CPU)+node_distance()(距离矩阵)。
  • distance 的10来自include/linux/topology.hLOCAL_DISTANCE,是硬编码基准。
  • 普通页迁移走migrate_pages()四步引擎;目标 node 经mtc->nid传入alloc_migration_target(),是决定页落点的关键一行。
  • 设备页(GPU 显存)迁移走migrate_vma_setup/pages/finalize三段式,落点由驱动填dst[]时选的 node 决定。
  • struct mempolicy负责“算目标 node”,migrate_pages负责“搬”,两者解耦。

🔗关联阅读

  • 设备页面迁移:migrate_vma 三阶段操作协议
http://www.jsqmd.com/news/1299179/

相关文章:

  • 2026年威海人防密闭过线盒济南总代理严选指南:哪个更值得信赖? - geo交流
  • Rust记忆管理系统优化:从OpenClaw到SkillLite的实践
  • 测速结果只能当作参考,别把单次测试当作网络最终定论
  • 现代C++核心特性解析:从C++11到C++20的高效编程指南
  • 利用Excel VBA与COM接口实现SIMPACK仿真自动化
  • BFS算法在游戏寻路中的应用:从原理到HUD-Asteroids实战
  • 2026年汨罗优质的木门批发厂家甄选指南 - geo交流
  • RePKG终极指南:3步掌握Wallpaper Engine资源提取的完整教程
  • 终极炉石传说增强指南:55项功能一键解锁游戏新体验
  • 大模型落地招投标:从OCR到RAG的工程实现全解析
  • NUMA-04 显存迁回内存:AMD/Intel 的 SVM 与Eviction的实现是否考虑了NUMA
  • C++代码优化实战:从算法到内存与编译器的全方位性能提升
  • 2026 年当下,合肥口碑好的蓝色铁皮围挡供货厂家哪个好,小区楼下突然围起的这玩意儿,居然藏着半年后房价的大秘密? - 行业推荐官【官方】
  • 2026年碳纤维管厂家/3K碳纤维卷管推荐榜:无人机配件与机器人手臂等高端应用的高强度精密之选 - 优企名品
  • 手动安装 OpenAI Codex Windows 桌面版
  • Godot脚本编辑器进阶:打造IDE级智能编码体验的插件配置指南
  • 猫抓浏览器扩展:5个步骤轻松获取网页视频资源的终极解决方案
  • 51单片机PWM控制舵机Proteus仿真:从定时器中断到波形验证
  • 半导体器件实战指南:从数据手册到PCB布局的硬件设计核心
  • 解决Python pandas中openpyxl依赖缺失问题
  • 西门子PLC远程调试实战:基于智能网关的S7协议穿透与安全连接
  • 终极内存优化指南:如何用Mem Reduct让老旧电脑重获新生!
  • Jdk17安装、环境配置详细教程【Windows】
  • g2o框架在SLAM位姿图优化中的原理与实践
  • 2026年玻璃纤维制品实力厂家:深圳、东莞玻纤管、玻纤棒、玻纤扁条高强度耐腐蚀耐高温源头工厂 - 优企名品
  • 空调集群等效储能建模与微电网经济调度MATLAB实现
  • C++实现施密特正交化:从数值稳定性到工业级应用
  • C语言中%运算符的全面解析:从取余运算到格式化字符串的深度应用
  • 益阳市防水补漏_2026湘北洞庭湖畔漏水维修流程教程与五大正规团队推荐 - 雨婺虹房屋维修
  • PaddleOCR GPU 推理显存不释放?这样改,显存稳稳回落不翻倍--亲测有效