服务器入坑指北:大页与页表

举报
Tulpartner 发表于 2026/11/12 09:00:10 2026/11/12
【摘要】 内存都用上了、却不知道页表本身吃多少;听人说开大页能提速,但不知道能省多少;同一份读数里数字对不上。 在你动手去读之前,先把三条前提看清:① 同一份 /proc/meminfo 里两种口径并存——HugePages_* 是页数(没有单位后缀),而 Hugepagesize / Hugetlb / PageTables 是 kB;② HugePages_Total 为 0 就是"没有配大页池"——此

内存都用上了、却不知道页表本身吃多少;听人说开大页能提速,但不知道能省多少;同一份读数里数字对不上。
在你动手去读之前,先把三条前提看清:① 同一份 /proc/meminfo 里两种口径并存——HugePages_* 是页数(没有单位后缀),而 Hugepagesize / Hugetlb / PageTables 是 kB;② HugePages_Total 为 0 就是"没有配大页池"——此时谈"往大页里放东西"或去挂载 hugetlbfs 都没有意义;③ 挂载 hugetlbfs 需要内核启用 HugeTLB。
这篇给三段可直接粘贴的内容:读两类量 → 算两式 → 改与读回(最后一段配池与改策略是写操作)。取值用占位符,字段名、命令名与参数名照写,并且每个数字都连着它的单位一起写。

页表为什么会长

页表规模与"用了多少内存"成正比、与"页多大"成反比——页越大,项数越少、页表本身越小。
判据:PageTables 那一行给的就是页表自身实占多少内存(kB 口径)——它随常驻内存一起长;想知道"大页能省多少",就要先把项数算出来。

页表规模怎么算

页表项数 ≈ 内存总量 / 页大小——先把两个量换成同一单位再除。这里的"内存总量"用的是 MemTotal(上限口径):它给的是"最坏能有多少项";若要估真实占用,就把常驻量(VmRSS 那一类)代进同一个式子——方法不变,换的是分子。

grep -E -e '^MemTotal:' -e '^PageTables:' /proc/meminfo   # 总内存与页表实占:值自带 kB 后缀,kB=1024 字节
cat /proc/meminfo                             # 兜底:全部字段一次看齐(含 PageTables、Hugetlb)
getconf PAGE_SIZE                             # 基础页大小(常规页口径,单位字节)
grep -E -e '^Huge' /proc/meminfo              # 大页现读:HugePages_* 是页数,Hugepagesize 才是每页多少 kB

算一遍(数值为演示值):MemTotal 取 6291456 kB(6 GiB)⇒ 换成字节 6291456 * 1024 = 6442450944;常规页按 getconf PAGE_SIZE 的 4096 字节 ⇒ 页表项数 ≈ 6442450944 / 4096 = 1572864(项)。
单位不统一会差 1024 倍:把 MemTotal 的 kB 读数当字节直接用,算出来的项数只有实际的 1/1024——所以那一步换算不能省。
从"项数"落到"页表本身吃多少":每项一个页表项、按 8 字节算 ⇒ 1572864 * 8 = 12582912(字节,也就是 12 MiB)⇒ 占这块内存的 12582912 / 6442450944 ≈ 0.195%(约 0.2%)。对照就在你读的第一条命令里:同一次 grep -E -e '^MemTotal:' -e '^PageTables:' /proc/meminfo 里,PageTables 就是页表的实际占用(kB)——把 PageTables 与 MemTotal 相除,就能拿到你机器上真实的那个百分比(我本机现读是 0.42% 量级,与上面按项数估出来的量级可比)。

大页把项数降下来多少

换成大页,项数按"页大小之比"降——大页省的是"页表的那一层",不是"数据的那一层"。
接着算(沿用上面那组 MemTotal,只把页大小换成大页):Hugepagesize 取 2048 kB(也就是 2 MiB)⇒ 换成字节 2048 * 1024 = 2097152;项数 ≈ 6442450944 / 2097152 = 3072(项)。
降了多少:1572864 / 3072 = 512——这就是"项数按页大小之比降"(4 KiB 常规页对 2 MiB 大页,正好是 512 倍)。注意:512 是"页数之间"的倍数关系,不是"页数读数与 kB 读数之间"的换算倍数——两者别混。
判据:它省下来的是"管理开销"——所以小内存机器上收益有限、大内存机器上才明显。

同一份读数里的两种单位

同一份文件里,两种口径相邻排列——这是本题最容易栽的地方:

grep -E -e '^Huge' /proc/meminfo              # 页数(无后缀)与 kB(有后缀)就在相邻几行里
sysctl vm.nr_hugepages                        # 大页池规模:这里同样是页数,不是 kB(版本相关,以现读为准)
sysctl vm.nr_overcommit_hugepages             # 池外可临时借的页数(版本相关,以现读为准)
cat /sys/kernel/mm/transparent_hugepage/defrag # 碎片整理策略:决定大页能否真拿到(同样是方括号多值行,方括号里才是当前值)

逐项看后缀:

  • 带 kB 的是量:MemTotal、PageTables、Hugetlb、Hugepagesize;
  • 不带后缀的是个数:HugePages_Total、HugePages_Free、HugePages_Rsvd、HugePages_Surp。
    换算时不要用一个写死的倍数:页数与 kB 之间差的是一个 Hugepagesize 的倍数(以现读为准)——Hugepagesize 是多少,倍数就是多少。
    页大小的来源也要写清:getconf PAGE_SIZE 是命令前端,而 C API 里的名字是 sysconf(_SC_PAGESIZE)(getconf _SC_PAGESIZE 不是可用写法,会报 “Unrecognized variable”;sysconf 的手册页以现读为准)。

取舍:配大页池还是靠透明大页

