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

开启透明大页(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;
}

关键修改点说明

  1. 临时大内存分配:先申请比需求大2MB的内存,确保有足够空间找到2MB对齐的地址
  2. 地址对齐计算:通过位运算算出第一个符合2MB对齐要求的地址
  3. 安全重映射:使用MAP_FIXED_SAFE重新映射到对齐地址(Linux 4.17+支持,比老的MAP_FIXED更安全,不会覆盖已有内存映射)
  4. 清理临时内存:释放未对齐的临时内存段,只保留对齐的目标区间

修改后再测试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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 07:35:28