CentOS7下MAP_HUGETLB大页内存调用mbind绑定NUMA节点失败
问题原因
这个使用场景本身是完全支持的,mbind调用失败是CentOS7默认3.10系列内核对Hugetlb大页的处理逻辑、以及代码未满足大页场景下的mbind调用约束导致的,和接口本身不支持无关。
普通4K匿名页和Hugetlb大页的核心分配逻辑差异是:
- 普通
MAP_ANONYMOUS映射是按需缺页分配,mmap阶段仅创建虚拟内存区域(VMA),不实际分配物理内存,后续调用mbind设置策略后,第一次访问内存触发缺页时,内核会直接按照绑定策略在目标NUMA节点分配物理页,不需要做内存迁移,因此对参数对齐、标志位的容错性很高。 - 带
MAP_HUGETLB的映射在mmap调用阶段就会从系统大页预留池中直接预留对应数量的物理大页,物理内存已经分配在发起mmap调用时CPU所在的NUMA节点,后续再调用mbind需要触发跨节点大页迁移,对参数、环境的要求非常严格,稍有不符合就会直接报错。
常见的失败原因有三个:
- 内存范围未按大页大小对齐:mbind操作大页内存时,要求传入的起始地址、长度都必须是系统大页大小的整数倍(2M大页对应2MiB对齐,1G大页对应1GiB对齐),内核不会像处理普通4K页那样自动做对齐兜底,不对齐直接返回
EINVAL。 - mbind标志位缺失:对已经分配完成的大页做绑定,需要在mbind的最后一个flags参数传入
MPOL_MF_MOVE,允许内核迁移已存在的物理页;传0的话内核只会修改未来新分配页的策略,不会处理已经预留的大页,3.10内核下会直接返回错误。 - 目标节点大页资源不足:全局配置的大页数不代表每个NUMA节点都有足够的空闲大页,跨节点迁移需要目标节点预留足够的空闲大页,否则mbind会迁移失败返回错误。
另外CentOS7初始版本的3.10内核存在多个Hugetlb和NUMA策略相关的bug,如果是低于3.10.0-1160的内核版本,即使参数正确也可能出现调用失败,建议升级到CentOS7最新的稳定内核版本。
推荐解决方案
兼容性最高、最稳定的实现方式是先设置NUMA策略再分配大页,不要等大页分配完成后再调用mbind做迁移,这个方案在所有CentOS7内核版本上都可正常工作:
- 在调用mmap分配大页前,先通过
set_mempolicy将当前线程的内存策略临时设置为绑定目标NUMA节点 - 再执行mmap分配Hugetlb大页,此时内核会直接从目标NUMA节点的大页池中预留大页,不需要后续迁移
- 大页分配完成后,恢复默认内存策略,避免影响后续其他内存分配操作
可参考的实现代码:
#include <numaif.h> #include <sys/mman.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <unistd.h> int main() { // 获取系统默认大页大小 const long hpage_sz = sysconf(_SC_HUGEPAGESIZE); // 分配长度对齐到大页整数倍,例:分配4个2M大页共8M const size_t alloc_sz = 4 * hpage_sz; const int target_numa_node = 1; unsigned long nodemask = 1UL << target_numa_node; // 临时设置线程内存绑定策略到目标节点 if (set_mempolicy(MPOL_BIND, &nodemask, sizeof(nodemask) * 8) < 0) { printf("set_mempolicy failed: %s\n", strerror(errno)); return 1; } // 分配大页内存,预留的物理大页直接来自目标NUMA节点 void *huge_buf = mmap(NULL, alloc_sz, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_HUGETLB, -1, 0); if (huge_buf == MAP_FAILED) { printf("hugetlb mmap failed: %s\n", strerror(errno)); // 恢复默认内存策略 set_mempolicy(MPOL_DEFAULT, NULL, 0); return 1; } // 恢复默认内存策略,不影响后续内存分配 set_mempolicy(MPOL_DEFAULT, NULL, 0); /* 正常使用huge_buf即可,所有物理内存已经在目标NUMA节点 */ munmap(huge_buf, alloc_sz); return 0; }
编译时需要链接NUMA库,添加编译参数-lnuma。
额外排查项
- 分配前确认目标NUMA节点的空闲大页数量足够,可查看对应路径的计数:
/sys/devices/system/node/node<目标节点号>/hugepages/hugepages-<大页大小>/free_hugepages - 如果坚持使用先mmap再mbind的流程,除了保证地址、长度按大页对齐外,mbind调用需要添加
MPOL_MF_MOVE标志,同时保证目标节点空闲大页数量满足分配需求。
内容的提问来源于stack exchange,提问作者ByteMe95
相关产品推荐
相关产品推荐

