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

numactl 设置 numa 内存分配规则

numactl: 
numactl命令的主要功能用来绑核和绑numa内存,比如:

运行如下命令,通过strace来系统调用:
 
strace numactl --membind=0,1 --cpunodebind=0,1   pwd
 
输出:
.....
设置进程的亲和性。
sched_setaffinity(0, 512, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, ...]) = 0
省略
 
设置内存的分配规则
set_mempolicy(MPOL_BIND, [0x0000000000000003, 000000000000000000, 000000000000000000, 000000000000000000], 257) = 0
....
上面对应了内核中具体的系统调用实现函数:

sched_setaffinity:sched_setaffinity   //kernel/sched/core.c

set_mempolicy:do_set_mempolicy   //mm/mempolicy.c

int sched_setaffinity(pid_t pid, size_t cpusetsize,
                             const cpu_set_t *mask);
cpu_set_t:                             
typedef struct
{
  __cpu_mask __bits[__CPU_SETSIZE / __NCPUBITS];
} cpu_set_t;
 
 
=>
typedef struct
{
  unsigned long int __bits[1024 / sizeof(unsigned long int )* 8]; //默认16个数组
} cpu_set_t;
 
 
内核:
typedef struct cpumask { DECLARE_BITMAP(bits, NR_CPUS); } cpumask_t;
long sched_setaffinity(pid_t pid, const struct cpumask *in_mask);
 
gdb:
(gdb) b do_taskset
Breakpoint 1 at 0x402154: file schedutils/taskset.c, line 118.
(gdb) set args -c 1,2,3 pwd
(gdb) b do_taskset
(gdb) p {cpu_set_t}set
$9 = {__bits = {14, 0 <repeats 15 times>}} //第一个long值为14: 二进制1110 。刚好是1,2,3 这几个cpu
 
strace打印:
sched_setaffinity(0, 128, [1, 2, 3])    = 0
 
 
 
内存绑定:
long set_mempolicy(int mode, const unsigned long *nodemask,
                          unsigned long maxnode);
 
内核:
typedef struct { DECLARE_BITMAP(bits, MAX_NUMNODES); } nodemask_t;
static long do_set_mempolicy(unsigned short mode, unsigned short flags,
                 nodemask_t *nodes)
                 
                 
//根据bits数量,确定多少long类型数组,内核long 8字节。向上取整。                 
#define DECLARE_BITMAP(name,bits) \
    unsigned long name[BITS_TO_LONGS(bits)]                 
numactl分配的内存策略,
1. 缺省(default):总是在本地节点分配(分配在当前进程运行的节点上);

2. 绑定(bind):强制分配到指定节点上;

3. 交叉(interleave):在所有节点或者指定的节点上交织分配;

4. 优先(preferred):在指定节点上分配,失败则在其他节点上分配。

numactl 设置内存策略的参数,互相冲突,只能选择一个
[  --interleave  nodes ] [ --preferred node ]  [ --membind nodes ]
内核也有对应的枚举类型,include/uapi/linux/mempolicy.h

enum {
    MPOL_DEFAULT,
    MPOL_PREFERRED,
    MPOL_BIND,
    MPOL_INTERLEAVE,
    MPOL_LOCAL,
    MPOL_MAX,    /* always last member of enum */
};
内核do_set_mempolicy函数: 
static long do_set_mempolicy(unsigned short mode, unsigned short flags,
                             nodemask_t *nodes)
{
        struct mempolicy *new, *old;
        NODEMASK_SCRATCH(scratch);
        int ret;
 
        if (!scratch)
                return -ENOMEM;
 
        new = mpol_new(mode, flags, nodes);
        if (IS_ERR(new)) {
                ret = PTR_ERR(new);
                goto out;
        }
 
        ret = mpol_set_nodemask(new, nodes, scratch);
        if (ret) {
                mpol_put(new);
                goto out;
        }
        task_lock(current);
        old = current->mempolicy;   //设置当面进程的mempolicy
        current->mempolicy = new;
        if (new && new->mode == MPOL_INTERLEAVE)
                current->il_prev = MAX_NUMNODES-1;  //interleave模型,记录上一次分配的node节点,在后面分配的时候会看到怎么用的
        task_unlock(current);
        mpol_put(old);
        ret = 0;
out:
        NODEMASK_SCRATCH_FREE(scratch);
        return ret;
}
分配内存的时候根据mempolciy来分配:
先来一张用户用户空间页错误异常:参考资料。

do_anonymous_page的分配物理页: 

page = __alloc_pages_nodemask(gfp, order, preferred_nid, nmask); //在nodemask节点分配页表
pol = get_vma_policy(vma, addr); //获取mempolicy,如果没有就是要进程的current->policy
 => alloc_pages_vma
  =>alloc_page_vma
  =>page = alloc_zeroed_user_highpage_movable(vma, vmf->address); //
 => do_anonymous_page
 => handle_pte_fault
 => __handle_mm_fault
 => handle_mm_fault
 => do_page_fault
 => do_translation_fault
 => do_mem_abort
 => el0_da
 => el0_sync_handler
 => el0_sync 

