持续提升虚拟机性能的一个有效途径是使用大页内存。本稿从内存映射的原理出发,阐述大页内存的概念与基础操作,并分享在大页内存动态分配方面的探索与实践。
MMU 与内存映射
在计算机体系结构中,CPU 通过寻址访问内存。以 32 位 CPU 为例,寻址宽度为 0~0xFFFFFFFF,理论上可支持的物理内存为 4G。但在实际场景中,若某个程序需要 4G 内存,而可用物理内存不足,程序就会受到限制。为解决此类问题,现代 CPU 引入了 MMU(Memory Management Unit,内存管理单元)。MMU 的核心思想是通过虚拟地址进行寻址,操作系统再将虚拟地址映射到物理地址,从而突破物理内存的局限。
内存映射则是将虚拟地址映射到物理地址的过程。内核为每个进程维护页表,记录虚拟地址与物理地址之间的映射关系。页表信息通常由 MMU 在硬件层面实现,CPU 可以直接通过硬件找到要访问的内存位置。
什么是 HugePages
大页内存(HugePages)指的是比标准 4K 页面更大的内存页,提供了更大的页面单位,在某些场景下可以显著提升性能。
HugePages 的查看与分配
在 Linux 系统中,可以通过以下命令查看大页内存信息:
常用字段解释如下:
AnonHugePages:透明大页的大小
HugePages_TOTAl:大页资源池中的大页总量
HugePages_free:池中未使用的大页数量
HugePages_Rsvd:已分配并预留但尚未使用的大页数量
HugePages_SuRp:池中超出 /proc/sys/vm/nr_hugepages 的 HugePages 数量
HugePagesize:单个大页的大小
通常使用 2M 的大页,即 HugePagesize = 2048 KB。
需要注意的是,这里的单位以页为单位计算,而不是以字节为单位。例如,HugePages_TOTAl 为 100 时,机器上会分配 100 页大页,对应内存总量为 100 × 2048 KB = 200 MB。
那么如何分配 HugePages 呢?
分配方式主要有两种:
1) 修改 /etc/grub2.cfg(或相关启动配置)中的内核参数,使得系统启动时更容易获得连续的内存区域用于 HugePages 分配;
2) 使用 sysctl vm.nr_hugepages 的命令进行临时分配。该方式灵活,但在系统内存被大量使用时,可能会导致可用的连续物理内存减少,从而无法分配新的 HugePages。
OpenStack 场景中的挑战
在 OpenStack 环境中,libvirt 的驱动需要先检查用户是否在 flavor 配置中开启了大页相关选项,并直接从已分配的资源池中为虚拟机划分资源。这就带来一个问题:大页资源池需要用户提前分配,预先分配的资源会占用内存空间,随着资源池中可用大页数量减少,后续创建大页内存虚拟机的能力也会下降。
简而言之,OpenStack 本身并没有提供统一的机制来管理和维护大页资源池。
ARStack 的尝试
如何在尽量多创建高性能大页虚拟机的同时,避免云平台初期大页资源池的过度占用呢?基于此,团队设计了一套动态维护大页资源池的方案。
系统初始化时,允许以较低的初始值设置大页资源池。接着,NOVa 的周期性任务会定期检查各计算节点的大页资源是否符合预设策略。当初始资源池耗尽时,NOVa 会自动扩容以维持较为充裕的资源状态。与此同时,NOVa-Scheduler 在创建大页虚拟机时也会评估各节点的资源情况,若资源不足,将尝试计算缺口并进行扩容。
当大页虚拟机被删除后,相应的大页会被释放回资源池。若某计算节点的资源池存在过多未分配的大页,NOVa 还会进行收缩以释放物理内存,降低整体内存占用。
基于这套动态分配方案,最终实现了性能与灵活性的双重目标。
后记
OpenStack 的实现往往涉及多方协同,NOVa-Scheduler、NUMA 以及 ARStack 的内存预分配都针对 HugePages 场景进行了调优与改进。感谢团队成员的辛勤付出。未来,我们将继续完善相关方案,力求为用户提供更高性能的私有云体验。
