资讯详情

Linux HugePages配置实战:从TLB原理到数据库性能优化

📅 2026/9/30 10:25:07 | 华诺云谱 👁 阅读
Linux HugePages配置实战:从TLB原理到数据库性能优化
简介本资源为Linux环境下快速配置HugePages的完整步骤指南面向需要优化大内存应用性能的数据库管理员、系统运维人员及Linux中高级用户尤其针对Oracle数据库在RHEL等系统上使用大页内存的场景。内容涵盖memlock无限制设置、vm.nr_hugepages参数计算与配置、sysctl生效及重启验证并补充ASM/ASMM与AMM的兼容性提醒、SGA调整后的重算思路等关键注意事项。资源为1个PDF文件压缩包约53KB便于离线查阅或打印对照操作。已有1700余人学习下载。文中除配置步骤外还提供hugepages_settings.sh脚本的完整内容与执行逻辑读者可直接参考脚本计算推荐值减少手工估算误差同时获得与文档编号401749.1一致的官方配置思路适合在真实服务器上快速落地并规避常见坑点。1. 4K页表把数据库拖成“抖动”HugePages到底在解决什么问题在一台内存明明还剩一半的数据库服务器上访问延迟却每隔一会儿猛跳一次load average无故抬升翻dmesg又只能看到一堆page allocation stall换到嵌入式Linux的采集板卡固定分配大内存的进程偶尔直接退出理由同样是内存分配失败。这两类问题常常有一个共同根因Linux默认把物理内存切成4KB的小页工作集一放大TLB页表缓存就会频频未命中内存访问从几十纳秒拖成一次完整缺页中断。Linux系统下快速配置HugePages大页就是把页表粒度从4KB提到2MB甚至1GB让TLB用更少的条目覆盖更大的内存区从根上缓解这类抖动。这篇笔记会沿着配置HugePages的完整步骤展开先判断该不该用、尺寸给多少再走sysctl、挂载和应用接入最后把我在服务器Linux运维和嵌入式Linux里踩过的坑列出来。2. 动手前先摸清家底应用该不该用HugePages、内存预算怎么给2.1 用两条Linux常用命令盘点HugePages现状配置HugePages最忌讳一上来就写vm.nr_hugepages结果既不知道当前池子状态也不知道目标进程到底走哪条内存路径。先做一次只读检查# 整体看内存余量判断可动用的物理内存 free -h # 单独过滤HugePages字段Linux系统管理里最惯用的一条 grep -Ei HugePages_|Hugepagesize /proc/meminfo第一遍跑出来如果看到HugePages_Total是0说明这台机器从没开过静态大页池。看Hugepagesize字段常见值是2048kB少数CPU/平台配的是1GB。这个值决定后面算页数时分母用2还是1048576。还有一个容易看漏的字段是HugePages_Rsvd它表示已经预留但还没被实际写入的页。很多管理员拿HugePages_Free判断“够不够”但真正要等Rsvd被消费后Free才会下降。所以做前后对比时应该同时盯住Free和Rsvd两个字段。2.2 用pmap和smaps判断应用内存模型不是所有程序都能通过改一个参数就吃到大页。进程要使用2MB静态大页通常走两条路径一条是共享内存段shmget加SHM_HUGETLB标志Oracle和PostgreSQL都这么干另一条是文件映射或匿名映射mmap加MAP_HUGETLB标志很多自研服务走这里。默认的glibc malloc并不透明使用静态HugePages。先确认目标进程现在是什么形态# 假设数据库主进程PID是4231看它内存段组成 pmap -x 4231 | tail -20 # 更精确看每段映射的页大小足够大的段会单独显示 grep -E KernelPageSize|MMUPageSize /proc/4231/smaps | sort | uniq -cpmap 输出里如果大量是anon段说明进程靠匿名内存承载堆和共享内存如果出现/dev/shm或SYSV开头的段说明它用POSIX或System V共享内存。后面配置数据库时这条判断决定你要调的是vm.hugetlb_shm_group还是给代码加MAP_HUGETLB。grep smaps那段能直接看出进程里现在有没有已经映射了大页的段——如果KernelPageSize普遍是4kB那就还在用默认小页。2.3 估页数和内存预算一个可抄的小公式HugePages池是“预分配”出来的分配出去的页不会被普通内存申请占用也不会被换出到swap。给多了浪费物理内存给少了数据库启动直接失败。业界常规做法是按共享内存上限再加一点余量# 假设Oracle的SGA打算开8GB池子用2MB页 SGA_MB8192 SAFE_MB$((SGA_MB 512)) # 多留512MB给内核日志、临时段 NR_PAGES$((SAFE_MB / 2)) # 2MB页所以除以2 echo 建议nr_hugepages$NR_PAGES这个公式算出来是4352页约8.5GB池子。注意这里留的512MB只是基础余量不是绝对值。如果你的业务有瞬时创建大共享内存段的习惯比如测试环境频繁重启数据库就把余量提到1GB以上。还有个常被忽略的参数vm.nr_overcommit_hugepages它允许内存在短时间内超过静态池上限去预借。生产环境我一般保持较小值或直接关闭避免出现“共享内存段建出来了但实际没有页可用的悬空状态”。测试环境开一点倒是方便后面避坑章节会细说。提示以上估算只是静态池预算。应用在池外还有自己的进程堆、栈和文件缓存这些不会占用HugePages池所以不要拿整机内存去套公式。3. 快速配置的完整命令行从sysctl到hugetlbfs挂载3.1 临时生效先用sysctl验证预算第一次配置不要直接写进/etc/sysctl.conf先在运行环境里把池子建出来确认页数能分配成功、进程能启动再固化配置。临时生效只要一条命令# 按上一章的预算临时分配4352个2MB页 sysctl -w vm.nr_hugepages4352 # 马上回读看Total是不是等于设置值 grep -Ei HugePages_|Hugepagesize /proc/meminfosysctl -w本质是往/proc/sys/vm/nr_hugepages写入一个目标值。内核收到这个值时会尝试一次性预留对应数量的连续物理页所以如果系统内存碎片严重HugePages_Total会小于你写的目标甚至停在0。遇到这种情况先看/proc/buddyinfo的空闲块分布或者执行sync echo 3 /proc/sys/vm/drop_caches释放页缓存再试。这一步成功后的状态只对本次开机有效重启就丢。它的价值在于让你快速验证预算可行性和应用启动成功率不用反复重启。3.2 持久化写入/etc/sysctl.conf并让GRUB兜底临时验证通过后把参数固化。常见做法是一起维护五个关联项cat /etc/sysctl.conf EOF vm.nr_hugepages 4352 vm.nr_overcommit_hugepages 64 vm.hugetlb_shm_group 1000 kernel.shmmax 9011200000 kernel.shmall 2200000 EOF # 立即加载并核对数值 sysctl -p grep -Ei HugePages_|Hugepagesize /proc/meminfo这里逐个解释参数含义vm.nr_hugepages是池子的静态页数决定总容量。vm.nr_overcommit_hugepages允许“借”的页数上限默认0表示不允许超额。vm.hugetlb_shm_group是关键中的关键它指定哪个用户组有权创建使用HugePages的共享内存段。Oracle的DBA用户、PostgreSQL的运行用户都要放进这个组否则即使池子有富余应用也建不出SHM_HUGETLB段。kernel.shmmax限制单个共享内存段字节数必须大于SGA或PostgreSQL的shared_buffers规划值。kernel.shmall是共享内存总页数上限单位是普通4KB页不是2MB大页这里顺便一起调。如果担心sysctl.conf在启动早期没被读取可以把池子大小写进内核引导参数# 编辑/etc/default/grub在GRUB_CMDLINE_LINUX里追加别删原有参数 # GRUB_CMDLINE_LINUX... crashkernelauto hugepagesz2M hugepages4352 # 然后重新生成grub配置不同发行版命令略有差别 grub2-mkconfig -o /boot/grub2/grub.cfg这样内核在最早期就尝试从连续内存里预留大页成功率比sysctl阶段高一截。Debian/Ubuntu系对应update-grub路径以实际发行版为准。3.3 挂载hugetlbfs并给用户组授权静态池建好后要让应用程序能按文件方式拿到大页还需要hugetlbfs。这一步不是所有场景都必要——Oracle走的是共享内存段通常可以不挂载但自研服务、Redis类内存系统、以及很多嵌入式Linux里的用户态程序都习惯在hugetlbfs上open/mmap。命令如下# 创建挂载点并挂载2MB页面的hugetlbfs mkdir -p /hugepages mount -t hugetlbfs -o pagesize2M,uid1000,gid1000,mode0770 none /hugepages # 写进fstab开机自动挂载 echo none /hugepages hugetlbfs pagesize2M,uid1000,gid1000,mode0770 0 0 /etc/fstab # 验证 mount | grep hugetlbfs挂载参数里uid/gid/mode决定哪些用户能在这个文件系统里创建文件。建议不要用mode0777给一个专用组就够了。对嵌入式Linux场景如果平台内存本身只有1GB更要克制权限范围避免任意进程从这里吃掉大页。提示挂载点的文件大小对应实际物理大页创建文件后内存就被占住了。用完记得删除文件或unlink否则池子不会自动释放。3.4 参数速查表参数作用常见坑vm.nr_hugepages静态大页池总页数设太大导致物理内存耗尽vm.nr_overcommit_hugepages允许超额预借的大页数生产环境不要开大否则段建出来但页不够用vm.hugetlb_shm_group允许用SHM_HUGETLB的用户组ID用户不在这组数据库必然报共享内存错误kernel.shmmax单个共享内存段上限字节小于数据库SGA直接启动失败kernel.shmall共享内存总页数上限4KB页只顾shmmax忽略它同样失败4. 应用真正吃到大页Oracle/PostgreSQL/Redis三套配置法4.1 Oracleuse_large_pagesONLY 与 lock_sgaOracle是HugePages最典型的使用者SGA本身就是一大块共享内存。在vm.hugetlb_shm_group配置正确、池子大小覆盖SGA的前提下进入SQL*Plus执行ALTER SYSTEM SET sga_target8G SCOPESPFILE; ALTER SYSTEM SET memory_target0 SCOPESPFILE; ALTER SYSTEM SET use_large_pagesONLY SCOPESPFILE; ALTER SYSTEM SET lock_sgaTRUE SCOPESPFILE;use_large_pagesONLY是我特别推荐的值。设为TRUE或FALSE时Oracle会尝试用大页不行就退回4KB设为ONLY则强制只用大页池子不够直接启动失败。看起来不够“智能”但正是这种明确失败能让你立刻发现预算不足而不是默默回到小页性能问题被掩盖。lock_sgaTRUE的作用是把SGA锁进物理内存避免被换出。重启数据库后看alert日志里有没有出现Large Pages Information这一段重点核对Total Shared Global Region in Large Pages是否等于SGA总量。4.2 PostgreSQLshared_memory_typehuge_pagePostgreSQL从9.4之后支持直接使用HugePages核心参数就两个# postgresql.conf shared_memory_type huge_page huge_pages onshared_memory_typehuge_page让PostgreSQL通过shmget配合SHM_HUGETLB创建共享内存段huge_pageson是强制调用。如果配了on却启动失败常见报错是could not create shared memory segment九成原因是运行postgres的操作系统用户不在vm.hugetlb_shm_group指定的组里或者kernel.shmmax比shared_buffers规划值小。调试阶段可以临时把huge_pages改成try这样系统会在拿不到大页时自动退回普通共享内存让实例起来。确认能正常启动后再改回on避免生产环境悄悄降级。4.3 Redis这个场景多半是THP在捣乱把Redis和HugePages放一起很容易陷入误区。Redis的内存来自malloc分配器默认不直接消费静态大页池它受影响的是Linux的透明大页THP。很多服务器发行版默认把THP设为alwaysRedis在运行时内存会被塌缩成2MB大块并页分裂时带来明显延迟抖动。对延迟敏感的业务第一件事往往不是建HugePages池而是关掉THP# 查看当前THP策略 cat /sys/kernel/mm/transparent_hugepage/enabled # 对延迟敏感的服务建议改为never echo never /sys/kernel/mm/transparent_hugepage/enabledTHP与本文配的静态HugePages是两套机制THP是内核自动把匿名页合并成大页静态池是手动预分配、应用显式申请。如果Redis确实要手动吃大页需要让分配器走mmap(MAP_HUGETLB)或从hugetlbfs映射改动成本高且池子耗尽时会直接ENOMEM。我一般的判断标准是Redis场景优先处理THP不要为了“用上HugePages”这个词而强行往池子里塞。4.4 自研服务用mmap手动申请HugePages如果你的程序是自己写的不想依赖数据库中间件标准做法是在mmap调用里加MAP_HUGETLB#include sys/mman.h #include stdio.h #include string.h #include unistd.h int main(void) { size_t len 2 * 1024 * 1024; // 刚好一个2MB页 void *p mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0); if (p MAP_FAILED) { perror(mmap hugepage); return 1; } // 写入首尾页触发真实分配 memset(p, 0xAB, len); munmap(p, len); return 0; }编译和运行验证gcc -o hugedemo hugedemo.c ./hugedemo这段代码先在hugetlbfs上借大页随memset写入才真正从池子里扣页。如果你的程序里已有自己的内存池把池子的初始映射换成这种方式最干净不用改业务逻辑。要注意MAP_HUGETLB依赖静态大页池池满时mmap直接返回ENOMEM所以应用层要捕获这个错误并给出降级路径。5. HugePages配置避坑五个我踩过的“血泪经验”5.1 设置了nr_hugepagesHugePages_Total却涨不上去现象执行sysctl -w vm.nr_hugepages4352后grep HugePages_Total /proc/meminfo显示的数值远小于目标甚至为0。原因内核收到目标值后要一次性预留对应数量的2MB连续物理页。物理内存碎片严重时凑不出那么多连续块。常见诱因是页缓存占用太多、刚有进程退出导致内存不连续或虚拟机/嵌入式平台本身内存局促。解决先执行sync echo 3 /proc/sys/vm/drop_caches释放页缓存再看/proc/buddyinfo确认高order空闲块数量。如果一次分配不上来可以分几次调低目标值逐步增加先设2048稳定后再提到4352。对嵌入式Linux如果只有1GB内存且还要跑业务2MB连续页很难凑需要考虑改用1GB页前提是CPU和内核支持或接受较小池子。5.2 池子有富余数据库却报shmget No space left on device现象/proc/meminfo里HugePages_Free还剩几百页Oracle启动时报shmget: No space left on device或PostgreSQL报could not create shared memory segment。原因大页池有页但进程没有权限创建“使用大页的共享内存段”。要么是运行用户不在vm.hugetlb_shm_group指定的组里要么是kernel.shmmax比应用要求的段小内核在权限层就把请求拒了。解决先确认用户组id oracle看gid是否与cat /proc/sys/vm/hugetlb_shm_group一致。不一致就usermod -aG hugetlb_shm oracle并重新登录会话。然后核对kernel.shmmax是否大于SGA或shared_buffers的目标值别忘了kernel.shmall同样要够。5.3 free显示内存足够但大页池始终分配不出来现象free -h显示还有十几GB可用但设置nr_hugepages时HugePages_Total就是涨不动。原因free显示的可用内存包含可回收页缓存和可换出的匿名页而HugePages池要的是“现在就能腾出来且连续”的物理内存。另一个隐藏因素是cgroup的memory.max限制容器或嵌入式环境里进程组能锁定的内存上限挡住预留申请。解决在宿主机上先echo 3 /proc/sys/vm/drop_caches如果跑在容器里确认cgroupmemory.max没有把进程能锁的额度掐死。还可以检查ulimit -l数据库进程要锁大页locked memory限制也必须放行通常设为unlimited。5.4 透明大页THP与静态大页混在一起性能反而更怪现象静态池配置全部正常数据库也吃到了大页但业务延迟曲线仍然毛刺不断/proc/meminfo里AnonHugePages数值很大。原因THP和静态HugePages是两条独立路径。应用程序的普通匿名内存可能被THP自动合并成2MB页这部分内存不受你控制可能在运行时分裂回4KB造成瞬时卡顿。和静态池的作用互相干扰。解决对数据库、Redis这类延迟敏感业务建议把THP设为never和静态池场景保持一致。改动命令是echo never /sys/kernel/mm/transparent_hugepage/enabled需要固化就写进/etc/rc.local或对应的sysfs配置。如果业务确实想享受透明合并至少要设为madvise只对显式声明madvise的段生效。5.5 重启后配置一切归零或NUMA节点上分配不均现象sysctl -p后一切正常重启服务器HugePages_Total又变回0或只分配到一部分页。原因只写了/etc/sysctl.conf但没有在引导早期预留。某些内核版本和发行版组合里sysctl生效时内存已经碎片化预留失败就静默放弃。多路服务器上还有NUMA问题默认页可能全部落在node0导致node0内存吃紧、其他节点大页空闲。解决把hugepagesz2M hugepages4352加进GRUB_CMDLINE_LINUX并重新生成引导配置让内核在初始化早期预留。NUMA场景下预留后还要去/sys/devices/system/node/node0/hugepages/hugepages-2048kB/核对各节点页数必要时用numactl --interleave或用nr_hugepages_mempolicy按节点指定。6. 配置后的验证技巧用三条命令确认HugePages真被消费掉配置完成不等于应用吃到了。我最常做的验证是“启动前后对比法”启动数据库之前记一张/proc/meminfo快照启动之后再记一张盯住HugePages_Free和HugePages_Rsvd# 启动前 grep -E HugePages_Free|HugePages_Rsvd /proc/meminfo /tmp/huge_before.txt # 启动应用后 grep -E HugePages_Free|HugePages_Rsvd /proc/meminfo /tmp/huge_after.txt # 对比差Free下降说明大页被真正划走了 cat /tmp/huge_before.txt /tmp/huge_after.txt如果Free纹丝不动说明应用实际还在走4KB页回去查组和shmmax。第二条是看进程内部映射# 对目标进程PID统计smaps里的页大小分布 grep -E KernelPageSize|MMUPageSize /proc/PID/smaps | sort | uniq -c看到大量2048 kB字段说明进程内存段里确实有2MB页在承载看到全是4kB就说明还没吃上。第三条是系统调用级验证适合判断某些封装比较复杂、看不透的应用# 跟踪shmget调用确认标志位里带SHM_HUGETLB strace -f -e traceshmget su - postgres -c /usr/pgsql-13/bin/postgres -D /data 21 | grep -i huge我现在的习惯是新环境配置HugePages一定是先临时态验证、再固化、再用启动前后快照确认三段走完。如果连续两次都确认不了Free下降不要再硬堆参数先回应用层查内存模型。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