Linux之HugePage的原理与使用
- 虚拟地址与物理地址
- 虚拟地址
- 物理地址
- 虚拟地址与物理地址的转换
- HugePage的概念
- Linux使用HugePage
- 创建HugePage
- 在程序中使用HugePage
- 总结
虚拟地址与物理地址
在研究HugePage之前,首先需要明白虚拟地址和物理地址的概念。在计算机系统中,虚拟地址和物理地址是两个重要的概念。
虚拟地址
虚拟地址是由操作系统为每个进程分配的地址空间。每个进程都认为自己拥有独立的、连续的地址空间,从地址零开始。
- 提供了一种内存保护机制。不同的进程有各自独立的虚拟地址空间,一个进程不能直接访问另一个进程的内存,从而防止了进程之间的非法访问和干扰。
- 实现了多任务环境下的内存管理。操作系统可以在物理内存不足时,将部分暂时不用的页面存储到磁盘上,当需要时再重新加载到内存中,使得多个进程可以在有限的物理内存下同时运行。
- 方便程序的编写和移植。程序员可以在不考虑实际物理内存布局的情况下进行编程,提高了开发效率。而且,由于虚拟地址空间的独立性,程序可以在不同的硬件平台上更容易地进行移植。
物理地址
物理地址是计算机内存中实际的存储单元地址。
- 物理地址直接对应着内存中的实际存储位置,是数据真正存储的地方。
- 硬件设备(如 CPU、内存控制器等)通过物理地址来访问内存中的数据。
虚拟地址与物理地址的转换
为了让进程能够正确地访问内存中的数据,操作系统需要将虚拟地址转换为物理地址。这个转换过程通常由硬件(如内存管理单元 MMU)和操作系统共同完成,并且需要MTT(内存转换表,Memory Translate Table)以及MPT(内存保护表,Memory Protect Table)。
- 当进程访问一个虚拟地址时,MMU 会根据页表等数据结构将虚拟地址转换为物理地址。
- 如果转换过程中发现所需的页面不在物理内存中,操作系统会触发页面错误,然后将所需的页面从磁盘加载到物理内存中,并更新页表,再次进行地址转换。
总之,虚拟地址和物理地址在计算机系统中起着不同的作用,虚拟地址为进程提供了独立的地址空间和内存保护,而物理地址则是实际存储数据的位置。通过虚拟地址到物理地址的转换,操作系统实现了高效的内存管理和多任务环境下的程序运行。
例如下图的转换所示:
HugePage的概念
Linux 会以页为单位管理内存,而默认的页面大小为 4KB,虽然部分处理器会使用 8KB、16KB 或者 64KB 作为默认的页面大小,不过 4KB 仍然是操作系统的默认页面配置的主流。这样一来,一个程序可能会使用很多的内存页面,而每个页面都需要相应的MTT(Memory Translate Table)和MPT(Memory Protect Table)来进行虚拟地址(VA)到物理地址(PA)的转换,当一个程序需要大量的内存时,由于每个页面都很小,就需要很多的MTT和MPT,从而会增加系统的开销,影响程序的性能。
为此,Linux中应用了大页(HugePage)的概念,也就是使用比较大的内存页面,2MB 一般都是 HugePages 的默认大小,在 arm64 和 x86_64 的架构上甚至支持 1GB 的大页面,是 Linux 默认页面大小的 262144 倍。这样,当一个应用程序需要大量的内存时,就可以只是使用非常少的内存页面就能够满足程序的需求,可以大大降低所需要的MTT和MPT,提升性能。
可以通过如下所示的命令查看有关HugePage的信息:
cat /proc/meminfo | grep Huge
例如如下的结果:
上图的结果表示,HugePage的大小为2048KB也就是2MB,现有的HugePage有两个,并且它们都没有被使用。
Linux使用HugePage
创建HugePage
首先需要在linux系统中创建HugePage,然后才可以去使用,创建并加载HugePage的命令如下所示,这里使用的时默认的大页(HugePage)大小,2MB:
- 创建一定数量的HugePage
sudo sysctl -w vm.nr_hugepages=<HugePage 数量>
- 挂载 HugePages 文件系统
sudo mkdir /mnt/huge
sudo mount -t hugetlbfs none /mnt/huge
- 检查 HugePages 是否被正确使用
cat /proc/meminfo | grep Huge
也可以使用如下所示的命令查看已经挂载了的HugePage:
mount | grep hugetlbfs
其运行结果为:
可以看出,我创建并且挂载了两个大页。
在程序中使用HugePage
在linux中一般使用 mmap 或 shmget 来分配和使用大页需要在程序中明确地调用这些函数,并指定所需的标志和内存大小。
例如下面的示例代码,我们使用了等待用户输入字符这种方法,或者死循环的方法也可以,以便于使得程序运行很长的时间,从而我们就可以通过命令行来检验程序是否使用了大页:
hp.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main() {
size_t hugepage_size = 2 * 1024 * 1024; // 大页大小,2MB
int fd = open("/mnt/huge/none", O_CREAT | O_RDWR, 0755); // 打开大页文件系统路径
if (fd < 0) {
perror("Failed to open hugetlbfs");
return 1;
}
// 使用 mmap 分配大页
void *addr = mmap(NULL, hugepage_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS | MAP_HUGETLB, fd, 0);
if (addr == MAP_FAILED) {
perror("mmap failed");
return 1;
}
// 向大页写入数据
strcpy((char *)addr, "Hello, Huge Page!");
printf("Huge page allocated at: %p\n", addr);
printf("Data in huge page: %s\n", (char *)addr); // 读取并打印数据
// 程序运行,等待输入以退出
printf("Press Enter to exit...");
getchar();
// 使用完毕后,记得释放内存
munmap(addr, hugepage_size);
close(fd);
return 0;
}
对代码进行编译:
gcc -o hpt hp.c
运行程序:
sudo ./hpt
为了验证我们的程序是否使用了指定的大页,我们首先查看程序的进程:
ps aux | grep hpt
命令行运行之后的结果为:
这里需要注意,真正使用了大页的是中间那行所表示的进程号,这是由于第一行的进程号是原始的c程序的进程,在其中我们开启了一个调用大页,所以会重新开启一个进程,最后那一行的进程可以不用管,与我们的大页示例程序无关,如果非要去深究一下,那么,它实际上表示的是对应的那个大页分配给了我们的这个程序。
这里记录一下程序运行的pid
,我的就是169136
,后续在使用的时候需要替换为你自己的pid。
然后我们再次运行下面的命令:
cat /proc/meminfo | grep Huge
结果为:
可以看出来,空闲的HugePage只有一个,实际上非空闲的那个HugePage正在被我们的程序使用。
在前面我们已经得到了程序的pid
,现在就使用这个pid直接去查看程序所使用的页面,命令行如下所示:
sudo cat /proc/<pid>/smaps | grep -i Private_Hugetlb
例如我的命令就是:
sudo cat /proc/169136/smaps | grep -i Private_Hugetlb
得到的结果为:
可以看出,的确使用了一个大小为2048KB也就是2MB的大页HugePage。
至此,我们成功使用了大页并对其进行了验证,可以关闭之前的那个程序了。
总结
本文介绍了虚拟地址、物理地址、地址转换、大页(HugePage)等概念,并在linux中创建大页,然后还使用c语言编写程序真实地使用了HugePage并且对其使用情况进行了检验和分析。希望本文对大家有所帮助和启发。