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、已有映射段冲突。
- 提前确认系统虚拟地址位宽:x86_64架构默认4级页表下用户态地址空间上限为47位(即128TB),仅当内核开启5级页表(
- 标志关联校验:携带
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
相关产品推荐
相关产品推荐

