NUMA 与远端访存
AMD EPYC 9004(Genoa,Zen 4)采用 chiplet 设计:多个 Core Compute Die(CCD)通过 Infinity Fabric 接到中央的 I/O Die(IOD),IOD 上集成 12 个内存控制器(24 通道 DDR5)。CCD 到不同内存控制器的物理距离不一致,访存延迟因此不均匀 — 这是 NUMA(Non-Uniform Memory Access)的物理来源。
调度器可以跨节点迁移 task,但 task 已经分配的内存页面不会跟着走。比如 task 从节点 A 调度到节点 B 运行,内存留在 A,B 上的每次访问都是远端。
内核无法预知哪些页面会被频繁访问,NUMA balancing 在运行时观察 task 实际访问的页面,再把它们和 task 放到同一节点。
Sample-and-Migrate
整体是个采样 → 决策 → 迁移的循环:
内核把页面的 PTE 权限改成PROT_NONE,task 访问这个页面时触发 page fault,fault handler 把 task、CPU、被访问页面记入 per-task 统计;基于这些统计周期性算出最优节点并触发迁移。整套机制纯软件实现,不依赖硬件 PMU。
扫描会改大量 PTE 和刷 TLB;迁移还要跨核 IPI。每一处都有节流机制,后面会展开。
Sample-and-Migrate 的实现
NUMA balancing 循环运行三个阶段:采样、决策、迁移。每个阶段由不同子系统负责,靠 task 和 mm_struct 里的字段串起来。## 采样
Scan。task_tick_numa()在 scheduler tick 里触发,通过task_work_add()把task_numa_work()延迟到任务自身上下文执行。这个函数遍历 VMA 链表,对满足条件的 VMA 调change_prot_numa()把范围内的 PTE 改成PROT_NONE。MMU 没法完成翻译,下次访问触发 page fault。
Fault。访问被标记页面触发 hinting fault —— 这是预期的访问采样而非错误。do_numa_page()处理时恢复 PTE 权限、记录当前 CPU 所在节点、读取页面所在节点、判断 local/remote,再把信息交给task_numa_fault()。
Account。task_numa_fault()把每次 fault 信息累积到task->numa_faults,按节点维度统计;历史数据指数衰减,让统计反映近期的访存模式,供决策阶段使用。
决策
task_numa_placement()定期评估,遍历各节点得分,挑出numa_preferred_nid。共享内存场景下,多个 task 对同一页产生 shared fault 时会被聚合到numa_group,用 group 级统计做决策,避免每个 task 独立决策把共享页拉来拉去。
迁移
Memory Tiering
配 PMEM 或 CXL 的平台同时有 DRAM(快)和 PMEM(慢)。NUMA_BALANCING_MEMORY_TIERING模式复用扫描框架做热页提升。PTE scanner 标记页面时记下扫描时间,后续 hinting fault 用 “fault 时间 − 扫描时间” 作为热度信号 — 间隔短说明刚扫完就被访问、是热页,提升到 DRAM。普通 NUMA balancing 问的是"哪个节点",tiering 问的是"哪个层级"。
控制接口
numactl/mbind绑定的页面,NUMA balancing 不扫描、不迁移。
参考资料
- Chips and Cheese,Evaluating Uniform Memory Access Mode on AMD’s Turin ft. Verda— https://chipsandcheese.com/p/evaluating-uniform-memory-access