pte_alloc的调用流程: 

pte_alloc
    __pte_alloc
        pte_alloc_one
            __pte_alloc_one
                alloc_page(gfp);
                    alloc_pages
                        alloc_pages_current    //mm/mempolicy.c ,根据memopolicy规则,分为alloc_page_interleave和__alloc_pages_nodemask
                          alloc_page_interleave
                            __alloc_pages
                                __alloc_pages_nodemask  //最终也是__alloc_pages_nodemask
                          __alloc_pages_nodemask   //mm/alloc_page.c
 
                
 
 
struct page *alloc_pages_current(gfp_t gfp, unsigned order)
{
    struct mempolicy *pol = &default_policy;
    struct page *page;
 
    if (!in_interrupt() && !(gfp & __GFP_THISNODE))
        pol = get_task_policy(current);
 
    /*
     * No reference counting needed for current->mempolicy
     * nor system default_policy
     */
    if (pol->mode == MPOL_INTERLEAVE)
        page = alloc_page_interleave(gfp, order, interleave_nodes(pol));
    else
        page = __alloc_pages_nodemask(gfp, order,
                policy_node(gfp, pol, numa_node_id()),
                policy_nodemask(gfp, pol));
 
    return page;
}
EXPORT_SYMBOL(alloc_pages_current);
上面根据当前进程的mempolicy的mode来区分,如果是MPOL_INTERLEAVE,也就是在numactl使用--interleave参数。则调用alloc_page_interleave。第三参数intrleave_nodes(pol):

/* Do dynamic interleaving for a process */
static unsigned interleave_nodes(struct mempolicy *policy)
{
    unsigned next;
    struct task_struct *me = current;
 
    next = next_node_in(me->il_prev, policy->v.nodes); //选出下一个node
    if (next < MAX_NUMNODES)
        me->il_prev = next;  //设置当前使用的node。
    return next;
}
 
 
static struct page *alloc_page_interleave(gfp_t gfp, unsigned order,
                    unsigned nid)
{
    struct page *page;
 
    page = __alloc_pages(gfp, order, nid);
    /* skip NUMA_INTERLEAVE_HIT counter update if numa stats is disabled */
    if (!static_branch_likely(&vm_numa_stat_key))
        return page;
    if (page && page_to_nid(page) == nid) {
        preempt_disable();
        __inc_numa_state(page_zone(page), NUMA_INTERLEAVE_HIT);
        preempt_enable();
    }
    return page;
}
mbind:
mbind 是 Linux 中的一个系统调用,用于绑定内存页面到指定的 NUMA 节点或 CPU 核心上。mbind 可以用于优化多核系统中的内存访问,提高性能,尤其是在 NUMA(Non-Uniform Memory Access)体系结构下。

SYSCALL_DEFINE6(mbind, unsigned long, start, unsigned long, len,
        unsigned long, mode, const unsigned long __user *, nmask,
        unsigned long, maxnode, unsigned int, flags)
{
    return kernel_mbind(start, len, mode, nmask, maxnode, flags);
}
 
kernel_mbind
    do_mbind
        mbind_range
            vma_replace_policy  //替换vm_area_struct的mempolicy
            
            
            
使用:
page = __alloc_pages_nodemask(gfp, order, preferred_nid, nmask); //在nodemask节点分配页表
pol = get_vma_policy(vma, addr); //获取mempolicy
 => alloc_pages_vma
  =>alloc_page_vma
  =>page = alloc_zeroed_user_highpage_movable(vma, vmf->address); //
 => do_anonymous_page
 => handle_pte_fault
 => __handle_mm_fault
 => handle_mm_fault
 => do_page_fault
 => do_translation_fault
 => do_mem_abort
 => el0_da
 => el0_sync_handler
 => el0_sync
下面是 mbind 的基本用法和一些相关的参数:

c
#define _GNU_SOURCE
#include <numaif.h>
#include <numa.h>
 
int mbind(void *addr, unsigned long len, int mode, const unsigned long *nodemask,
          unsigned long maxnode, unsigned flags);

参数说明:

addr:指向要操作的内存区域的起始地址。
len:指定要操作的内存区域的长度。
mode:指定操作的模式,常见的有 MPOL_BIND(绑定内存) 和 MPOL_DEFAULT(使用默认策略)。
nodemask:一个位掩码数组,指定可以使用的 NUMA 节点。
maxnode:指定 nodemask 的长度(即节点的总数)。
flags:指定其他标志,通常可以设置为 0。

example1(来自gpt,结果不供参考):

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <numaif.h>
 
