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: 目标进程的进程 ID
    • 0: 表示当前进程
    • 正整数: 指定具体进程的 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(&current_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 时需要注意:

  1. 内核版本: 需要支持 NUMA 的内核版本
  2. 权限要求: 迁移其他进程需要适当权限
  3. 性能影响: 页面迁移可能影响性能
  4. 内存锁定: 锁定的页面可能无法迁移
  5. 硬件支持: 需要真正的 NUMA 硬件
  6. 系统负载: 高负载下迁移可能失败

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 内存管理的重要系统调用:

关键特性:

  1. NUMA 感知: 支持多节点内存迁移
  2. 进程控制: 可以迁移指定进程的页面
  3. 性能优化: 优化内存访问局部性
  4. 系统管理: 系统级内存负载均衡

主要应用:

  1. 高性能计算和科学计算
  2. 数据库系统优化
  3. 虚拟化和容器环境
  4. 系统管理和监控工具

使用要点:

  1. 检查系统 NUMA 支持
  2. 理解 NUMA 拓扑结构
  3. 合理使用内存策略
  4. 监控迁移效果和性能

正确使用 migrate_pages 可以显著提升 NUMA 系统的内存访问性能,是现代多核系统优化的重要工具。