101. migrate_pages - 迁移进程页面到指定 NUMA 节点 链接到标题
1. 函数介绍 链接到标题
migrate_pages
是一个 Linux 系统调用,用于将指定进程的内存页面迁移到指定的 NUMA(Non-Uniform Memory Access)节点。它允许管理员或应用程序主动控制内存页面的 NUMA 位置,以优化内存访问性能。
2. 函数原型 链接到标题
#define _GNU_SOURCE
#include <numa.h>
#include <numaif.h>
long migrate_pages(int pid, unsigned long maxnode,
const unsigned long *old_nodes,
const unsigned long *new_nodes);
注意:这不是标准 C 库函数,需要通过
syscall()
调用或使用 libnuma 库。
3. 功能 链接到标题
将指定进程(或当前进程)的内存页面从一组 NUMA 节点迁移到另一组 NUMA 节点。这对于 NUMA 感知的应用程序优化、负载均衡和性能调优非常有用。
4. 参数 链接到标题
int pid
: 目标进程的进程 ID0
: 表示当前进程- 正整数: 指定具体进程的 ID
unsigned long maxnode
: 节点掩码中的最大节点数const unsigned long *old_nodes
: 源节点掩码(要迁移的页面所在节点)const unsigned long *new_nodes
: 目标节点掩码(页面要迁移到的节点)
5. 返回值 链接到标题
- 成功时:返回成功迁移的页面数
- 失败时:返回 -1,并设置
errno
6. 常见 errno 错误码 链接到标题
EPERM
: 权限不足(迁移其他进程需要适当权限)ESRCH
: 指定的进程不存在EINVAL
: 参数无效(如节点掩码错误)ENOMEM
: 内存不足ENODEV
: 指定的 NUMA 节点不存在EACCES
: 访问被拒绝ENOSYS
: 系统不支持 NUMA 或此系统调用
7. 相似函数,或关联函数 链接到标题
set_mempolicy()
: 设置内存策略get_mempolicy()
: 获取内存策略mbind()
: 将内存区域绑定到特定节点move_pages()
: 移动指定页面到特定节点numa_*
函数族: NUMA 相关操作函数/sys/devices/system/node/
: NUMA 节点信息/proc/[pid]/numa_maps
: 进程 NUMA 内存映射
8. 示例代码 链接到标题
示例1:基本使用 - NUMA 节点信息获取 链接到标题
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#ifdef HAVE_NUMA
#include <numa.h>
#include <numaif.h>
#endif
void print_numa_info() {
printf("=== NUMA 系统信息 ===\n");
#ifdef HAVE_NUMA
if (numa_available() == -1) {
printf("系统不支持 NUMA 或 libnuma 不可用\n");
return;
}
int max_node = numa_max_node();
printf("最大 NUMA 节点: %d\n", max_node);
printf("在线 NUMA 节点: ");
struct bitmask *nodes = numa_get_mems_allowed();
for (int i = 0; i <= max_node; i++) {
if (numa_bitmask_isbitset(nodes, i)) {
printf("%d ", i);
}
}
printf("\n");
printf("CPU 到 NUMA 节点映射:\n");
for (int i = 0; i <= max_node; i++) {
if (numa_bitmask_isbitset(nodes, i)) {
struct bitmask *cpus = numa_allocate_cpumask();
numa_node_to_cpus(i, cpus);
printf(" 节点 %d: ", i);
for (int j = 0; j < numa_num_possible_cpus(); j++) {
if (numa_bitmask_isbitset(cpus, j)) {
printf("CPU%d ", j);
}
}
printf("\n");
numa_free_cpumask(cpus);
}
}
numa_free_nodemask(nodes);
#else
printf("编译时未启用 NUMA 支持\n");
printf("请安装 libnuma-dev 并重新编译\n");
#endif
}
int main() {
printf("=== migrate_pages 基本信息演示 ===\n");
print_numa_info();
// 显示当前进程的 NUMA 信息
printf("\n当前进程信息:\n");
printf(" PID: %d\n", getpid());
#ifdef HAVE_NUMA
if (numa_available() != -1) {
printf(" 当前 NUMA 节点: %d\n", numa_node_of_cpu(sched_getcpu()));
// 显示进程的 NUMA 内存分布
printf(" 进程 NUMA 内存映射:\n");
system("cat /proc/self/numa_maps 2>/dev/null | head -10 || echo '无法读取 numa_maps'");
}
#endif
return 0;
}
示例2:migrate_pages 系统调用封装 链接到标题
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>
#include <string.h>
#ifndef SYS_migrate_pages
# ifdef __x86_64__
# define SYS_migrate_pages 256
# else
# define SYS_migrate_pages 2048 // 需要根据具体架构调整
# endif
#endif
// migrate_pages 系统调用包装函数
long migrate_pages_syscall(int pid, unsigned long maxnode,
const unsigned long *old_nodes,
const unsigned long *new_nodes) {
return syscall(SYS_migrate_pages, pid, maxnode, old_nodes, new_nodes);
}
#ifdef HAVE_NUMA
#include <numa.h>
#include <numaif.h>
void demonstrate_node_migration() {
printf("=== NUMA 节点迁移演示 ===\n");
if (numa_available() == -1) {
printf("系统不支持 NUMA\n");
return;
}
int max_node = numa_max_node();
printf("系统最大 NUMA 节点: %d\n", max_node);
if (max_node < 1) {
printf("系统只有一个 NUMA 节点,无法演示迁移\n");
return;
}
// 分配节点掩码
struct bitmask *old_nodes = numa_allocate_nodemask();
struct bitmask *new_nodes = numa_allocate_nodemask();
// 设置迁移策略:从节点 0 迁移到节点 1
numa_bitmask_setbit(old_nodes, 0);
numa_bitmask_setbit(new_nodes, 1);
printf("准备将页面从节点 0 迁移到节点 1\n");
// 执行迁移
long result = migrate_pages_syscall(0, max_node + 1,
old_nodes->maskp,
new_nodes->maskp);
if (result == -1) {
printf("迁移失败: %s\n", strerror(errno));
switch (errno) {
case EPERM:
printf(" 原因: 权限不足\n");
break;
case ENOSYS:
printf(" 原因: 系统不支持 migrate_pages\n");
break;
case EINVAL:
printf(" 原因: 参数无效\n");
break;
default:
printf(" 原因: 其他错误\n");
break;
}
} else {
printf("成功迁移 %ld 个页面\n", result);
}
// 清理资源
numa_free_nodemask(old_nodes);
numa_free_nodemask(new_nodes);
}
#else
void demonstrate_node_migration() {
printf("=== migrate_pages 系统调用演示 ===\n");
printf("编译时未启用 NUMA 支持,仅演示系统调用接口\n");
// 尝试调用以检查系统支持
long result = migrate_pages_syscall(0, 2, NULL, NULL);
if (result == -1) {
if (errno == ENOSYS) {
printf("系统不支持 migrate_pages 系统调用\n");
} else {
printf("系统调用存在,但调用失败: %s\n", strerror(errno));
}
} else {
printf("系统调用成功返回: %ld\n", result);
}
}
#endif
int main() {
printf("=== migrate_pages 功能演示 ===\n");
demonstrate_node_migration();
return 0;
}
示例3:内存分配和迁移演示 链接到标题
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
#include <string.h>
#ifdef HAVE_NUMA
#include <numa.h>
#include <numaif.h>
void print_page_nodes(void *addr, size_t size) {
size_t page_size = getpagesize();
size_t num_pages = (size + page_size - 1) / page_size;
printf("内存区域页面 NUMA 节点分布:\n");
for (size_t i = 0; i < num_pages; i++) {
void *page_addr = (char*)addr + i * page_size;
int node;
if (get_mempolicy(&node, NULL, 0, page_addr, MPOL_F_ADDR) == 0) {
printf(" 页面 %zu (地址 %p): 节点 %d\n", i, page_addr, node);
}
}
}
void demonstrate_memory_migration() {
printf("=== 内存分配和迁移演示 ===\n");
if (numa_available() == -1) {
printf("系统不支持 NUMA\n");
return;
}
int max_node = numa_max_node();
if (max_node < 1) {
printf("系统只有一个 NUMA 节点\n");
return;
}
printf("系统 NUMA 节点数: %d\n", max_node + 1);
// 在节点 0 上分配内存
struct bitmask *node_mask = numa_allocate_nodemask();
numa_bitmask_setbit(node_mask, 0);
size_t alloc_size = 4 * getpagesize(); // 4 个页面
void *memory = numa_alloc_onnode(alloc_size, 0);
if (memory == NULL) {
printf("在节点 0 上分配内存失败\n");
numa_free_nodemask(node_mask);
return;
}
printf("✓ 在节点 0 分配了 %zu 字节内存 (%p)\n", alloc_size, memory);
// 初始化内存
memset(memory, 0x42, alloc_size);
printf("✓ 初始化内存内容\n");
// 显示初始页面分布
print_page_nodes(memory, alloc_size);
// 将内存迁移到节点 1
printf("\n准备将内存迁移到节点 1...\n");
struct bitmask *target_mask = numa_allocate_nodemask();
numa_bitmask_setbit(target_mask, 1);
// 使用 mbind 将内存绑定到目标节点
if (mbind(memory, alloc_size, MPOL_BIND, target_mask->maskp,
max_node + 1, MPOL_MF_MOVE_ALL) == 0) {
printf("✓ 成功将内存绑定到节点 1\n");
} else {
printf("✗ 绑定内存失败: %s\n", strerror(errno));
}
// 显示迁移后的页面分布
print_page_nodes(memory, alloc_size);
// 清理资源
numa_free(memory, alloc_size);
numa_free_nodemask(node_mask);
numa_free_nodemask(target_mask);
printf("✓ 内存释放完成\n");
}
#else
void demonstrate_memory_migration() {
printf("=== 内存迁移演示 ===\n");
printf("编译时未启用 NUMA 支持\n");
printf("请安装 libnuma-dev 并使用 -DHAVE_NUMA 重新编译\n");
}
#endif
int main() {
printf("=== NUMA 内存迁移演示 ===\n");
#ifdef HAVE_NUMA
if (numa_available() == -1) {
printf("系统不支持 NUMA 功能\n");
return 1;
}
printf("✓ NUMA 支持可用\n");
demonstrate_memory_migration();
#else
printf("✗ 编译时未启用 NUMA 支持\n");
demonstrate_memory_migration();
#endif
return 0;
}
示例4:NUMA 策略和迁移工具 链接到标题
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#ifdef HAVE_NUMA
#include <numa.h>
#include <numaif.h>
void show_process_numa_info(pid_t pid) {
printf("=== 进程 %d 的 NUMA 信息 ===\n", pid);
char numa_maps_path[64];
snprintf(numa_maps_path, sizeof(numa_maps_path), "/proc/%d/numa_maps", pid);
FILE *fp = fopen(numa_maps_path, "r");
if (fp) {
char line[512];
int line_count = 0;
printf("%-16s %-8s %-10s %s\n", "地址范围", "节点", "大小", "详细信息");
printf("%-16s %-8s %-10s %s\n", "--------", "----", "----", "--------");
while (fgets(line, sizeof(line), fp) && line_count < 20) {
// 解析 numa_maps 行
char address[32], policy[32];
int node, pages;
if (sscanf(line, "%31s %31s %d=%d", address, policy, &node, &pages) >= 4) {
printf("%-16s %-8d %-10d %s", address, node, pages,
strchr(line, 'N') ? strchr(line, 'N') : "");
if (strchr(line, '\n')) printf("\n");
}
line_count++;
}
if (line_count >= 20) {
printf("... (显示前20行)\n");
}
fclose(fp);
} else {
printf("无法读取 %s: %s\n", numa_maps_path, strerror(errno));
}
}
void interactive_numa_tool() {
int choice;
pid_t target_pid = getpid();
while (1) {
printf("\n=== NUMA 管理工具 ===\n");
printf("当前目标进程: %d\n", target_pid);
printf("1. 显示进程 NUMA 信息\n");
printf("2. 显示系统 NUMA 信息\n");
printf("3. 设置目标进程 PID\n");
printf("4. 显示当前进程内存策略\n");
printf("5. NUMA 节点负载信息\n");
printf("0. 退出\n");
printf("请选择操作: ");
if (scanf("%d", &choice) != 1) {
printf("输入无效\n");
while (getchar() != '\n'); // 清空输入缓冲区
continue;
}
switch (choice) {
case 1:
show_process_numa_info(target_pid);
break;
case 2: {
printf("系统 NUMA 信息:\n");
int max_node = numa_max_node();
printf(" 最大节点: %d\n", max_node);
struct bitmask *nodes = numa_get_mems_allowed();
printf(" 在线节点: ");
for (int i = 0; i <= max_node; i++) {
if (numa_bitmask_isbitset(nodes, i)) {
printf("%d ", i);
}
}
printf("\n");
numa_free_nodemask(nodes);
break;
}
case 3:
printf("输入目标进程 PID: ");
if (scanf("%d", &target_pid) != 1) {
printf("输入无效\n");
target_pid = getpid();
}
break;
case 4: {
printf("当前进程内存策略:\n");
int policy;
struct bitmask *nodemask = numa_allocate_nodemask();
if (get_mempolicy(&policy, nodemask->maskp, nodemask->size,
NULL, 0) == 0) {
printf(" 策略类型: ");
switch (policy) {
case MPOL_DEFAULT: printf("默认\n"); break;
case MPOL_BIND: printf("绑定\n"); break;
case MPOL_INTERLEAVE: printf("交错\n"); break;
case MPOL_PREFERRED: printf("首选\n"); break;
default: printf("未知 (%d)\n", policy); break;
}
printf(" 节点掩码: ");
for (int i = 0; i < numa_num_possible_nodes(); i++) {
if (numa_bitmask_isbitset(nodemask, i)) {
printf("%d ", i);
}
}
printf("\n");
} else {
printf(" 获取策略失败: %s\n", strerror(errno));
}
numa_free_nodemask(nodemask);
break;
}
case 5: {
printf("NUMA 节点负载信息:\n");
system("numastat 2>/dev/null | head -10 || echo 'numastat 不可用'");
break;
}
case 0:
printf("退出 NUMA 管理工具\n");
return;
default:
printf("无效选择\n");
break;
}
}
}
void demonstrate_numa_policies() {
printf("=== NUMA 内存策略演示 ===\n");
if (numa_available() == -1) {
printf("系统不支持 NUMA\n");
return;
}
int max_node = numa_max_node();
printf("系统 NUMA 节点数: %d\n", max_node + 1);
// 显示当前策略
int current_policy;
struct bitmask *current_mask = numa_allocate_nodemask();
if (get_mempolicy(¤t_policy, current_mask->maskp,
current_mask->size, NULL, 0) == 0) {
printf("当前内存策略: %d\n", current_policy);
}
numa_free_nodemask(current_mask);
// 演示不同策略
printf("可用的 NUMA 策略:\n");
printf(" MPOL_DEFAULT (%d): 默认策略\n", MPOL_DEFAULT);
printf(" MPOL_PREFERRED (%d): 首选节点策略\n", MPOL_PREFERRED);
printf(" MPOL_BIND (%d): 绑定策略\n", MPOL_BIND);
printf(" MPOL_INTERLEAVE (%d): 交错策略\n", MPOL_INTERLEAVE);
}
#else
void interactive_numa_tool() {
printf("=== NUMA 工具 ===\n");
printf("编译时未启用 NUMA 支持\n");
printf("功能受限\n");
}
void demonstrate_numa_policies() {
printf("=== NUMA 策略演示 ===\n");
printf("编译时未启用 NUMA 支持\n");
}
#endif
int main() {
printf("=== NUMA 策略和迁移工具 ===\n");
#ifdef HAVE_NUMA
if (numa_available() == -1) {
printf("警告: 系统支持 NUMA 但当前不可用\n");
} else {
printf("✓ NUMA 支持可用\n");
}
#else
printf("ℹ 编译时未启用 NUMA 支持\n");
#endif
// 演示 NUMA 策略
demonstrate_numa_policies();
// 启动交互式工具
char choice;
printf("\n是否启动交互式 NUMA 工具? (y/N): ");
if (scanf(" %c", &choice) == 1 && (choice == 'y' || choice == 'Y')) {
interactive_numa_tool();
}
return 0;
}
9. NUMA 节点掩码操作 链接到标题
#ifdef HAVE_NUMA
// NUMA 节点掩码操作示例
void demonstrate_nodemask_operations() {
printf("=== NUMA 节点掩码操作 ===\n");
if (numa_available() == -1) return;
// 分配节点掩码
struct bitmask *mask = numa_allocate_nodemask();
// 设置节点
numa_bitmask_setbit(mask, 0);
numa_bitmask_setbit(mask, 2);
printf("设置节点 0 和 2\n");
// 检查节点
printf("节点 0 状态: %s\n",
numa_bitmask_isbitset(mask, 0) ? "设置" : "未设置");
printf("节点 1 状态: %s\n",
numa_bitmask_isbitset(mask, 1) ? "设置" : "未设置");
printf("节点 2 状态: %s\n",
numa_bitmask_isbitset(mask, 2) ? "设置" : "未设置");
// 清除节点
numa_bitmask_clearbit(mask, 0);
printf("清除节点 0 后,节点 0 状态: %s\n",
numa_bitmask_isbitset(mask, 0) ? "设置" : "未设置");
// 显示所有设置的节点
printf("当前设置的节点: ");
for (int i = 0; i < mask->size; i++) {
if (numa_bitmask_isbitset(mask, i)) {
printf("%d ", i);
}
}
printf("\n");
// 清理
numa_free_nodemask(mask);
}
#endif
10. 实际应用场景 链接到标题
场景1:NUMA 感知的应用程序 链接到标题
#ifdef HAVE_NUMA
void numa_aware_application() {
// 获取当前线程的 CPU 和 NUMA 节点
int cpu = sched_getcpu();
int node = numa_node_of_cpu(cpu);
// 在本地 NUMA 节点分配内存
void *local_memory = numa_alloc_local(1024 * 1024); // 1MB
// 处理数据...
// 释放内存
numa_free(local_memory, 1024 * 1024);
}
#endif
场景2:数据库系统优化 链接到标题
#ifdef HAVE_NUMA
void database_numa_optimization() {
// 为不同数据库实例分配不同的 NUMA 节点
int num_nodes = numa_num_configured_nodes();
int instance_id = getpid() % num_nodes;
// 绑定到特定节点
struct bitmask *node_mask = numa_allocate_nodemask();
numa_bitmask_setbit(node_mask, instance_id);
set_mempolicy(MPOL_BIND, node_mask->maskp, node_mask->size);
// 分配内存
void *db_memory = numa_alloc_onnode(1024 * 1024 * 1024, instance_id); // 1GB
// 使用内存...
numa_free(db_memory, 1024 * 1024 * 1024);
numa_free_nodemask(node_mask);
}
#endif
场景3:高性能计算 链接到标题
#ifdef HAVE_NUMA
void hpc_numa_placement() {
// 根据线程 ID 分配 NUMA 节点
int thread_id = 0; // 应该从线程库获取
int node_count = numa_num_configured_nodes();
int target_node = thread_id % node_count;
// 在指定节点分配内存
struct bitmask *node_mask = numa_allocate_nodemask();
numa_bitmask_setbit(node_mask, target_node);
// 设置内存策略
set_mempolicy(MPOL_BIND, node_mask->maskp, node_mask->size);
// 分配工作内存
void *work_memory = numa_alloc_onnode(64 * 1024 * 1024, target_node); // 64MB
// 执行计算...
numa_free(work_memory, 64 * 1024 * 1024);
numa_free_nodemask(node_mask);
}
#endif
11. 注意事项 链接到标题
使用 migrate_pages
时需要注意:
- 内核版本: 需要支持 NUMA 的内核版本
- 权限要求: 迁移其他进程需要适当权限
- 性能影响: 页面迁移可能影响性能
- 内存锁定: 锁定的页面可能无法迁移
- 硬件支持: 需要真正的 NUMA 硬件
- 系统负载: 高负载下迁移可能失败
12. 系统配置检查 链接到标题
# 检查 NUMA 支持
numactl --hardware
# 显示 NUMA 策略
numactl --show
# 查看 NUMA 统计信息
numastat
# 检查内核 NUMA 支持
grep -i numa /boot/config-$(uname -r)
# 查看进程 NUMA 信息
cat /proc/self/numa_maps
# 检查 NUMA 节点内存使用
cat /sys/devices/system/node/node*/meminfo
13. 编译和链接 链接到标题
# 安装 NUMA 开发库
# Ubuntu/Debian: sudo apt-get install libnuma-dev
# CentOS/RHEL: sudo yum install numactl-devel
# 编译支持 NUMA 的程序
gcc -DHAVE_NUMA -lnuma program.c -o program
# 运行 NUMA 感知程序
numactl --cpunodebind=0 --membind=0 ./program
总结 链接到标题
migrate_pages
是 Linux 系统中用于 NUMA 内存管理的重要系统调用:
关键特性:
- NUMA 感知: 支持多节点内存迁移
- 进程控制: 可以迁移指定进程的页面
- 性能优化: 优化内存访问局部性
- 系统管理: 系统级内存负载均衡
主要应用:
- 高性能计算和科学计算
- 数据库系统优化
- 虚拟化和容器环境
- 系统管理和监控工具
使用要点:
- 检查系统 NUMA 支持
- 理解 NUMA 拓扑结构
- 合理使用内存策略
- 监控迁移效果和性能
正确使用 migrate_pages
可以显著提升 NUMA 系统的内存访问性能,是现代多核系统优化的重要工具。