#define SIZE (1024 * 1024 * 1024) // 1 GB
 
int main(void) {
    // 创建一个匿名映射的内存范围
    void *addr = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (addr == MAP_FAILED) {
        perror("mmap");
        exit(1);
    }
 
    // 设置内存策略为 MPOL_INTERLEAVE,涉及节点 0 和 1
    unsigned long nodemask[2] = {0x3, 0x0}; // 位掩码,表示节点 0 和 1
    int mode = MPOL_INTERLEAVE; // 模式
    int flags = 0; // 标志
    if (mbind(addr, SIZE, mode, nodemask, 2 * 8 * sizeof(unsigned long), flags) == -1) {
        perror("mbind");
        exit(1);
    }
 
    // 写入数据,触发页面分配
    char *p = (char *)addr;
    for (int i = 0; i < SIZE; i++) {
        p[i] = 'a';
    }
 
    // 打印每个页面所在的节点 下面的定有问题 ??????
    int *status = malloc(SIZE / getpagesize() * sizeof(int));
    if (get_mempolicy(NULL, status, SIZE / getpagesize(), addr, MPOL_F_ADDR) == -1) {
        perror("get_mempolicy");
        exit(1);
    }
    for (int i = 0; i < SIZE / getpagesize(); i++) {
        printf("Page %d is on node %d\n", i, status[i]);
    }
 
    // 释放资源
    free(status);
    munmap(addr, SIZE);
    return 0;
}
运行结果如下(部分省略):

Page 0 is on node 0
Page 1 is on node 1
Page 2 is on node 0
Page 3 is on node 1
Page 4 is on node 0
Page 5 is on node 1
Page 6 is on node 0
Page 7 is on node 1
...
Page 262136 is on node 0
Page 262137 is on node 1
Page 262138 is on node 0
Page 262139 is on node 1
Page 262140 is on node 0
Page 262141 is on node 1
Page 262142 is on node 0
Page 262143 is on node 1
example2: 

#define _GNU_SOURCE
#include <numaif.h>
#include <numa.h>
#include <stdio.h>
 
int main() {
    // 分配一块内存
    size_t size = 1024 * 1024 * 100; // 100 MB
    void *mem = numa_alloc_onnode(size, 0); //numactl提供的接口。内部也调用mbind
 
    // 获取当前系统的 NUMA 节点数量
    int numNodes = numa_num_configured_nodes();
 
    // 创建节点掩码,这里使用第一个 NUMA 节点
    unsigned long nodemask = 1 << 0;
 
    // 绑定内存到指定节点
    if (mbind(mem, size, MPOL_BIND, &nodemask, numNodes, 0) == 0) {
        printf("Memory bound to NUMA node 0 successfully.\n");
    } else {
        perror("mbind");
        return 1;
    }
 
    // 使用绑定的内存进行其他操作
 
    // 释放内存
    numa_free(mem, size);
 
    return 0;
}
请注意,使用 mbind 需要确保系统支持 NUMA,并且在编译时需要链接 numa 库。在上面的例子中,使用 numa_alloc_onnode 分配内存,然后使用 mbind 将其绑定到第一个 NUMA 节点。如果 mbind 成功,可以在这块内存上执行其他操作。最后,使用 numa_free 释放内存。


http://www.mrgr.cn/news/50483.html

相关文章:

  • VUE项目基于源码实现可视化编程技术的探索
  • 【C语言教程】【常用类库】(十二)信号处理库 - <signal.h>
  • 【论文速读】字节跳动音乐生成模型 Seed-Music
  • keepalived实现高可用
  • Spark内置函数:字符串、日期和时间函数、聚合函数、数值函数、条件判断函数、窗口函数
  • YOLOv11改进策略【卷积层】| ECCV-2024 Histogram Transformer Block 适用于噪声大,图像质量低的检测任务
  • 学会分享是一种快乐的事情
  • 【保姆级】手把手带你用MyBatis Plus制作代码生成器
  • OpenCV高级图形用户界面(9)更改指定窗口的位置函数moveWindow()的使用
  • 护眼台灯哪个品牌更好?五款目前比较好用的护眼台灯
  • 今年双十一有什么值得买?高性价比好物推荐!
  • ubuntu安装docker及docker compose
  • Midjourney官宣网页版免费用!前谷歌大佬祭出AI生图2.0,全网惊艳实测
  • 代码随想录 -- 贪心 -- 摆动序列
  • vue中加载GLB模型,计算模型的长宽高、绘制模型的边框线
  • 99幅高清修复的中英文旅游地图
  • 从零入门AI篡改图片检测(金融场景)#Datawhale十月组队学习
  • 快充协议的未来前景
  • 4S店汽车行业专业线上小程序源码系统 功能强大 带完整的安装代码包以及搭建部署教程
  • SQL优化最佳实践