关于linux内核模块:Linux-HugePages大内存页-原理与使用

42次阅读

共计 3044 个字符,预计需要花费 8 分钟才能阅读完成。

在介绍 HugePages 之前,咱们先来回顾一下 Linux 下 虚拟内存 物理内存 之间的关系。

  • 物理内存:也就是装置在计算机中的内存条,比方装置了 2GB 大小的内存条,那么物理内存地址的范畴就是 0 ~ 2GB。
  • 虚拟内存:虚构的内存地址。因为 CPU 只能应用物理内存地址,所以须要将虚拟内存地址转换为物理内存地址能力被 CPU 应用,这个转换过程由 MMU(Memory Management Unit,内存治理单元) 来实现。在 32 位的操作系统中,虚拟内存空间大小为 0 ~ 4GB。

咱们通过 图 1 来形容虚拟内存地址转换成物理内存地址的过程:

如 图 1 所示,页表 保留的是虚拟内存地址与物理内存地址的映射关系,MMU 页表 中找到虚拟内存地址所映射的物理内存地址,而后把物理内存地址提交给 CPU,这个过程与 Hash 算法类似。

内存映射是以内存页作为单位的,通常状况下,一个内存页的大小为 4KB(如图 1 所示),所以称为 分页机制

一、内存映射

咱们来看看在 64 位的 Linux 零碎中(英特尔 x64 CPU),虚拟内存地址转换成物理内存地址的过程,如图 2:

从图 2 能够看出,Linux 只应用了 64 位虚拟内存地址的前 48 位(0 ~ 47 位),并且 Linux 把这 48 位虚拟内存地址分为 5 个局部,如下:

  • PGD 索引 :39 ~ 47 位(共 9 个位),指定在 页全局目录(PGD,Page Global Directory)中的索引。
  • PUD 索引 :30 ~ 38 位(共 9 个位),指定在 页下级目录(PUD,Page Upper Directory)中的索引。
  • PMD 索引 :21 ~ 29 位(共 9 个位),指定在 页两头目录(PMD,Page Middle Directory)中的索引。
  • PTE 索引 :12 ~ 20 位(共 9 个位),指定在 页表(PT,Page Table)中的索引。
  • 偏移量:0 ~ 11 位(共 12 个位),指定在物理内存页中的偏移量。

把 图 1 中的 页表 分为 4 级: 页全局目录 页下级目录 页两头目录 页表 目标是为了缩小内存耗费(思考下为什么能够缩小内存耗费)。

留神:页全局目录、页下级目录、页两头目录 和 页表 都占用一个 4KB 大小的物理内存页,因为 64 位内存地址占用 8 个字节,所以一个 4KB 大小的物理内存页能够包容 512 个 64 位内存地址。

另外,CPU 有个名为 CR3 的寄存器,用于保留 页全局目录 的起始物理内存地址(如图 2 所示)。所以,虚拟内存地址转换成物理内存地址的过程如下:

  • CR3 寄存器中获取 页全局目录 的物理内存地址,而后以虚拟内存地址的 39 ~ 47 位作为索引,从 页全局目录 中读取到 页下级目录 的物理内存地址。
  • 以虚拟内存地址的 30 ~ 38 位作为索引,从 页下级目录 中读取到 页两头目录 的物理内存地址。
  • 以虚拟内存地址的 21 ~ 29 位作为索引,从 页两头目录 中读取到 页表 的物理内存地址。
  • 以虚拟内存地址的 12 ~ 20 位作为索引,从 页表 中读取到 物理内存页 的物理内存地址。
  • 以虚拟内存地址的 0 ~ 11 位作为 物理内存页 的偏移量,失去最终的物理内存地址。

二、HugePages 原理

下面介绍了以 4KB 的内存页作为内存映射的单位,但有些场景咱们心愿应用更大的内存页作为映射单位(如 2MB)。应用更大的内存页作为映射单位有如下益处:

  • 缩小 TLB(Translation Lookaside Buffer) 的生效状况。
  • 缩小 页表 的内存耗费。
  • 缩小 PageFault(缺页中断)的次数。

