【问题标题】:HugePages on Raspberry Pi 4树莓派 4 上的 HugePages
【发布时间】:2021-03-02 22:41:20
【问题描述】:

我需要有关在运行 raspberry pi OS 64 位的 raspberry pi 4 上管理 Hugepages 的帮助。
我在网上没有找到太多可靠的信息。
首先,我重新编译了启用Memory Management options --->Transparent Hugepage Support 选项的内核源代码。 当我运行命令时:

grep -i huge /proc/meminfo

输出是:

AnonHugePages:    319488 kB
ShmemHugePages:        0 kB
FileHugePages:         0 k

并运行命令:

cat /sys/kernel/mm/transparent_hugepage/enabled

输出是:

[always] madvise never

所以我认为应该设置透明大页面(AnonHugePages)。 我需要使用 HugePages 来映射最大的连续内存块,使用 mmap 函数,c 代码。

mem = mmap(NULL,buf_size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);

查看https://www.man7.org/linux/man-pages/man2/mmap.2.html 有两个标志来管理巨页:MAP_HUGETLB 标志和 MAP_HUGE_2MB、MAP_HUGE_1GB 标志。

我的问题是:要使用 HugePages,我应该以这种方式映射吗?

mem = mmap(NULL,buf_size,PROT_READ|PROT_WRITE,MAP_SHARED,MAP_HUGETLB,fd,0);

内核配置:

CONFIG_SYS_SUPPORTS_HUGETLBFS=y
CONFIG_ARCH_WANT_HUGE_PMD_SHARE=y
CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE=y
CONFIG_HAVE_ARCH_HUGE_VMAP=y
CONFIG_TRANSPARENT_HUGEPAGE=y
CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS=y
# CONFIG_TRANSPARENT_HUGEPAGE_MADVISE is not set
CONFIG_TRANSPARENT_HUGE_PAGECACHE=y
# CONFIG_HUGETLBFS is not set

【问题讨论】:

  • 在内核命令行上为大页面设置了什么?
  • @stark 我不确定你的问题
  • 内核命令行是在启动时传递给内核的一组选项。
  • @stark 我该如何检查?

标签: linux linux-kernel raspberry-pi4 armv8 huge-pages


【解决方案1】:

大页面是一种通过减少 TLB 未命中次数来提高应用程序性能的方法。该机制将连续的标准物理页面(典型大小为 4 KB)合并为一个大页面(例如 2 MB)。 Linux 以两种方式实现此功能:透明大页面和显式大页面。

透明的大页面

透明大页 (THP) 由内核透明地管理。用户空间应用程序无法控制它们。内核会尽可能分配大页面,但不能保证。此外,THP 可能会引入开销,因为名为 khugepaged 的底层“垃圾收集器”内核守护进程负责合并物理页面以生成大页面。这可能会消耗 CPU 时间,并对正在运行的应用程序的性能产生不良影响。在具有时间关键应用程序的系统中,通常建议停用 THP。

可以在引导命令行上禁用 THP(参见此答案的结尾)或从 sysfs 中的 shell 禁用:

$ cat /sys/kernel/mm/transparent_hugepage/enabled
always [madvise] never
$ sudo sh -c "echo never > /sys/kernel/mm/transparent_hugepage/enabled"
$ cat /sys/kernel/mm/transparent_hugepage/enabled
always madvise [never]

注意:关于 THP 的性能评估/问题存在一些有趣的论文:

显式大页面

如果在应用程序级别(即来自用户空间)需要大页面。 HUGETLBFS 内核配置必须设置为激活 hugetlbfs 伪文件系统(内核配置器中的菜单类似于:“文件系统”-->“伪文件系统”- ->“HugeTLB 文件系统支持”)。在内核源代码树中,此参数位于 fs/Kconfig

config HUGETLBFS
    bool "HugeTLB file system support"
    depends on X86 || IA64 || SPARC64 || (S390 && 64BIT) || \
           SYS_SUPPORTS_HUGETLBFS || BROKEN
    help
      hugetlbfs is a filesystem backing for HugeTLB pages, based on
      ramfs. For architectures that support it, say Y here and read
      <file:Documentation/admin-guide/mm/hugetlbpage.rst> for details.

      If unsure, say N.

例如,在 Ubuntu 系统上,我们可以检查:

$ cat /boot/config-5.4.0-53-generic | grep HUGETLBFS
CONFIG_HUGETLBFS=y

NB:在树莓派上,可以配置 /proc/config.gz 的幻影,对 zcat 做同样的事情检查参数。要做到这一点,配置菜单是:“General setup”-->“Kernel .config support”+“Enable access to .config through /proc/config.gz”

设置此参数后,hugetlbfs 伪文件系统被添加到内核构建中(参见 fs/Makefile):

obj-$(CONFIG_HUGETLBFS)     += hugetlbfs/

hugetlbfs的源代码位于fs/hugetlbfs/inode.c。启动时,内核将挂载内部 hugetlbfs 文件系统,以支持其所运行架构的所有可用巨页大小:

