Linux NUMA 平衡:numa_balancing 的任务与内存页迁移
简介
当前服务器硬件普遍采用 NUMA 非统一内存访问架构,多颗 CPU 各自挂载独立本地内存,节点间通过高速互联总线通信。CPU 访问自身节点内存时延低、带宽充足,跨节点远程内存访问延迟会成倍增加,频繁远程访存直接造成业务吞吐下降、响应抖动、CPU 利用率虚高。
早期工程方案依赖运维手动绑定 CPU、内存亲和性,适配繁琐且无法应对业务动态内存访问变化。Linux 内核自 3.8 版本正式引入numa_balancing自动平衡机制,无需业务代码改造,内核后台周期性检测进程内存访问热点,根据统计结果动态选择迁移任务进程或者内存物理页,让计算逻辑与数据内存尽可能收敛到同一 NUMA 节点,大幅削减远程访存开销。
该机制广泛落地于数据库集群、大数据计算、云计算虚拟化、分布式中间件、高性能科学计算等场景。对于内核研发、服务器调优工程师、虚拟化运维、后端性能开发人员而言,吃透 numa_balancing 检测逻辑、页迁移流程、调度联动规则、参数调优与异常排查,是解决 NUMA 架构性能瓶颈、定制内核调度策略、撰写技术报告与学术论文的核心能力,同时也能帮助从业者规避自动平衡带来的 CPU 抖动、迁移失败等工程问题。
一、核心概念与术语解析
1.1 NUMA 基础架构概念
SMP 对称多处理器架构下所有 CPU 共享统一内存池,访问延迟一致;NUMA 架构将整机硬件划分为多个独立节点 Node,每个 Node 包含 CPU 核心、本地内存、IO 控制器,节点间通过 QPI/UPI 总线互联。
- 本地内存:CPU 同节点挂载内存,访问速度最快
- 远程内存:其他节点内存,跨总线访问,延迟高、损耗大
- NUMA 节点 ID:内核为每个硬件节点分配唯一编号,用于区分资源归属
1.2 numa_balancing 自动平衡机制
内核内置的动态优化机制,核心目标计算靠近数据,整套动作分为访问检测、热点统计、资源迁移三个阶段,两种优化路径:
- 页迁移:将远程热点内存页迁移至任务当前运行节点
- 任务迁移:将频繁访问远端内存的线程调度至内存所在节点
1.3 NUMA 缺页提示异常
机制核心触发手段,内核周期性扫描进程虚拟地址空间,将部分内存页标记为访问陷阱。进程读取写入该页面时触发NUMA hint fault异常,内核借此记录访问 CPU 节点、访问频次、访问时序,作为后续迁移判定依据。
1.4 关键内核结构体与核心函数
- task_struct:进程描述符,维护 numa 相关统计、优选节点、扫描状态
- struct page:物理页描述符,记录内存归属节点、访问计数
- migrate_pages:内核通用页迁移接口,完成物理页拷贝、页表刷新、TLB 同步
- do_numa_page:NUMA 缺页异常处理入口函数,统计访问信息并判定迁移
1.5 核心调控参数
全部位于/proc/sys/kernel/目录,管控扫描时机、迁移阈值、平衡启停
- numa_balancing:总开关,0 关闭、1 开启自动平衡
- numa_balancing_scan_delay_ms:进程创建后延迟首次扫描时长
- numa_balancing_scan_period_min/max_ms:扫描间隔上下限
二、环境准备
2.1 软硬件环境规格
| 环境分类 | 版本与配置要求 |
|---|---|
| 硬件平台 | 多 NUMA 节点 x86_64 服务器,至少 2Node、8 核 CPU、16G 内存 |
| 操作系统 | Ubuntu 20.04/22.04、CentOS 7/9 |
| 内核版本 | Linux 5.4、5.15、6.1 主流稳定版,开启 NUMA 编译选项 |
| 编译工具 | gcc 9.0+、make、libncurses-dev |
| 调试分析工具 | numactl、perf、ftrace、vmstat、gdb、proc 文件系统工具 |
2.2 内核编译配置要求
编译内核必须开启对应功能宏,否则无法使用 NUMA 自动平衡
CONFIG_NUMA=y # 启用NUMA架构支持 CONFIG_NUMA_BALANCING=y # 开启numa自动平衡核心功能 CONFIG_MIGRATION=y # 内存页迁移基础能力 CONFIG_SCHED_DEBUG=y # 调度调试与统计输出 CONFIG_FTRACE=y # 函数跟踪,观测迁移流程2.3 环境校验与基础命令配置
- 查看整机 NUMA 拓扑结构,确认节点数量
numactl --hardware- 查看 numa_balancing 默认开关状态
sysctl kernel.numa_balancing- 一键启停自动平衡功能
# 临时开启 echo 1 > /proc/sys/kernel/numa_balancing # 临时关闭 echo 0 > /proc/sys/kernel/numa_balancing- 查看全套平衡调控参数
sysctl -a | grep numa_balancing2.4 源码文件路径定位
日常研读、调试、二次开发核心源码目录
kernel/sched/numa_balancing.c # 自动平衡主体逻辑、扫描、任务迁移 mm/migrate.c # 内存页迁移底层实现 mm/memory.c # NUMA缺页异常处理 include/linux/migrate.h # 页迁移结构体与函数声明三、应用场景
numa_balancing 自动平衡在企业级服务器业务中不可或缺。MySQL、PostgreSQL 等数据库服务运行于双 NUMA 节点服务器,数据频繁跨节点读写,内核自动识别热点数据页并迁移至数据库进程运行节点,显著降低查询延迟、提升事务处理效率。大数据 Spark、Hadoop 计算任务分片调度分散在不同节点,自动平衡跟随计算进程迁移中间数据,减少跨节点数据拷贝耗时。云计算 KVM 虚拟机场景下,虚拟机内存散乱分布在各 NUMA 节点,平衡机制整合内存资源,优化虚拟机 IO 与运算性能。同时在工业仿真、气象算力、AI 模型推理等高负载场景,该机制动态适配任务访问特征,无需人工干预亲和性配置,规避远程访存带来的性能损耗,保障业务运行稳定性与算力输出上限。
四、实际案例与步骤、代码实操
4.1 numa_balancing 整体运行流程源码拆解
4.1.1 进程 NUMA 扫描初始化逻辑
进程创建完成后,按照延迟参数等待触发首轮内存扫描,源码节选附带注释
// kernel/sched/numa_balancing.c /* * 初始化进程NUMA扫描状态 * p:待管控进程结构体 * delay:系统配置的首次扫描延迟时间 */ void task_numa_init(struct task_struct *p, unsigned long delay) { // 重置访问统计计数 p->numa_faults = 0; // 记录下次扫描触发时间 p->numa_scan_start = jiffies + msecs_to_jiffies(delay); // 初始化优选节点,默认进程当前运行节点 p->numa_preferred_nid = task_node(p); // 标记进程纳入NUMA平衡管控队列 p->flags |= PF_NUMA_BALANCING; }代码作用:新建进程完成基础初始化,设定扫描延时、统计变量,等待内核扫描线程遍历检测内存访问行为。
4.1.2 NUMA 缺页异常判定与统计核心函数
进程访问标记陷阱页面触发异常,内核记录访问节点与频次
// mm/memory.c static int do_numa_page(struct vm_fault *vmf) { struct page *page = vmf->page; struct task_struct *curr = current; int page_nid, curr_nid; // 获取物理页归属节点、当前运行CPU节点 page_nid = page_to_nid(page); curr_nid = task_node(curr); // 统计NUMA访问缺页次数 curr->numa_faults++; // 本地访问无需处理,直接返回 if (page_nid == curr_nid) return VM_FAULT_OK; // 远程访问,判定是否满足页迁移条件 if (numa_migrate_condition(curr, page, page_nid, curr_nid)) { // 执行错配页面迁移 migrate_misplaced_page(page, curr_nid, vmf->vma); } return VM_FAULT_OK; }代码解析:区分本地与远程内存访问,累计访问故障次数,达到阈值后调用迁移接口,将远端页面搬迁至进程本地节点。
4.1.3 底层页面迁移核心调用函数
调用通用迁移接口,完成页面隔离、拷贝、映射刷新整套流程
// mm/migrate.c /* * 页面迁移主入口 * from:待迁移页面链表头 * get_new_page:新节点内存申请回调 * mode:迁移模式,普通NUMA平衡模式 */ int migrate_pages(struct list_head *from, new_page_t get_new_page, free_page_t put_new_page, unsigned long private, enum migrate_mode mode, int reason) { int ret = 0; struct page *page, *tmp_page; // 遍历所有待迁移物理页 list_for_each_entry_safe(page, tmp_page, from, lru) { // 锁定页面,防止迁移期间被释放修改 if (!isolate_lru_page(page)) { ret++; continue; } // 分配目标节点新物理内存 struct page *new_page = get_new_page(private); if (!new_page) { putback_lru_page(page); ret++; continue; } // 拷贝页面数据内容 copy_highpage(new_page, page); // 修改页表映射,指向新物理页 migrate_page_move_mapping(page, new_page); // 释放旧节点页面资源 put_new_page(page, private); } return ret; }使用场景:numa_balancing 判定页面需要搬迁后,调用该函数批量迁移内存页,全程不中断业务进程运行。
4.1.4 任务节点迁移判定源码
页面迁移收益不足时,内核选择迁移进程至内存所在节点
// kernel/sched/numa_balancing.c void task_numa_migrate(struct task_struct *p) { int cur_nid = task_node(p); int best_nid = p->numa_preferred_nid; // 进程已在最优节点,无需迁移 if (cur_nid == best_nid) return; // 筛选目标节点空闲CPU核心 struct cpumask *target_cpus = numa_node_to_cpus(best_nid); // 触发调度迁移,将进程调度至目标节点CPU set_task_cpu_affinity(p, target_cpus); // 更新进程优选节点标记 p->numa_preferred_nid = cur_nid; }4.2 编写测试程序模拟跨 NUMA 内存访问
编写 C 程序主动申请大内存,模拟业务频繁远程访存,触发自动平衡机制
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/mman.h> #include <numaif.h> #define MEM_SIZE (1024 * 1024 * 512) // 申请512MB测试内存 int main(void) { char *mem_buf; int i; // 匿名大内存申请,无固定节点绑定 mem_buf = mmap(NULL, MEM_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); if (mem_buf == MAP_FAILED) { perror("mmap alloc failed"); return -1; } printf("Test memory alloc success, start cross NUMA access\n"); // 循环读写内存,产生大量NUMA访问缺页 while(1) { for(i = 0; i < MEM_SIZE; i += 4096) { mem_buf[i] = 'A'; } usleep(50000); } munmap(mem_buf, MEM_SIZE); return 0; }编译运行命令,直接复制执行
gcc numa_test.c -o numa_test -lnuma # 后台运行测试程序 ./numa_test & # 记录进程PID,后续观测统计 echo $!程序作用:无亲和性约束下频繁读写大内存,内存散乱分布 NUMA 节点,快速触发内核扫描、缺页统计与页面迁移动作。
4.3 命令行观测 numa_balancing 运行状态
4.3.1 查看 NUMA 迁移统计数据
cat /proc/vmstat | grep numa关键字段说明:numa_pages_migrate 成功迁移页数、numa_faults 总访问异常次数,直观判断平衡活跃度。
4.3.2 perf 抓取 NUMA 平衡内核热点函数
定位迁移、扫描相关 CPU 消耗函数
# 采样5秒内核与进程调用栈 perf record -g -p 测试程序PID sleep 5 # 解析采样报告 perf report可观测到migrate_pages、do_numa_page等核心函数调用频次。
4.3.4 ftrace 跟踪完整平衡调用链路
追踪扫描、异常、迁移全流程,验证源码逻辑与实际运行一致
# 挂载调试文件系统 mount -t debugfs none /sys/kernel/debug # 清空跟踪日志 echo > /sys/kernel/debug/tracing/trace # 筛选跟踪核心函数 echo migrate_pages >> /sys/kernel/debug/tracing/set_ftrace_filter echo do_numa_page >> /sys/kernel/debug/tracing/set_ftrace_filter echo task_numa_migrate >> /sys/kernel/debug/tracing/set_ftrace_filter # 开启跟踪 echo function > /sys/kernel/debug/tracing/current_tracer echo 1 > /sys/kernel/debug/tracing/tracing_on # 停止跟踪查看日志 echo 0 > /sys/kernel/debug/tracing/tracing_on cat /sys/kernel/debug/tracing/trace4.4 手动页面迁移系统调用示例
使用 move_pages 系统调用,手动指定内存页迁移节点,对比自动平衡效果
#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/mman.h> #include <numaif.h> int main(void) { char *p; long node_id; int ret; unsigned long pages; p = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); pages = (unsigned long)p; node_id = 1; // 将内存页手动迁移至node1节点 ret = move_pages(0, 1, &pages, &node_id, NULL, MPOL_MF_MOVE); if(ret < 0) { perror("move pages failed"); return -1; } printf("Manual page migrate to node 1 success\n"); munmap(p, 4096); return 0; }五、常见问题与解答
Q1 开启 numa_balancing 后业务出现短暂 CPU 抖动、延迟突增是什么原因?
解答:页面迁移会触发内存拷贝、页表修改、TLB 刷新,占用 CPU 资源。短时间大批量页面搬迁就会产生抖动,高延迟业务可适当拉长扫描间隔,降低迁移触发频率,或业务低峰期启用平衡机制。
Q2 自动平衡频繁迁移任务进程,反而造成负载不均衡如何处理?
解答:内核优先页迁移,访问跨度极大时才会迁移进程。出现频繁进程切换,可手动对核心业务做 CPU 亲和绑定,限制进程调度节点范围,规避无效任务迁移。
Q3 查看 vmstat 发现 numa_pages_migrate 数值为 0,平衡机制未生效?
解答:一是内核未开启 CONFIG_NUMA_BALANCING 编译选项;二是进程内存全部本地分配,无远程访问行为;三是平衡总开关被关闭。依次核对内核配置、sysctl 参数、内存访问模式即可排查。
Q4 页面迁移失败率偏高,报错页面被占用无法隔离怎么办?
解答:处于 IO 读写、锁定状态、共享引用过多的页面无法迁移。减少业务内存频繁锁操作,降低页面竞争引用,同时内核会跳过不可迁移页面,不影响整体业务运行。
Q5 虚拟化虚拟机内部是否建议开启 numa_balancing?
解答:宿主机层面完成 NUMA 平衡即可,虚拟机内部关闭自动平衡,双层平衡策略冲突会造成内存反复无效迁移,损耗性能。
六、实践建议与最佳实践
开关精细化管控:数据库、低延迟交易业务默认开启平衡;时延敏感、固定算力调度的业务关闭自动平衡,采用手动 numactl 绑定亲和性。
扫描参数调优技巧:业务波动大、访问模式多变场景,调小扫描间隔快速适配热点;稳态运行业务增大延迟与间隔,减少内核扫描带来的资源开销。
故障排查固定流程:性能下降先查看 vmstat NUMA 统计,再用 perf 定位迁移函数开销,结合 ftrace 追踪调用链路,区分是访问模式问题还是内核平衡策略异常。
资源部署优化原则:核心业务进程尽量独占 NUMA 节点资源,减少多业务混合挤占节点内存,从源头降低跨节点访存概率,减轻平衡机制负载。
内核定制开发规范:二次修改 numa_balancing 逻辑时,保留页面与任务双迁移兜底策略,增加迁移开销判定阈值,避免无收益无效迁移占用系统资源。
测试环境验证准则:新内核、新业务上线前,搭建多 NUMA 节点测试机复现访问场景,观测平衡效果与抖动情况,再部署至生产服务器。
七、总结与应用延伸
本文系统性拆解 Linux numa_balancing 自动平衡机制,从基础概念、环境搭建、拓扑查看、内核源码、测试程序、命令观测到问题排查、工程优化,完整还原任务扫描、缺页统计、页面迁移、进程调度迁移整套运行逻辑。该机制核心价值是以自动化方式解决 NUMA 架构远程内存访问性能缺陷,无需业务改造就能适配动态内存访问特征,平衡资源分布。
在实际工程落地中,numa_balancing 是服务器性能调优、虚拟化资源调度、大数据算力优化的核心抓手;在内核学习与学术研究层面,吃透机制能够深入理解内存管理、调度域、软硬件拓扑协同设计思想,可支撑内核论文撰写、调度策略定制、服务器架构方案设计。
建议读者基于文中源码与测试代码,在双节点 NUMA 服务器反复实操,修改扫描参数观察迁移频次变化,对比开关平衡前后业务时延、CPU 利用率差异,真正掌握自动平衡的优势与边界约束,将理论知识运用到数据库运维、云计算部署、内核开发真实项目当中。
