帮你快速理解、总结文档立即下载
文档中心>TencentOS Server>操作指南>系统配置>内存大页管理(Hugepages)

内存大页管理(Hugepages)

最近更新时间:2026-08-24 18:27:31
我的收藏
Hugepages 是 Linux 内核提供的一种内存分配机制,它允许内核以更大的页面大小来分配内存,而不是默认的 4KB 等小页面。这种机制特别适用于那些需要大量连续内存空间的应用程序,比如数据库、虚拟机和某些高性能计算任务。使用 hugepages 的应用程序可以更直接地访问大块内存,而不需要内核进行多次页面映射,提高内存访问效率。
虽然 Linux 提供了 proc 等方式给用户设置大页,但是这些方式往往直接作用于系统而非具体业务本身。glibc 引入的 hugetlb 参数以进程为粒度调整 malloc 的行为,使其根据用户期望的方式从大页申请内存,在提升性能的同时,不影响系统全局的设置。

环境变量取值

hugetlb 环境变量的设置方式与取值如下:
取值
含义
设置命令
0
不使用大页
export GLIBC_TUNABLES=glibc.malloc.hugetlb=0
1
使用透明大页(THP)
export GLIBC_TUNABLES=glibc.malloc.hugetlb=1
2
使用系统默认的大页
export GLIBC_TUNABLES=glibc.malloc.hugetlb=2
x(x > 2)
指定页大小为 x(需与系统支持的页面大小匹配)
export GLIBC_TUNABLES=glibc.malloc.hugetlb=x

使用透明大页

确认透明大页的启用状态

cat /sys/kernel/mm/transparent_hugepage/enabled
输出示例(方括号表示当前模式):
always [madvise] never
三种模式含义:
模式
说明
always
总是使用透明大页
madvise
根据应用程序的建议使用透明大页
never
从不使用透明大页
如果设置了 always,则表示整个系统的策略都是优先考虑透明大页,相当于默认支持了此特性,如果可以接受此设置,则本小节可以直接跳过。如果设置了 never,则需要使用如下命令调整:
echo madvise > /sys/kernel/mm/transparent_hugepage/enabled

设置 glibc hugetlb 环境变量

export GLIBC_TUNABLES=glibc.malloc.hugetlb=1

测试验证

按照 malloc & memset 测试用例 章节的方法编译并运行测试程序,在赋值完成后查看 /proc/self/smapsAnonHugePages 字段,其值非 0 即表示该进程已使用透明大页,说明透明大页已生效。

使用普通大页

当 hugetlb 设置的值大于 1 时,glibc 支持两种使用场景:一种是使用系统默认的大页,另一种是让用户自定义大页,这里分情况介绍。

系统默认大页

确认系统默认大页的大小

实际上,glibc 也是通过读取此字段来获取这个值的:
cat /proc/meminfo | grep Hugepagesize
输出示例:
Hugepagesize: 2048 kB

设置 nr_hugepages

使用普通大页时,需要用户先指定预分配的大页数量。由上一节可知系统默认大页为 2M,因此需要设置 hugepages-2048kB 对应的 nr_hugepages
ls /sys/kernel/mm/hugepages
输出示例:
hugepages-1048576kB hugepages-2048kB
echo 520 > /sys/kernel/mm/hugepages/hugepages-2048kB

设置 glibc hugetlb 环境变量

export GLIBC_TUNABLES=glibc.malloc.hugetlb=2

测试验证

按照 malloc & memset 测试用例 章节的方法编译并运行测试程序,在赋值之后查看 /proc/self/smapsPrivate_Hugetlb 非 0 的字段,得到如下日志:
MMUPageSize: 2048 kB
Private_Hugetlb: 1050624 kB
...
MMUPageSize: 2048 kB
Private_Hugetlb: 2048 kB
其中 MMUPageSize(Memory Management Unit Page Size,内存管理单元页大小)为 2048 kB(2M),符合 设置 nr_hugepages 一节中的设置;Private_Hugetlb 有两块,1050624kB 和 2048kB:
产生第一个 1050624kB 的原因是 malloc 通过 chunk 管理内存,除了提供 1G 内存之外,还需要预留 chunk 头来管理这块内存,由于此时是 2M 对齐,因此多出了这部分。
产生第二个 2048kB 的原因是 malloc 在初次调用时会给 tcache 的数据结构申请空间,又由于 2M 对齐,导致多出了这部分。
因此,这里预留了 520 个 2M 的大页(共计 1040M)用于验证 1G 场景 malloc & memset;仅预留 1G 并不够,因为除了 1G 内存本身,还需要为 chunk 头和 tcache 额外预留 2M 对齐的空间。