static int __init init_hugetlbfs_fs(void)
{
    struct vfsmount *mnt;
    struct hstate *h;
    int error;
    int i;

    if (!hugepages_supported()) {
        pr_info("disabling because there are no supported hugepage sizes\n");
        return -ENOTSUPP;
    }

    error = -ENOMEM;
    hugetlbfs_inode_cachep = kmem_cache_create("hugetlbfs_inode_cache",
                    sizeof(struct hugetlbfs_inode_info),
                    0, SLAB_ACCOUNT, init_once);
    if (hugetlbfs_inode_cachep == NULL)
        goto out;

    error = register_filesystem(&hugetlbfs_fs_type);
    if (error)
        goto out_free;

    /* default hstate mount is required */
    mnt = mount_one_hugetlbfs(&hstates[default_hstate_idx]);
    if (IS_ERR(mnt)) {
        error = PTR_ERR(mnt);
        goto out_unreg;
    }
    hugetlbfs_vfsmount[default_hstate_idx] = mnt;

    /* other hstates are optional */
    i = 0;
    for_each_hstate(h) {
        if (i == default_hstate_idx) {
            i++;
            continue;
        }

        mnt = mount_one_hugetlbfs(h);
        if (IS_ERR(mnt))
            hugetlbfs_vfsmount[i] = NULL;
        else
            hugetlbfs_vfsmount[i] = mnt;
        i++;
    }

    return 0;

 out_unreg:
    (void)unregister_filesystem(&hugetlbfs_fs_type);
 out_free:
    kmem_cache_destroy(hugetlbfs_inode_cachep);
 out:
    return error;
}

hugetlbfs 文件系统是一种 RAM 文件系统,内核在其中创建文件以支持应用程序映射的内存区域。

可以通过将需要的大页面数量写入/sys/kernel/mm/hugepages/hugepages-hugepagesize/nr_hugepages来保留所需的大页面数量。

然后,mmap() 能够将应用程序地址空间的某些部分映射到大页面上。这是一个展示如何做到这一点的示例:

#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>

#define HP_SIZE  (2 * 1024 * 1024) // <-- Adjust with size of the supported HP size on your system

int main(void)
{
  char *addr, *addr1;

  // Map a Huge page
  addr = mmap(NULL, HP_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED| MAP_HUGETLB, -1, 0);
  if (addr == MAP_FAILED) {
    perror("mmap()");
    return 1;
  }

  printf("Mapping located at address: %p\n", addr);

  pause();

  return 0;
}

在前面的程序中,addr指向的内存是基于大页的。用法示例:

$ gcc alloc_hp.c -o alloc_hp
$ ./alloc_hp
mmap(): Cannot allocate memory
$ cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
0
$ sudo sh -c "echo 1 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages"
$  cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
1
$ ./alloc_hp 
Mapping located at address: 0x7f7ef6c00000

在另一个终端,可以观察进程图来验证内存页的大小(在pause()系统调用中被阻塞):

$ pidof alloc_hp
13009
$ cat /proc/13009/smaps
[...]
7f7ef6c00000-7f7ef6e00000 rw-s 00000000 00:0f 331939     /anon_hugepage (deleted)
Size:               2048 kB
KernelPageSize:     2048 kB   <----- The page size is 2MB
MMUPageSize:        2048 kB
[...]

在前面的映射中,大页面区域的文件名 /anon_hugepage 是由内核在内部生成的。它被标记为已删除,因为内核删除了关联的内存文件,这将使文件一旦不再有引用就消失(例如,当调用进程结束时,底层文件在 exit() 时关闭 em>,文件上的引用计数器下降到 0 并且删除操作完成使其消失)。

其他大页面大小的分配

在 Raspberry Pi 4B 上,默认超大页面大小为 2MB,但该卡支持其他几种超大页面大小:

$ ls -l /sys/kernel/mm/hugepages
total 0
drwxr-xr-x 2 root root 0 Nov 23 14:58 hugepages-1048576kB
drwxr-xr-x 2 root root 0 Nov 23 14:58 hugepages-2048kB
drwxr-xr-x 2 root root 0 Nov 23 14:58 hugepages-32768kB
drwxr-xr-x 2 root root 0 Nov 23 14:58 hugepages-64kB

要使用它们,需要挂载一个与所需巨页大小相对应的hugetlbfs 类型的文件系统。 kernel documentation 提供有关可用安装选项的详细信息。例如,要在 /mnt/huge 上挂载一个 hugetlbfs 文件系统,其中有 8 个大小为 64KB 的 Huge Pages,命令是:

mount -t hugetlbfs -o pagesize=64K,size=512K,min_size=512K none /mnt/huge

然后可以在用户程序中映射 64KB 的大页面。以下程序创建 /tmp/hpfs 目录,在该目录上挂载一个 hugetlbfs 文件系统,该文件系统的大小为 4 个 64KB 的巨页。创建一个名为 /memfile_01 的文件并将其扩展为 2 个大页面的大小。由于 mmap() 系统调用,文件被映射到内存中。它没有传递 MAP_HUGETLB 标志,因为提供的文件描述符是针对在 hugetlbfs 文件系统上创建的文件。然后,程序调用 pause() 来暂停其执行,以便在另一个终端中进行一些观察:

