开启透明大页(THP)后调用madvise(MADV_HUGEPAGE)始终分配N-1个2MB大页的问题咨询
开启透明大页(THP)后调用madvise(MADV_HUGEPAGE)始终分配N-1个2MB大页的问题咨询
你遇到的这个问题根源很明确——你的虚拟地址区间没有对齐到2MB(THP的页大小)边界,导致内核无法将整个请求的N*2MB区间全部用大页分配,只能给后面对齐的N-1个2MB区间分配大页,前面的非对齐部分只能用普通4KB小页。
具体现象分析
看你给出的测试例子,mmap返回的地址是0x7fa3b6036000,这个地址距离下一个2MB对齐地址0x7fa3b6200000还有0x1c9000字节(约1.75MB)。当你调用madvise(MADV_HUGEPAGE)并memset整个区间后,内核只会对完全对齐到2MB的虚拟地址子区间分配THP大页:
- 从
0x7fa3b6036000到0x7fa3b6200000的非对齐区间:只能用普通小页,不会计入AnonHugePages - 从
0x7fa3b6200000到0x7fa3b7400000的9个完整2MB对齐区间:被分配为THP大页,对应AnonHugePages: 18432 kB(9*2048KB) - 最后从
0x7fa3b7400000到0x7fa3b7436000的剩余224KB区间:同样用普通小页
这就解释了为什么输入N=10时,实际分配的大页是N-1=9个。
解决代码与修改说明
要让内核分配完整的N个THP大页,你需要确保mmap的起始地址和总大小都对齐到2MB边界(你的总大小已经满足,问题在起始地址)。以下是修改后的代码:
#include <sys/mman.h> #include <unistd.h> #include <string.h> #include <stdio.h> #include <stdint.h> #include <stdlib.h> #define HPAGE_SIZE (2 * 1024 * 1024) #define HPAGE_ALIGN_MASK (~(uintptr_t)(HPAGE_SIZE - 1)) int main(int argc, char ** argv) { char cmd[30]; pid_t pid = getpid(); int nr_huge_page; if (argc < 2) { fprintf(stderr, "no args\n"); return 1; } nr_huge_page = atoi(argv[1]); size_t size = nr_huge_page * HPAGE_SIZE; // 先分配比需求大2MB的临时内存,确保有足够空间找到对齐地址 size_t alloc_size = size + HPAGE_SIZE; void *mem = mmap(NULL, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (mem == MAP_FAILED) { perror("mmap temp"); return 1; } // 计算出第一个2MB对齐的地址 uintptr_t aligned_addr = ((uintptr_t)mem + HPAGE_SIZE) & HPAGE_ALIGN_MASK; // 用MAP_FIXED_SAFE重新映射对齐的地址区间(避免覆盖已有映射) void *aligned_mem = mmap((void *)aligned_addr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_SAFE, -1, 0); if (aligned_mem == MAP_FAILED) { munmap(mem, alloc_size); perror("mmap aligned"); return 1; } // 释放临时内存中未对齐的前半部分 munmap(mem, aligned_addr - (uintptr_t)mem); // 后续操作与原代码一致 madvise(aligned_mem, size, MADV_HUGEPAGE); memset(aligned_mem, 0, size); printf("Allocated %zu bytes at %p (aligned to 2MB)\n", size, aligned_mem); sprintf(cmd, "cat /proc/%d/smaps_rollup", pid); system(cmd); munmap(aligned_mem, size); return 0; }
关键修改点说明
- 临时大内存分配:先申请比需求大2MB的内存,确保有足够空间找到2MB对齐的地址
- 地址对齐计算:通过位运算算出第一个符合2MB对齐要求的地址
- 安全重映射:使用
MAP_FIXED_SAFE重新映射到对齐地址(Linux 4.17+支持,比老的MAP_FIXED更安全,不会覆盖已有内存映射) - 清理临时内存:释放未对齐的临时内存段,只保留对齐的目标区间
修改后再测试N=10,AnonHugePages就会显示20480KB(10*2MB),也就是完整的10个THP大页。
额外注意事项
- 若你的内核版本低于4.17,可替换
MAP_FIXED_SAFE为MAP_FIXED,临时分配的大区间内的地址肯定是空闲的,不会有冲突 MADV_HUGEPAGE只是给内核的提示,最终是否分配大页还受THP全局配置(如/sys/kernel/mm/transparent_hugepage/enabled)影响,你的环境已能分配大页,无需额外调整memset操作是必须的:THP采用写时分配机制,只有内存被实际写入脏页化后,内核才会真正分配大页
内容来源于stack exchange
相关产品推荐
相关产品推荐

