HugePage解析

原文出自公众号 深度Linux

Linux的内存体系以页作为内存管理的最小单元,默认大小为4KB。当前业务中存在的大量高并发、数据库和中间件等大内存占用场景使得页表项数量暴涨,进而占用大量内核内存,同时导致TLB缓存失效和Page Fault 缺页异常,加剧寻址开销。

内存基础知识

系统地址

  • 虚拟地址:操作系统和程序看到的逻辑地址,是操作系统中各进程内存隔离的实现形式。
  • 物理地址:内存芯片实际的物理地址,对应DRAM或其余存储元件的真实存储单元。CPU通过地址总线发送物理地址访问内存。
  • 总线地址:外设(DMA、网卡)在直接访问内存时所用地址,往往与物理地址相同,可能需IOMMU进行转换

MMU与TLB

  • 内存管理单元:完成虚拟地址到物理地址转换和检查内存访问权限的硬件组件。一方面根据虚拟地址的页号从页表中获取到页框号,另一方面检查页访问权限和用户特权等级
  • 翻译后备缓冲器:缓存近期使用的虚拟地址到物理地址的映射关系,加速寻址过程

内存分页和分表

  • 内存分页机制
    • Linux将虚拟内存和物理内存均划分为多个连续的固定大小的地址范围,分别对应页和页框,作为内存管理的最小分配单位
    • 页由操作系统定义,页框的大小与页完全一致
    • 一个页通过页表映射到一个页框,完成虚拟地址到物理地址的转换
    • 多个页也可以映射到同一个页框,常见于共享内存和写时复制
  • 页表
    • 存储页与页框的对应关系
    • 页的访问权限:读写执行权限,对用户态、内核态的访问限制,
    • 存在标志:当前页是否已经映射到物理页框
    • 页表组织格式与CPU架构有关,x86采用页表项格式,能够被相应MMU识别
  • 页内偏移计算:
    • 确定页大小后,虚拟地址中页内偏移已经确定,由于页与页框大小一致,虚拟地址中的页内偏移能直接映射到物理地址的偏移部分

多级页表

  • 单级页表缺陷:对于虚拟地址为4GB的32位操作系统,在默认4KB页大小情况下,单级页表需要维护\(2^{20}\)个页表项,假设每个页表项占用4字节,则需要占用4MB内存
  • 多级页表优化思路:核心思想为“按需分配页表”,虚拟地址的页号部分被拆分为多个部分,根据进程的使用来创建下级页表。

大页机制

HugePage

使用更大的单位来管理内存:

  • 减少页表项数量
  • 提高TLB命中率
  • 降低内存碎片

大小页协同工作

Linux内核中的大页和小页是通过多级页表映射共存的。不同层级页表中的元素可以对应不同大小的页。

  • 在x86_64的4级页表中,页表遍历到页表项(PTE)层级以获取4KB小页;
  • 内核将PTE位设置为0,页表遍历到中间页目录层级就终结,PMD代表2MB的物理内存,覆盖了原512个小页的范围;
  • 内核将PTE和PMD均设置为0,页表则遍历到上级页目录终结,一个PUD对应1GB内存

内存大页和小页存在独立的内存分配机制

  • 小页由伙伴系统管理,大页由hugetlbfs系统管理,有着专门的内存池。
  • 静态大页机制下,内核启动时根据hugepagesz和nr_hugepages参数来预留一块连续的物理内存作为大页专用池。
  • 在透明大页机制下,内核通过khugepaged守护进程扫描内存,将连续的小页合并成大业,实现大页小页在物理内存中动态转换,共享伙伴系统内存池

静态大页与透明大页

静态大页

  • 需手动配置,明确指定大页数量和大小,例如内核启动参数hugepagesz和nr_hugepages划分出独立内存池
  • 应用程序显式进行系统调用mmap或shmget并且带有MAP_HUGETLB标志来申请和管理大页
  • 性能有保证:大页被预先分配,系统运行不会触发内存整理,大页分配和使用效率高,减少动态分配开销,避免碎片化
  • 手动配置增加管理复杂度,未充分利用的大页区域会导致内存浪费,且预分配不足后很难动态调整

透明大页

  • 自动管理,对用户和应用程序透明
  • 内核khugepaged守护进程会在系统运行时动态合并连续小页成大页,内存分布不连续会触发后台整理任务
  • 系统可在小页和大页之间动态切换,使用大多数通用场景
  • 动态分配大页和后台内存整理可能会导致性能抖动

HugePage的配置和实践

系统对HugePage的支持

通过查看/proc/meminfo文件获取大页信息

cat /proc/meminfo | grep -i huge
HugePages_Total:系统配置大页总数
HugePages_Free:当前空闲大页总数
HugePages_Rsvd:预保留但未实际分配大页数量
HugePages_Surp:额外大页数量,启用超大页的超分功能会用
Hugepagesize:每个大页的大小

配置大页

  1. 静态预留大页:编辑/etc/sysctl.conf文件,在文件末尾添加一行vm.nr_hugepages = [数量],大页默认大小为2MB。添加完成后执行sudo sysctl -p更新大页配置
  2. 挂载 hugetlbfs 文件系统,挂载完成后,应用程序可使用mmap等系统调用和MAP_HUGETLB标志来映射/hugepages目录下的文件来使用大页内存
    mkdir /hugepages
    # -t hugetlbfs指定文件系统类型
    # none 为设备名称
    sudo mount -t hugetlbfs none /hugepages
    # 持久化在/etc/fstab 文件添加
    none /hugepages hugetlbfs defaults 0 0
    
  3. 应用启动大页内存:以DPDK为例,需要在启动脚本中--huge-dir参数指定大页挂载目录,并且确保启动前系统预留足够大页。

配置大页注意事项

内存连续性问题

HugePage 分配依赖连续物理内存,在内存碎片化严重时会出现分配失败,解决问题思路:

  1. 内存整理:通过echo 1 > /proc/sys/vm/compact_memory命令尝试出发内核内存整理机制
  2. 重启系统:重启后内存处于初始状态,没有经过内存分配和释放操作,碎片化程度低
  3. 合理规划内存使用:应用层面尽量避免频繁分配和释放内存块,尤其大内存;可以采用内存池技术预先分配内存再重复利用

权限和配置文件设置

  • 应用程序使用HugePage存在用户权限要求,普通用户可能无权限直接使用大页,需具备CAP_IPC_LOCK能力,需要将用户添加到特定组或修改/etc/security/limits.conf文件来设置
  • 大页内存文件/etc/sysctl.conf文件是否书写错误

透明大页的干扰

透明大页是内核自动管理的内存优化机制,在系统运行中会动态将小页合并成大页,对应用透明。在与HugePage共存时候,一方面在内存管理上冲突,另一方面透明大页在严重碎片化也会干扰HugePage正常工作。

# 检查透明大页状态,含always表示启用,never表示禁用
cat /sys/kernel/mm/transparent_hugepage/enabled
# 临时关闭透明大页,在重启后生效
echo never > /sys/kernel/mm/transparent_hugepage/enabled
# 永久关闭大页,/etc/default/grub文件
GRUB_CMDLINE_LINUX="... transparent_hugepage=never"
# 保存文件后执行一下命令且重启系统后生效
sudo update-grub

应用场景

  • 数据库系统
  • 虚拟化环境
  • 高性能计算和大数据处理
posted @ 2026-07-21 02:11  tanch25  阅读(8)  评论(0)    收藏  举报