#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <fcntl.h>


#define ERR(fmt, ...) do {                            \
    fprintf(stderr,                                   \
            "ERROR@%s#%d: "fmt,                       \
             __FUNCTION__, __LINE__, ## __VA_ARGS__); \
                         } while(0)


#define HP_SIZE   (64 * 1024)
#define HPFS_DIR  "/tmp/hpfs"
#define HPFS_SIZE (4 * HP_SIZE)


int main(void)
{
void *addr;
char  cmd[256];
int   status;
int   rc;
char  mount_opts[256];
int   fd;

  rc = mkdir(HPFS_DIR, 0777);
  if (0 != rc && EEXIST != errno) {
    ERR("mkdir(): %m (%d)\n", errno);
    return 1;
  }

  snprintf(mount_opts, sizeof(mount_opts), "pagesize=%d,size=%d,min_size=%d", HP_SIZE, 2*HP_SIZE, HP_SIZE);

  rc = mount("none", HPFS_DIR, "hugetlbfs", 0, mount_opts);
  if (0 != rc) {
    ERR("mount(): %m (%d)\n", errno);
    return 1;
  }

  fd = open(HPFS_DIR"/memfile_01", O_RDWR|O_CREAT, 0777);
  if (fd < 0) {
    ERR("open(%s): %m (%d)\n", "memfile_01", errno);
    return 1;
  }

  rc = ftruncate(fd, 2 * HP_SIZE);
  if (0 != rc) {
    ERR("ftruncate(): %m (%d)\n", errno);
    return 1;
  }

  addr = mmap(NULL, 2 * HP_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
  if (MAP_FAILED == addr) {
    ERR("mmap(): %m (%d)\n", errno);
    return 1;
  }

  // The file can be closed
  rc = close(fd);
  if (0 != rc) {
    ERR("close(%d): %m (%d)\n", fd, errno);
    return 1;
  }

  pause();

  return 0;

} // main

前面的程序在调用mount()时必须以root身份运行:

$ gcc mount_tlbfs.c -o mount_tlbfs
$ cat /sys/kernel/mm/hugepages/hugepages-64kB/nr_hugepages 
0
$ sudo sh -c "echo 8 > /sys/kernel/mm/hugepages/hugepages-64kB/nr_hugepages"
$ cat /sys/kernel/mm/hugepages/hugepages-64kB/nr_hugepages 
8
$ sudo ./mount_tlbfs 

在另一个终端中,可以显示/proc/[pid]/smaps文件来检查大页分配情况。程序一写入大页,惰性分配机制就会触发大页的有效分配。

参照。这个article了解未来详情

提前预订

大页面由连续的物理内存页面组成。保留应该在系统启动的早期完成(尤其是在负载较重的系统上),因为物理内存可能非常碎片化,以至于有时无法在之后分配大页面。为了尽早预留,可以在内核boot command line上做这个:

hugepages=  
       [HW] Number of HugeTLB pages to allocate at boot.
       If this follows hugepagesz (below), it specifies
       the number of pages of hugepagesz to be allocated.
       If this is the first HugeTLB parameter on the command
       line, it specifies the number of pages to allocate for
       the default huge page size.  See also
       Documentation/admin-guide/mm/hugetlbpage.rst.
       Format: <integer>

hugepagesz=
        [HW] The size of the HugeTLB pages.  This is used in
        conjunction with hugepages (above) to allocate huge
        pages of a specific size at boot.  The pair
        hugepagesz=X hugepages=Y can be specified once for
        each supported huge page size. Huge page sizes are
        architecture dependent.  See also
        Documentation/admin-guide/mm/hugetlbpage.rst.
        Format: size[KMG]

transparent_hugepage=
        [KNL]
        Format: [always|madvise|never]
        Can be used to control the default behavior of the system
        with respect to transparent hugepages.
        See Documentation/admin-guide/mm/transhuge.rst
        for more details.

在 Raspberry Pi 上,通常可以在 /boot/cmdline.txt 中更新引导命令行,并且可以在 /proc/ 中查看正在运行的内核使用的当前引导命令行命令行.

注意

  • 这个配方有更详细的解释herehere
  • 有一个名为libhugetlbfs 的用户空间库,它在此处描述的内核hugetlbfs 机制之上提供了一个抽象层。它带有像get_huge_pages() 这样的图书馆服务和像hugectl 这样的配套工具。此用户空间服务的目标是将静态链接的可执行文件的堆和文本+数据段映射到大页面(不支持动态链接程序的映射)。所有这些都依赖于此答案中描述的内核功能。

【讨论】:

  • 当我运行命令 cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages 输出是:没有这样的文件或目录
  • 你的内核配置是什么?如果你已经安装了 config.gz : zcat /proc/config.gz |巨大的。在您的问题中添加此结果...
  • 我在问题中添加了结果。
  • 我猜你需要配置CONFIG_HUGETLBFS
  • 好的,你知道吗?因为使用 make menuconfig 我没有找到其他 HugePages 选项。我应该编辑 .config 文件吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-23
  • 2021-06-10
  • 2021-08-26
  • 2021-04-04
  • 2012-12-04
相关资源
最近更新 更多