You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

mmap分配虚拟内存返回ENOMEM报错及入参校验方法咨询

mmap调用入参合法性校验方案

问题现象

ftrace捕获到mmap调用失败日志:

mmap(0x200000000000, 17179869184, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = -1 ENOMEM (Cannot allocate memory)

触发场景为尝试分配128TB以上高地址空间的匿名私有内存块,对应业务代码如下:

void alloc_page_full_reverse()
{
printf("Allocating default pagesize pages > 128TB \n");
mmap_chunks_higher(24575, 0);
printf("Allocating default pagesize pages < 128TB \n");
/* Note: Allocating a 16GB chunk less due to heap space required 
for other mappings */
mmap_chunks_lower(8190, 0);
  }

int mmap_chunks_higher(unsigned long no_of_chunks, unsigned long hugetlb_arg)
 {
unsigned long i;
char *hptr;
char *hint;
int mmap_args = 0;
for (i = 0; i < no_of_chunks; i++){
    hint = hind_addr();
    hptr = mmap(hint, MAP_CHUNK_SIZE, PROT_READ | PROT_WRITE,
        MAP_PRIVATE | MAP_ANONYMOUS | hugetlb_arg, -1, 0); // MAP_CHUNK_SIZE = 16GB

    if (hptr == MAP_FAILED){
        printf("\n Map failed at address %p < 384TB in iteration = %d \n", hptr, i);
        exit(-1);   
    }

    if (validate_addr(hptr, 1)){
        printf("\n Address failed, not in > 128Tb iterator = %d\n", i);
        exit(-1);
    }
}
printf("> 128Tb: \n chunks allocated= %d \n", i);
}

static char *hind_addr(void)
{
int bits = 48 + rand() % 15;
return (char *) (1UL << bits);
}

mmap函数标准原型:

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

各入参前置校验规则

1. void *addr 校验

  • 对齐校验:传入非NULL地址作为映射hint时,必须保证地址按系统页大小对齐,页大小可通过sysconf(_SC_PAGESIZE)获取。若flags携带MAP_FIXED标志,非页对齐地址会直接触发EINVAL错误;无MAP_FIXED时不对齐地址会被内核直接忽略,导致映射到非预期地址段。
  • 地址范围校验:
    • 提前确认系统虚拟地址位宽:x86_64架构默认4级页表下用户态地址空间上限为47位(即128TB),仅当内核开启5级页表(CONFIG_X86_5LEVEL=y,cpuinfo存在la57标志)时,才支持128TB以上的用户态地址。生成的hint地址不能超过当前系统支持的用户态地址上限,否则直接触发ENOMEM。
    • 校验hint地址不落在已占用的地址段,可通过解析/proc/self/maps获取当前进程已分配地址区间,避免hint与栈、vdso、已有映射段冲突。
  • 标志关联校验:携带MAP_FIXED标志时,addr必须为未占用的合法地址,否则会直接覆盖已有映射造成内存破坏。

2. size_t length 校验

  • 非零校验:length不能为0,否则直接返回EINVAL。
  • 溢出校验:length为无符号类型,计算映射长度时必须校验整数溢出,避免传入截断后的异常值;映射总长度不能超过RLIMIT_AS(进程虚拟内存上限,可通过getrlimit获取)限制。
  • 资源上限校验:内核实际分配时会将length按页大小向上取整,需提前计算取整后的总映射大小,同时校验当前进程的映射段数量不超过/proc/sys/vm/max_map_count限制,避免映射段数量超限触发ENOMEM。
  • 大页场景额外校验:如果使用hugetlb大页映射,length必须按对应大页大小对齐,且系统预留的对应规格大页数量足够。

3. int prot 校验

  • 合法位校验:仅允许传入PROT_READ、PROT_WRITE、PROT_EXEC、PROT_NONE以及对应架构支持的扩展标志(如PROT_GROWSDOWN),传入未定义的权限位会触发EINVAL。
  • 权限匹配校验:文件映射场景下,prot权限不能超过fd打开时的权限,例如只读打开的文件不能映射为可写;匿名映射无此限制。

4. int flags 校验

  • 必填标志校验:MAP_PRIVATE和MAP_SHARED必须且仅能选择一个传入,同时传或同时不传都会触发EINVAL。
  • 匿名映射关联校验:携带MAP_ANONYMOUS标志时,必须配套传入fd=-1、offset=0,否则行为未定义。
  • 兼容性校验:使用新引入的mmap标志(如MAP_HUGETLB、MAP_POPULATE)时,提前校验内核版本支持情况,传入不支持的标志位会触发EINVAL。

5. int fd 校验

  • 匿名映射场景:必须传入-1,不能传入有效文件描述符。
  • 文件映射场景:必须传入当前进程持有的有效、未关闭的文件描述符,否则返回EBADF;同时提前校验文件大小不小于offset+length,否则后续访问映射区域会触发SIGBUS。

6. off_t offset 校验

  • 对齐校验:文件映射场景下,offset必须按系统页大小对齐,否则返回EINVAL。
  • 范围校验:offset必须为非负值,匿名映射场景offset固定传0;文件映射场景下offset+length不能超过对应文件的大小。

本次报错的核心原因:传入的hint地址0x200000000000为49位虚拟地址,若系统未开启5级页表,该地址超出默认128TB用户态地址空间上限,即使物理内存充足也会返回ENOMEM。

内容的提问来源于stack exchange,提问作者samir mulani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.31 17:06:28