Linux 6.6内核内存管理深度解析(四):大页(HugeTLB / THP)— 为什么一个页表项要管 2MB

发布时间:2026/10/15 2:49:08
Linux 6.6内核内存管理深度解析(四):大页(HugeTLB / THP)— 为什么一个页表项要管 2MB 〇、全景4KB 页不够用TLB 先扛不住默认页大小是 4KB。这对大内存工作负载是灾难TLB页表缓存容量有限一个 TLB 项只覆盖一个页。进程用 1GB 内存 262144 个 4KB 页需要 26 万条 TLB 项——远超 TLB 容量L1 几十到几百项、L2 几千项结果就是频繁 TLB miss每次 miss 都要走一遍多级页表翻译慢一个数量级。大页就是让一个页表项覆盖更多内存2MB 大页一个项顶 512 个 4KB 页1GB 大页一个项顶 26 万页。内核有两种大页机制完全不同大页一个页表项管 2MB / 1GBHugeTLB显式预留池THP 透明大页内核自动hugetlbfs / MAP_HUGETLB管理员预先保留hstate 预留池缺页时直接分 2MBdo_huge_pmd_anonymous_page后台合并 4KB→2MBkhugepaged一句话主线大页让一个页表项覆盖 2MB/1GB减少页表层级和 TLB miss内核两条路——HugeTLB 是显式预留池管理员/用户预先申请、从池子里分配THP 是透明大页缺页时do_huge_pmd_anonymous_page直接分 2MB khugepaged后台把 4KB 页合并成 2MB两者都在缺页路径上把分配一页升级成分配一个大页。一、为什么大页TLB 覆盖 页表层级两个直接收益TLB 覆盖率暴增同样 1GB 内存4KB 页要 262144 个 TLB 项2MB 页只要 512 项1GB 页只要 1 项。TLB miss 数量成数量级下降对访存密集的负载数据库、HPC、GPU 计算是实打实的性能。页表层级变浅4KB 页要完整走 PGD→P4D→PUD→PMD→PTE 五级2MB 大页到 PMD 就停PMD 直接指向大页省了 PTE 一层1GB 大页到 PUD 就停省 PMDPTE 两层。翻译更快、页表本身也省内存。代价也明显内存碎片——要连续 2MB/1GB 物理内存内存碎成小页后就凑不出来了。这是大页永恒的矛盾见 §六。二、大页的页表层级PMD / PUD 直接指向大页大页的核心 trick 是在页表中间层就停下来让中间层页表项直接指向大页大页大小停在哪一层省掉哪些层对应 shift4KB普通页PTE全走PAGE_SHIFT122MBPMD省 PTE 一层PMD_SHIFT211GBPUD省 PMDPTE 两层PUD_SHIFT30// arch/x86/include/asm/pgtable_64_types.h (v6.6, line 91 / 84)#definePMD_SHIFT21// 2^21 2MB#definePUD_SHIFT30// 2^30 1GB于是缺页时内核会先问这一层能不能直接给个大页能就少走几层// mm/memory.c (v6.6, line 5057) —— __handle_mm_fault 里if(pud_none(*vmf.pud)hugepage_vma_check(vma,vm_flags,false,true,true)){retcreate_huge_pud(vmf);// 试着直接建 1GB 大页if(!(retVM_FAULT_FALLBACK))returnret;}// …if(pmd_none(*vmf.pmd)hugepage_vma_check(vma,vm_flags,false,true,true)){retcreate_huge_pmd(vmf);// 试着直接建 2MB 大页if(!(retVM_FAULT_FALLBACK))returnret;}hugepage_vma_checkhuge_memory.c:74判断这个 vma 能不能用大页是否对齐、权限、是否被 madvise 禁止等create_huge_pmd最终落到do_huge_pmd_anonymous_page。三、HugeTLB显式预留池HugeTLB 是显式的管理员先通过内核参数//proc/sysfs预留一批大页用户态用mmap(MAP_HUGETLB)或挂载hugetlbfs显式申请。每种大小的大页对应一个hstate// mm/hugetlb.c (v6.6, line 53)structhstatehstates[HUGE_MAX_HSTATE];// 每种大页大小一个 hstatehstate维护一个预留池free_huge_pages/resv_huge_pages/nr_huge_pages计数。hugetlb_inithugetlb.c:4229subsys_initcall启动时把预留的大页建好。用户访问大页时缺页走hugetlb_faulthugetlb.c:6079从预留池dequeue一个大页alloc_hugetlb_folio_vmahugetlb.c:2515池子不够 → 尝试从 buddy 补alloc_buddy_hugetlb_foliohugetlb.c:21201GB 页还要用 CMA/alloc_gigantic_folio填好 PTE/PMD返回。HugeTLB 的特点用户/管理员完全掌控——申请多少、什么时候申请、用多大页都显式指定。好处是确定性池子就那么大不会多给坏处是要手动管理。四、THP透明大页 khugepagedTHP 是透明的用户什么都不用做内核自动把连续的 4KB 页合成 2MB 大页。有两条路径4.1 缺页时直接分 2MBdo_huge_pmd_anonymous_page匿名映射堆、栈、malloc大块缺页时如果满足条件do_huge_pmd_anonymous_pagehuge_memory.c:774直接分配一个 2MB 页而不是 4KB// mm/huge_memory.c (v6.6, line 774)vm_fault_tdo_huge_pmd_anonymous_page(structvm_fault*vmf){// 读访问 → 用全局共享的 2MB 零页transparent_hugepage_use_zero_pageif(!(vmf-flagsFAULT_FLAG_WRITE)…){zero_pagemm_get_huge_zero_page(vma-vm_mm);// …}// 写访问 → 真正分配一个 2MB 页__do_huge_pmd_anonymous_page:646// …khugepaged_enter_vma(vma,vma-vm_flags);// 把这个 vma 登记给 khugepaged}这和普通匿名页上一篇 §5.1 的do_anonymous_page是同一个逻辑的2MB 放大版读用共享零页、写才分配只不过一次分 2MB。4.2 后台合并 4KB→2MBkhugepaged已经用 4KB 页建立的映射由khugepaged内核线程后台扫描、把连续的 4KB 页合并成 2MB// mm/khugepaged.c (v6.6, line 2484)staticvoidkhugepaged_do_scan(structcollapse_control*cc){// … 遍历 mm → hpage_collapse_scan_pmd:1240// 扫到一个 PMD 范围内的页都值得合并→ collapse_huge_page:1080}collapse_huge_pagekhugepaged.c:1080把 512 个 4KB 页复制进一个新的 2MB 页、改 PMD 指向它、释放原来的 4KB 页。合并失败内存不够、页被换出等就跳过。THP 还支持拆分__split_huge_pmdhuge_memory.c:2266把 2MB 大页拆回 4KB内存压力、需要回写时。THP 的开关/sys/kernel/mm/transparent_hugepage/enabledalways/madvise/never。上游默认是alwaysmm/Kconfig:831许多发行版把它改成madvise只在madvise(MADV_HUGEPAGE)的区域开 THP。五、与 GPU DMA 的关系面试高频大页对 GPU 计算尤其重要核心还是TLB但这次是IOMMU 的 TLBGPU 通过 DMA 访问主存时设备地址 → 物理地址的翻译走IOMMU 页表和 CPU 的 MMU 页表是两套。IOMMU 也有 TLB容量同样有限。GPU 做大规模显存拷贝/计算CUDAcuMem、训练数据搬运时若用 4KB 页IOMMU TLB miss 会成为瓶颈用 2MB/1GB 大页IOMMU 页表项和 TLB miss 数量级下降。所以 GPU 场景尤其 GPU 直通/虚拟化、大显存 DMA几乎都要求大页。IOMMU 页表与大页的细节见 [[blog-iommu-expansion]]。六、为什么这样设计Why 层6.1 为什么是显式 HugeTLB和透明 THP两条路而不是合一条因为两类用户需求相反HugeTLB 要确定性和控制数据库/HPC 希望自己精确控制内存布局、预留多少、绝不 OOM 才用THP 要零配置普通应用希望自动获得好处、又不用懂大页。前者给池子和显式 API后者给全自动 madvise 微调。显式 vs 透明对应专业用户 vs 普通用户两种取舍。6.2 为什么 THP 要缺页时直接分 2MB而不是只靠 khugepaged 事后合并因为事后合并有窗口期新分配的页一开始都是 4KB等 khugepaged 扫到才变 2MB这期间 TLB 该 miss 还是 miss。缺页时直接分 2MB让刚申请的连续内存第一时间就是大页。两者互补直接分覆盖新分配khugepaged 覆盖老 4KB 页。6.3 为什么大页最大的敌人是内存碎片大页要连续的 2MB/1GB 物理内存。buddy#2虽然努力把空闲页合并成大块但长期运行后内存会被碎成小页凑不出连续 2MB。这是大页的固有矛盾越大越快也越难凑齐。所以内核有khugepaged/内存规整compaction/CMA 等手段持续整理碎片、为大页腾连续空间。6.4 为什么读大页也用共享零页和普通匿名页一样#3 §6.4程序常malloc大块但暂时只读内容全是 0。让所有进程共享一个 2MB 零页省下海量内存一旦要写再 COWdo_huge_pmd_wp_pagehuge_memory.c:1289复制一份。零页共享 写时复制对大页同样成立只是从 4KB 放大到 2MB。七、与已有博客的交叉引用已有博客本篇覆盖的关系内存 #3 页表缺页本篇 §二 的大页是 #3 缺页流程在 PMD/PUD 层的提前终止do_huge_pmd_anonymous_page就是do_anonymous_page的 2MB 版内存 #2 buddy2MB 大页从 buddy 拿order9的连续块alloc_buddy_hugetlb_folio但 1GB 大页是order18超出 buddy 的MAX_ORDER10buddy 给不了走 CMA/alloc_contig_pagesalloc_gigantic_folio——碎片问题正是 buddy 的 order 机制固有矛盾IOMMU 系列本篇 §五 GPU DMA 用大页减少的是IOMMUTLB miss不是 MMU两套页表各自独立见 [[blog-iommu-expansion]]附本篇关键源码索引符号位置hstates[HUGE_MAX_HSTATE]每种大页一个 hstatemm/hugetlb.c:53hugetlb_init预留池初始化mm/hugetlb.c:4229hugetlb_fault大页缺页处理mm/hugetlb.c:6079hugetlb_reserve_pages/alloc_hugetlb_folio_vmamm/hugetlb.c:6703/:2515alloc_buddy_hugetlb_folio从 buddy 拿大页mm/hugetlb.c:2120hugepage_vma_checkvma 能否用大页mm/huge_memory.c:74do_huge_pmd_anonymous_pageTHP 缺页分 2MBmm/huge_memory.c:774do_huge_pmd_wp_page大页写时复制mm/huge_memory.c:1289__split_huge_pmd大页拆回 4KBmm/huge_memory.c:2266khugepaged_do_scan/hpage_collapse_scan_pmd/collapse_huge_pagemm/khugepaged.c:2484/:1240/:1080__handle_mm_fault里的大页入口create_huge_pud/pmdmm/memory.c:5057/:5091PMD_SHIFT2MB/PUD_SHIFT1GBarch/x86/include/asm/pgtable_64_types.h:91/:84

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询