
如何用 mmap + MAP_HUGETLB 分配大页内存
直接用 mmap 申请大页是最快路径,但前提是系统已配置好大页且权限到位。不是加个 flag 就能跑通,得先确认内核支持、页池有余量、进程有锁页权限。
- 必须提前在系统侧分配大页,比如
echo 128 > /proc/sys/vm/nr_hugepages(128是 2MB 页数量);若用 1GB 页,需写入/proc/sys/vm/nr_hugepages_1gb(仅较新内核支持) - 普通用户默认无权使用大页,需运行
sudo sysctl vm.hugetlb_shm_group=$(id -g)或给进程加CAP_IPC_LOCK能力 -
mmap时必须指定length为大页大小的整数倍(如2 * 1024 * 1024),且addr建议传nullptr让内核对齐,手动指定地址容易因未按大页边界对齐而失败 - 失败时
mmap返回MAP_FAILED,errno通常是ENOMEM(页不足)或EPERM(权限不够),别只看返回值忽略errno
示例:分配一块 2MB 大页缓冲区
void* buf = mmap(nullptr, 2 * 1024 * 1024,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
-1, 0);
if (buf == MAP_FAILED) {
perror("mmap hugepage failed");
// 检查 /proc/meminfo 中 HugePages_Free 是否 > 0
}为什么不能直接用 malloc 或 new?
malloc 和 new 完全不感知大页——它们走的是 glibc 的 brk 或 mmap(小页模式),即使后台启用了透明大页(THP),也只是内核尝试合并,无法保证分配结果,更无法控制 TLB 行行为。
- THP 对堆内存效果极差:频繁
malloc/free导致大页被反复拆分,实际仍以 4KB 页访问,TLB miss 不降反升 - 某些 libc(如 musl)甚至完全禁用 THP for heap,
malloc永远拿不到大页 - 若真想用堆式语义,可封装一层:用
mmap(MAP_HUGETLB)预分配大块,再在上面做 slab 管理,但要自己处理对齐、释放、线程安全
madvise(MADV_HUGEPAGE) 的真实作用范围
这个调用常被误解为“把已有内存转成大页”,其实它只是向内核提建议,仅对匿名映射(MAP_ANONYMOUS)或 hugetlbfs 文件有效,且依赖 THP 策略和内存碎片情况,成功率不稳定。
立即学习“C++免费学习笔记(深入)”;
- 对
mmap普通文件、malloc出来的内存、或已锁定的物理页,MADV_HUGEPAGE直接静默忽略 - 即使成功,也只影响后续缺页路径,已有 4KB 页不会被合并,缓冲区初始化阶段仍会触发大量 TLB miss
- 生产环境别依赖它做关键路径优化;它适合做后台预热,而非主缓冲区构造手段
如何验证大页真的生效了?
别信代码里 flag 写对了就完事。得从三处交叉验证:分配是否成功、物理页是否大页、TLB miss 是否下降。
- 检查
/proc/self/maps:对应地址行应含huge标记,例如7f8b2c000000-7f8b2c200000 rw-p 00000000 00:00 0 [anon:hugepages] - 查
/proc/self/numa_maps:看mm=字段是否带huge,以及huge=2M或huge=1G - 用
perf stat -e dTLB-load-misses,uTLB-load-misses对比前后数值,理想情况下 uTLB miss 应下降 10x 以上(2MB 页 vs 4KB 页理论比是 512:1) - 注意:若缓冲区太小(如
真正难的不是分配,是让整个数据流(分配 → 初始化 → 访问 → 释放)全程不退化到小页路径。任何一次 mmap 失败回退、任意一个越界读写导致 COW、任意一个未对齐的指针运算,都可能让 TLB 优势归零。