自定义大页

确认系统支持的大页规格

例如,当前的环境支持 1G 和 2M 两种规格,默认是 2M 已经验证过,本小节选择 1G 的大页:
ls /sys/kernel/mm/hugepages
输出示例:
hugepages-1048576kB hugepages-2048kB

设置 nr_hugepages

同上一节,需要预留大页:
echo 3 > /sys/kernel/mm/hugepages/hugepages-1048576kB
这里预留了 3 个 1G 大页用于验证 1G 场景 malloc & memset

设置 glibc hugetlb 环境变量

警告:需要注意,这里的单位是 B(字节),因此需要手动换算。
export GLIBC_TUNABLES=glibc.malloc.hugetlb=1073741824

测试验证

按照 malloc & memset 测试用例 章节的方法编译并运行测试程序,在赋值之后查看 /proc/self/smapsPrivate_Hugetlb 非 0 的字段,得到如下日志:
MMUPageSize: 1048576 kB
Private_Hugetlb: 2097152 kB
...
MMUPageSize: 1048576 kB
Private_Hugetlb: 1048576 kB
其中 MMUPageSize(Memory Management Unit Page Size,内存管理单元页大小)为 1048576 kB(1G),符合 设置 nr_hugepages 一节中的设置;Private_Hugetlb 有两块,2097152kB 和 1048576kB:
产生第一个 2097152kB 的原因仍是预留 chunk 数据结构加页对齐。
产生第二个 1048576kB 的原因仍是 tcache。
需要解释的是,多出的 1048576kB 不是就不用了,后续如果有小块内存申请,malloc 还是会优先考虑从这里切内存出去。但是从上例也可以看出大页的弊端,就是使用不当会造成极大的内存浪费,需要开发人员认真规划内存的使用。

malloc & memset 测试用例

以下测试程序可用于验证透明大页和普通大页是否生效。程序分配 1GB 内存并进行初始化,随后通过查看进程的 /proc/<PID>/smaps 输出,判断大页使用情况。

测试程序

创建测试程序 test_hugepage.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define MEM_SIZE (1UL << 30) /* 1GB */

int main()
{
char *p = malloc(MEM_SIZE);
if (!p) {
perror("malloc failed");
return 1;
}

printf("PID: %d\\n", getpid());

/* 初始化内存,触发大页分配 */
memset(p, 0, MEM_SIZE);

printf("已分配并初始化 %lu MB 内存,按 Enter 键退出...\\n", MEM_SIZE >> 20);
getchar();

free(p);
return 0;
}

编译并运行

gcc -o test_hugepage test_hugepage.c
./test_hugepage
程序运行后会打印进程 PID 并等待输入,此时在另一个终端执行以下命令查看大页使用情况:
cat /proc/<PID>/smaps | grep -E "AnonHugePages|Private_Hugetlb|MMUPageSize"

验证方法

根据配置的大页类型,查看 smaps 中对应字段是否为非零值:
大页类型
smaps 字段
非零含义
透明大页(hugetlb=1)
AnonHugePages
该进程已使用透明大页
普通大页(hugetlb=2 或自定义值)
Private_Hugetlb
该进程已使用普通大页
说明:
对于透明大页,AnonHugePages 值非 0 表示透明大页已生效;对于普通大页,Private_Hugetlb 值非 0 且 MMUPageSize 与配置的大页规格一致,表示普通大页已生效。

原理介绍

本特性涉及的原理如下:
0f982c1827 malloc: Enable huge page support on main arena
0849eed45d malloc: Move MORECORE fallback mmap to sysmalloc_mmap_fallback
c1beb51d08 malloc: Add Huge Page support to arenas
98d5fcb8d0 malloc: Add Huge Page support for mmap
6cc3ccc67e malloc: Move mmap logic to its own function
7478c9959a malloc: Add THP/madvise support for sbrk
5f6d8d97c6 malloc: Add madvise support for Transparent Huge Pages