Tips:TLB 是一块高速缓存,TLB 缓存虚拟内存地址与其映射的物理内存地址。MMU 首先从 TLB 查找内存映射的关系,如果找到就不必回溯查找页表。否则,只能依据虚拟内存地址,去页表中查找其映射的物理内存地址。

因为映射的内存页越大,所须要的 页表 就越小(很容易了解); 页表 越小,TLB 生效的状况就越少。

应用大于 4KB 的内存页作为内存映射单位的机制叫 HugePages,目前 Linux 罕用的 HugePages 大小为 2MB 和 1GB,咱们以 2MB 大小的内存页作为例子。

要映射更大的内存页,只须要减少偏移量局部,如 图 3 所示:

如 图 3 所示,当初把偏移量局部扩大到 21 位(页表局部被笼罩了,21 位可能示意的大小范畴为 0 ~ 2MB),所以 页两头目录 间接指向映射的 物理内存页地址

这样,就能够缩小 页表 局部的内存耗费。因为内存映射关系变少,所以 TLB 生效的状况也会缩小。

三、HugePages 应用

理解了 HugePages 的原理后,咱们来介绍一下怎么应用 HugePages。

HugePages 的应用不像一般内存申请那么简略,而是须要借助 Hugetlb 文件系统 来创立,上面将会介绍 HugePages 的应用步骤:

1. 挂载 Hugetlb 文件系统

Hugetlb 文件系统是专门为 HugePages 而发明的,咱们能够通过以下命令来挂载一个 Hugetlb 文件系统:

$ mkdir /mnt/huge
$ mount none /mnt/huge -t hugetlbfs

执行完下面的命令后,咱们就在 /mnt/huge 目录下挂载了 Hugetlb 文件系统。

2. 初始化 HugePages

要应用 HugePages,首先要向内核申明能够应用的 HugePages 数量。/proc/sys/vm/nr_hugepages 文件保留了内核能够应用的 HugePages 数量,咱们能够应用以下命令设置新的可用 HugePages 数量:

$ echo 20 > /proc/sys/vm/nr_hugepages

下面命令设置了可用的 HugePages 数量为 20 个(也就是 20 个 2MB 的内存页)。

3. 编写申请 HugePages 的代码

要应用 HugePages,必须应用 mmap 零碎调用把虚拟内存映射到 Hugetlb 文件系统中的文件,如下代码:

#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <stdio.h>

 #define MAP_LENGTH (10*1024*1024) // 10MB

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

    /* 1. 创立一个 Hugetlb 文件系统的文件 */
    fd = open("/mnt/huge/hugepage1", O_CREAT|O_RDWR);
    if (fd < 0) {perror("open()");
        return -1;
    }

    /* 2. 把虚拟内存映射到 Hugetlb 文件系统的文件中 */
    addr = mmap(0, MAP_LENGTH, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) {perror("mmap()");
        close(fd);
        unlink("/mnt/huge/hugepage1");
        return -1;
    }

    strcpy(addr, "This is HugePages example...");
    printf("%s\n", addr);

    /* 3. 应用实现后,解除映射关系 */
    munmap(addr, MAP_LENGTH);
    close(fd);
    unlink("/mnt/huge/hugepage1");

    return 0;
 }

编译下面的代码并且执行,如果没有问题,将会输入以下信息:

This is HugePages example...

四、总结

本文次要介绍了 HugePages 的原理和应用,尽管 HugePages 有很多长处,但也有其有余的中央。比方调用 fork 零碎调用创立子过程时,内核应用了 写时复制 的技术(可参考《Linux 写时复制机制原理》一文),在父子过程内存产生扭转时,须要复制更大的内存页,从而影响性能。

咱们的公众号

正文完
 0