cat /sys/kernel/mm/transparent_hugepage/enabled   # 透明大页模式:多值并列,方括号里那个才是当前生效值

透明大页的那一行读出来长这样(方括号里的是当前生效模式):

always [madvise] never

这个读法要与"读单值"的命令区分开:sysctl vm.nr_hugepages 读的是一个数,而 transparent_hugepage/enabled 是多值并列、靠方括号指明当前值——别把整行当成"当前值"。
三条路各有代价:

  • 显式池(vm.nr_hugepages + hugetlbfs):容量预先划走、行为可预期;代价是划走的内存别的用途就用不了;
  • 透明大页:按需、无需预留;代价是行为随内核策略变、不容易精确控制(应用侧可以用 MADV_HUGEPAGE 按区间申请);
  • 都不用:省事;代价是大内存下页表开销一直在。
    判据:先问"这块内存的访问模式是不是连续大块"。程序要自己指定大页时,接口层是 mmap 的 MAP_HUGETLB(并可配合 MAP_HUGE_2MB 指定 2 MiB 尺寸)。

改与读回

先看池到底配没配——HugePages_Total 为 0 ⇒ 没配(此时去挂载 hugetlbfs 没有意义):

sysctl vm.nr_hugepages                        # 改前:记录原值(这里是页数,不是 kB)
sysctl -w vm.nr_hugepages=<页数>               # 配池(写操作:需权限,先记原值、留回滚点)
grep -E -e '^Huge' /proc/meminfo              # 读回:页数与 Hugepagesize 两个口径分别再看一次
mount -t hugetlbfs none <挂载点>               # 挂载大页文件系统(需内核启用 HugeTLB:先在 meminfo 里能看到 HugePages_Total)

池容量与使用率怎么算:大页池容量 = HugePages_Total(页数)× Hugepagesize(kB);已用页数 = HugePages_Total − HugePages_Free(同一份读数里就能拿到,不必依赖 Hugetlb 的语义),已用内存 = 已用页数 × Hugepagesize。
算一遍(这是一组新的演示值):HugePages_Total 取 640(页)× Hugepagesize 2048(kB)⇒ 池容量 = 640 * 2048 = 1310720(kB,1.25 GiB);HugePages_Free 取 400(页)⇒ 已用页数 = 640 - 400 = 240,已用内存 = 240 * 2048 = 491520(kB,480 MiB)⇒ 使用率 = 491520 / 1310720 = 0.375,也就是 37.5%。
Hugetlb 这一项我无法自证:它在有些内核上表示"池占用的内存"、在另一些上表示"已被应用用掉",语义以现读为准——核验路径:在可写 proc/sys 的机器上配一个极小的池(如 sysctl -w vm.nr_hugepages=1)后看 Hugetlb 是"一页的量"还是 0;所以我用上面的 HugePages_Free 式子做判据,Hugetlb 只作旁证。
判据:"没配池"与"配了没用"是两件事——池容量为 0 ⇒ 压根没配;池有容量而 Hugetlb 为 0 ⇒ 配了没人用——先看池容量是不是 0,再谈使用率。配池与改策略是写操作:先记原值、标回滚(sysctl -w 只改运行值,重启即失;要跨重启写进 /etc/sysctl.d/ 再用 sysctl -p 重新读取)。

现象到动作的判定表

现象 结论 动作
页表占用看着偏大 先换统一单位再算 用 MemTotal ÷ 页大小 估项数,再乘 8 字节换成内存量、与 PageTables 对照
同一份读数里数字对不上 页数对 kB 逐项看后缀:带 kB 是量、不带是个数
池容量为 0 没配大页池 先配池,再谈挂载与使用率
配了池但没用上 已用页数为 0(Total − Free) 检查应用是否真的走大页路径(MAP_HUGETLB / MADV_HUGEPAGE)
挂载不成功 可能内核没启用 HugeTLB 先在 meminfo 里确认能看到 HugePages_Total

这篇不解决什么

  • 讲zswap 配置取舍的那篇管压缩交换的配置(大页与透明大页在那里只是邻接词);本篇管页大小与页表规模的关系。判定入口:“要不要开压缩交换、怎么配”→ 那篇;“页表吃多少、大页能省多少”→ 本篇。
  • 讲内存口径打架时信谁的那篇管多个内存读数不一致时选哪个;本篇用它的结论但只管页表与大页这一路。
  • 讲内存超额承诺的取舍的那篇管分配承诺;本篇管已分配内存的页表开销。

踩坑清单

  • 把 HugePages_Total(页数)当 kB:同一份文件里两种口径,混用差一个 Hugepagesize 的倍数。
  • 写死一个"512 倍"的换算:倍数取决于 Hugepagesize(512 只是 4 KiB 对 2 MiB 的页数关系)。
  • 把 MemTotal 当字节:它是 kB,差 1024 倍。
  • 池容量为 0 还去挂载:先看 HugePages_Total 是不是 0。
  • 把 transparent_hugepage/enabled 整行当当前值:方括号里那个才是。
  • 内核没启用就谈 hugetlbfs:先在 meminfo 里确认。
  • 把"省页表"说成"省数据":省的是管理开销那一层。
  • 改池不记原值:回滚没有依据。
  • 只看项数不看实占:用同一条命令里的 PageTables ÷ MemTotal,拿到你机器上的真实比值再下结论。

改完怎么验证

  • 在机器上对照过:把 PageTables 与 MemTotal 相除,与按项数估出的量级比一比(对不上就回头看单位);
  • 两个量同单位(MemTotal 的 kB 先换成字节);
  • 已用页数用 HugePages_Total − HugePages_Free 复核过;
  • 方括号读法写了(enabled 与 defrag 都是多值行)。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。