变量解析流程

在首次 malloc 时,会调用解析 glibc hugetlb 环境变量的函数,代码如下,value 即为用户指定的值:
static __always_inline int
do_set_hugetlb (size_t value)
{
if (value == 1)
{
enum malloc_thp_mode_t thp_mode = __malloc_thp_mode ();
/* Only enable THP madvise usage if system does support it and
has 'madvise' mode. Otherwise the madvise() call is wasteful. */
if (thp_mode == malloc_thp_mode_madvise)
mp_.thp_pagesize = __malloc_default_thp_pagesize ();
}
else if (value >= 2)
__malloc_hugepage_config (value == 2 ? 0 : value,
&mp_.hp_pagesize, &mp_.hp_flags);
return 0;
}
value = 1(透明大页):当 value 为 1 时,通过 __malloc_default_thp_pagesize 函数读取 /sys/kernel/mm/transparent_hugepage/hpage_pmd_size 设置 thp_pagesize 的值:
unsigned long int
__malloc_default_thp_pagesize (void)
{
int fd = __open64_nocancel (
"/sys/kernel/mm/transparent_hugepage/hpage_pmd_size", O_RDONLY);
if (fd == -1)
return 0;

char str[INT_BUFSIZE_BOUND (unsigned long int)];
ssize_t s = __read_nocancel (fd, str, sizeof (str));
__close_nocancel (fd);
if (s == 0)
return 0;

unsigned long int r = 0;
for (ssize_t i = 0; i < s; i++)
{
if (str[i] == '\\n')
break;
r *= 10;
r += str[i] - '0';
}
return r;
}
value = 2(系统默认大页):当 value 为 2 时,通过 malloc_default_hugepage_size 获取系统默认的大页大小。
value > 2(自定义大页):当 value 大于 2 时,读取 /sys/kernel/mm/hugepage 判断输入的 value 是否为系统支持的大页大小:
void
__malloc_hugepage_config (size_t requested, size_t *pagesize, int *flags)
{
*pagesize = 0;
*flags = 0;

if (requested == 0)
{
*pagesize = malloc_default_hugepage_size ();
if (*pagesize != 0)
*flags = hugepage_flags (*pagesize);
return;
}

/* Each entry represents a supported huge page in the form of:
hugepages-<size>kB. */
int dirfd = __open64_nocancel ("/sys/kernel/mm/hugepages",
O_RDONLY | O_DIRECTORY, 0);
if (dirfd == -1)
return;

char buffer[1024];
while (true)
{
...
hpsize *= 1024;

if (hpsize == requested)
{
*pagesize = hpsize;
*flags = hugepage_flags (*pagesize);
__close_nocancel (dirfd);
return;
}
}

__close_nocancel (dirfd);
}
其中 hugepage_flags 实现如下,flags 最终会传递给 mmap,从而指定传统大页的大小:
static inline int
hugepage_flags (size_t pagesize)
{
return MAP_HUGETLB | (__builtin_ctzll (pagesize) << MAP_HUGE_SHIFT);
}

madvise 封装

对于使用透明大页的场景,当 /sys/kernel/mm/transparent_hugepage/enabled 设置为 madvise 时,需要用户态通过 madvise 系统调用告诉内核指定虚拟内存的使用模式,glibc 对 madvise 进行了如下封装:
static inline void
madvise_thp (void *p, INTERNAL_SIZE_T size)
{
# ifdef MADV_HUGEPAGE
/* Do not consider areas smaller than a huge page or if the tunable
is not active. */
if (mp_.thp_pagesize == 0 || size < mp_.thp_pagesize)
return;

...

__madvise (p, size, MADV_HUGEPAGE);
# endif
}
其中 thp_pagesize 在初次调用 malloc 时初始化为系统默认的透明大页大小。glibc 通过在每次 mmap 后调用 madvise_thp 即可实现使用透明大页。

总结

内存大页可以提高内存访问效率,提升性能,但是也会带来内存浪费等问题。glibc 的 hugetlb 环境变量通过更为精细的粒度,在保留大页高效使用内存的同时,在合理规划内存的情况下,可以避免大页过多消耗内存的问题。