HugePages_Rsvd 是什么意思?


HugePages 使用率?

一天,有同事问,怎么看 HugePages 的使用率。那么问题来了,HugePages 使用率是指什么?这个答案因人而异。

在 /proc/meminfo 里,看到以下四个关于 HugePages 的数值:

HugePages_Total: vvv
HugePages_Free:  www
HugePages_Rsvd:  xxx
HugePages_Surp:  yyy
Hugepagesize:    zzz kB

从 Kernel Doc (vm/hugetlbpage.txt) 可以知道, HugePages_Total 是指系统总共预留了多少 HugePages. HugePages_Free 指当前还有多少 HugePages 未分配 (allocate),HugePages_Rsvd 是指有多少 HugePages 是系统承诺了会分配给程序(commitment to allocate),但实际并未分配。HugePages_Surp 指超分的页。

所以会有个问题, HugePages_Free 是否包含了 HugePages_Rsvd? 从文档的表述,应该是包含了。可以通过 Kernel doc 自带的示例程序来验证。

测试:用 mmap 申请 Hugepages

代码改编自 Kernel Doc (vm/hugetlbpage.txt)。这个示例程序会申请 128 个 HugePages, 然后会读取第一个 HugePage 的第一个字节,随后会往这 128 个 HugePages 里填满内容。

/*
 * Example of using huge page memory in a user application using the mmap
 * system call.  Before running this application, make sure that the
 * administrator has mounted the hugetlbfs filesystem (on some directory
 * like /mnt) using the command mount -t hugetlbfs nodev /mnt. In this
 * example, the app is requesting memory of size 256MB that is backed by
 * huge pages.
 *
 * For the ia64 architecture, the Linux kernel reserves Region number 4 for
 * huge pages.  That means that if one requires a fixed address, a huge page
 * aligned address starting with 0x800000... will be required.  If a fixed
 * address is not required, the kernel will select an address in the proper
 * range.
 * Other architectures, such as ppc64, i386 or x86_64 are not so constrained.
 */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>

#define FILE_NAME "/mnt/hugepagefile"
#define LENGTH (256UL*1024*1024)
#define PROTECTION (PROT_READ | PROT_WRITE)

#define ADDR (void *)(0x0UL)	/* let kernel choose address */
#define FLAGS (MAP_SHARED)

void check_bytes(char *addr)
{
	printf("First hex is %x\n", *((unsigned int *)addr));
}

void write_bytes(char *addr)
{
	unsigned long i;

	for (i = 0; i < LENGTH; i++)
		*(addr + i) = (char)i;
}

void read_bytes(char *addr)
{
	unsigned long i;

	check_bytes(addr);
	for (i = 0; i < LENGTH; i++)
		if (*(addr + i) != (char)i) {
			printf("Mismatch at %lu\n", i);
			break;
		}
}

int main(void)
{
	void *addr;
	int fd;

	fd = open(FILE_NAME, O_CREAT | O_RDWR, 0755);
	if (fd < 0) {
		perror("Open failed");
		exit(1);
	}

	addr = mmap(ADDR, LENGTH, PROTECTION, FLAGS, fd, 0);
	if (addr == MAP_FAILED) {
		perror("mmap");
		unlink(FILE_NAME);
		exit(1);
	}

	printf("Returned address is %p\n", addr);
	getchar();  // 申请好HugePages后,暂停。
	check_bytes(addr); // 获取申请到的HugePages的第一个字节的内容。
	getchar();  // 暂停
	write_bytes(addr); // 往内存里写数据
	getchar();  // 暂停
	read_bytes(addr);

	munmap(addr, LENGTH);
	close(fd);
	unlink(FILE_NAME);

	return 0;
}

在运行上面的程序之前,需要先让系统预留好 HugePages, 并且挂载 hugetlbfs。

在 /etc/sysctl.conf 添加

vm.nr_hugepages = 130

执行:

# sysctl -p
# mount -t hugetlbfs nodev /mnt

从 /proc/meminfo 可以看到,系统已经预留好了 130 个 HugePages.

HugePages_Total:     130
HugePages_Free:      130
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB

运行示例程序:

# ./hugetest 
Returned address is 0x2aaaaac00000

查看 meminfo,可以看到 HugePages_Rsvd 变成了 128,也就是程序所申请的数量,而 HugePages_Free 仍然是 130.

HugePages_Total:     130
HugePages_Free:      130
HugePages_Rsvd:      128
HugePages_Surp:        0

继续执行程序,程序会检查第一个HugePages的内容。

# ./hugetest 
Returned address is 0x2aaaaac00000

First hex is 0

从 meminfo 可以看到, HugePages_Free 和 HugePages_Rsvd 都减少了1。这是因为程序读取第一个页的时候,系统为这个页真正地分配空间(allocate)了。

HugePages_Total:     130
HugePages_Free:      129
HugePages_Rsvd:      127
HugePages_Surp:        0

继续执行程序,程序会往所申请到的128个页中写满内容。从 meminfo 可以看到,HugePages_Rsvd 已经变为0了,因为程序所申请的所有HugePages这个时候都真正获得分配了。

HugePages_Total:     130
HugePages_Free:        2
HugePages_Rsvd:        0
HugePages_Surp:        0

所以,关于 HugePages 使用率这个问题,如果我们希望知道的是系统实际分配给了程序多少个 HugePages, 我们可以直接通过 HugePages_Total - HugePages_Free 计算得到。HugePages_Rsvd 只是承诺程序的数量,并未真正使用。