HugeTLB 页

概述

本文档旨在简要概述 Linux 内核中的 hugetlbpage 支持。此支持构建在大多数现代架构提供的多页面大小支持之上。例如,x86 CPU 通常支持 4K 和 2M(如果架构支持,则为 1G)页面大小,ia64 架构支持多种页面大小 4K、8K、64K、256K、1M、4M、16M、256M,而 ppc64 支持 4K 和 16M。TLB 是虚拟到物理地址转换的缓存。通常,这是处理器上非常稀缺的资源。操作系统尝试充分利用有限数量的 TLB 资源。随着越来越大的物理内存(几 GB)更容易获得,这种优化现在变得更加关键。

用户可以通过使用 mmap 系统调用或标准 SYSV 共享内存系统调用(shmget、shmat)来使用 Linux 内核中的大页支持。

首先,Linux 内核需要使用 CONFIG_HUGETLBFS(位于“文件系统”下)和 CONFIG_HUGETLB_PAGE(选择 CONFIG_HUGETLBFS 时自动选择)配置选项进行构建。

/proc/meminfo 文件提供有关内核大页池中持久 hugetlb 页总数的信息。它还显示默认的大页大小以及有关默认大小的大页池中空闲、保留和剩余大页数量的信息。大页大小是生成系统调用的参数的正确对齐和大小所必需的,这些系统调用映射大页区域。

cat /proc/meminfo 的输出将包括如下行

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

其中

HugePages_Total

是大页池的大小。

HugePages_Free

是池中尚未分配的大页数量。

HugePages_Rsvd

是“保留”的缩写,并且是已承诺从池中分配的大页数量,但尚未进行任何分配。保留的大页保证应用程序能够在缺页时从大页池中分配一个大页。

HugePages_Surp

是“剩余”的缩写,并且是池中超出 /proc/sys/vm/nr_hugepages 中的值的大页数量。剩余大页的最大数量由 /proc/sys/vm/nr_overcommit_hugepages 控制。注意:当启用释放与每个 hugetlb 页关联的未使用 vmemmap 页的功能时,当系统处于内存压力下时,剩余大页的数量可能会暂时大于剩余大页的最大数量。

Hugepagesize

是默认的大页大小(以 kB 为单位)。

Hugetlb

是所有大小的大页消耗的内存总量(以 kB 为单位)。如果使用了不同大小的大页,则此数字将超过 HugePages_Total * Hugepagesize。要获得更详细的信息,请参阅 /sys/kernel/mm/hugepages(如下所述)。

/proc/filesystems 还应显示内核中配置的“hugetlbfs”类型的文件系统。

/proc/sys/vm/nr_hugepages 指示内核大页池中当前“持久”大页的数量。“持久”大页在任务释放时将返回到大页池。具有 root 权限的用户可以通过增加或减少 nr_hugepages 的值来动态分配更多或释放一些持久大页。

注意:当启用释放与每个 hugetlb 页关联的未使用 vmemmap 页的功能时,当系统处于内存压力下时,我们可能无法释放用户触发的大页。请稍后再试。

用作大页的页面在内核中保留,不能用于其他目的。在大内存压力下,大页无法交换出去。

一旦将大量大页预分配到内核大页池,具有适当权限的用户就可以使用 mmap 系统调用或共享内存系统调用来使用大页。请参阅下面的 使用大页 的讨论。

管理员可以通过指定“hugepages=N”参数在内核启动命令行上分配持久大页,其中“N”=请求的大页数。这是分配大页最可靠的方法,因为内存尚未碎片化。

某些平台支持多种大页大小。要分配特定大小的大页,必须在大页引导命令参数前加上大页大小选择参数“hugepagesz=<size>”。 <size> 必须以字节为单位指定,并带有可选的比例后缀 [kKmMgG]。可以使用 “default_hugepagesz=<size>” 引导参数选择默认的大页大小。

Hugetlb 引导命令行参数语义

hugepagesz

指定大页大小。与 hugepages 参数结合使用,以预分配指定数量的指定大小的大页。因此,hugepagesz 和 hugepages 通常以配对方式指定,例如

hugepagesz=2M hugepages=512

对于特定的大页大小,hugepagesz 只能在命令行上指定一次。有效的大页大小取决于体系结构。

hugepages

指定要预分配的大页数量。这通常遵循有效的 hugepagesz 或 default_hugepagesz 参数。但是,如果 hugepages 是第一个或唯一一个 hugetlb 命令行参数,它将隐式指定要分配的默认大小的大页数量。如果默认大小的大页数量是隐式指定的,则不能通过默认大小的 hugepagesz,hugepages 参数对来覆盖它。此参数还具有节点格式。节点格式指定要在特定节点上分配的大页数量。

例如,在具有 2M 默认大页大小的体系结构上

hugepages=256 hugepagesz=2M hugepages=512

将导致分配 256 个 2M 大页,并显示一条警告消息,指示 hugepages=512 参数被忽略。如果 hugepages 参数前面有一个无效的 hugepagesz 参数,它将被忽略。

节点格式示例

hugepagesz=2M hugepages=0:1,1:2

它将在 node0 上分配 1 个 2M 大页,在 node1 上分配 2 个 2M 大页。如果节点号无效,则该参数将被忽略。

hugepage_alloc_threads

指定在引导期间应用于分配大页的线程数。当分配大量大页时,可以使用此参数来提高系统启动时间。

默认值为可用硬件线程的 25%。使用 8 个分配线程的示例

hugepage_alloc_threads=8

请注意,此参数仅适用于非巨型大页。

default_hugepagesz

指定默认的大页大小。此参数只能在命令行上指定一次。 default_hugepagesz 可以选择性地跟在 hugepages 参数之后,以预分配特定数量的默认大小的大页。如上面的 hugepages 部分所述,也可以隐式指定要预分配的默认大小的大页的数量。因此,在具有 2M 默认大页大小的体系结构上

hugepages=256
default_hugepagesz=2M hugepages=256
hugepages=256 default_hugepagesz=2M

都将导致分配 256 个 2M 大页。有效的默认大页大小取决于体系结构。

hugetlb_free_vmemmap

当设置了 CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP 时,这将启用 HugeTLB Vmemmap 优化 (HVO)。

当支持多种大页大小时,/proc/sys/vm/nr_hugepages 指示当前预分配的默认大小的大页的数量。因此,可以使用以下命令来动态分配/取消分配默认大小的持久大页

echo 20 > /proc/sys/vm/nr_hugepages

此命令将尝试将大页池中默认大小的大页数量调整为 20,根据需要分配或释放大页。

在 NUMA 平台上,内核将尝试将大页池分布在修改 nr_hugepages 的任务的 NUMA 内存策略指定的允许节点集上。当任务具有默认内存策略时,允许节点的默认设置为所有具有内存的在线节点。当分配持久大页时,将静默跳过可用连续内存不足以容纳大页的允许节点。请参阅下面有关任务内存策略、cpuset 和每个节点属性与持久大页的分配和释放交互的 讨论

大页分配的成功与否取决于系统中存在物理连续内存的数量。如果内核无法从 NUMA 系统中的某些节点分配大页,它将尝试通过在其他具有足够可用连续内存的节点上分配额外的页面来弥补差异(如果有的话)。

系统管理员可能希望将此命令放入其中一个本地 rc init 文件中。这将使内核能够在启动过程的早期分配大页,此时获得物理连续页面的可能性仍然很高。管理员可以通过检查 sysctl 或 meminfo 来验证实际分配的大页数量。要检查 NUMA 系统中大页的每个节点分布,请使用

cat /sys/devices/system/node/node*/meminfo | fgrep Huge

/proc/sys/vm/nr_overcommit_hugepages 指定大页池可以增长到多大,如果应用程序请求的大页超过 /proc/sys/vm/nr_hugepages。将任何非零值写入此文件表明,当持久大页池耗尽时,hugetlb 子系统允许尝试从内核的普通页面池中获取该数量的“剩余”大页。当这些剩余大页变得未使用时,它们将被释放回内核的普通页面池。

当通过 nr_hugepages 增加大页池大小时,任何现有的剩余页面将首先提升为持久大页。然后,如果需要且可能,将分配额外的大页以满足新的持久大页池大小。

管理员可以通过将 nr_hugepages sysctl 设置为较小的值来缩小默认大页大小的持久大页池。内核将尝试在修改 nr_hugepages 的任务的内存策略中的所有节点上平衡大页的释放。所选节点上的任何空闲大页都将释放回内核的普通页面池。

警告:通过 nr_hugepages 缩小持久大页池,使其小于使用中的大页数,会将使用中的大页的平衡转换为剩余大页。即使剩余页面的数量超过过度提交值,也会发生这种情况。只要此条件成立(即,直到 nr_hugepages+nr_overcommit_hugepages 充分增加,或者剩余大页不再使用并被释放),将不允许分配更多的剩余大页。

由于运行时可用的多个大页池的支持,/proc/sys/vm 中的大多数大页用户空间接口已在 sysfs 中复制。为了向后兼容性,保留了上面讨论的 /proc 接口。 sysfs 中的根大页控制目录是

/sys/kernel/mm/hugepages

对于正在运行的内核支持的每个大页大小,都将存在一个子目录,其形式为

hugepages-${size}kB

在这些目录中的每一个中,都将存在 /proc 中包含的一组文件。此外,还可能存在两个额外的用于降级大页的接口

demote
demote_size
nr_hugepages
nr_hugepages_mempolicy
nr_overcommit_hugepages
free_hugepages
resv_hugepages
surplus_hugepages

降级接口提供将大页拆分为更小的大页的能力。例如,x86 架构同时支持 1GB 和 2MB 大页大小。 1GB 大页可以拆分为 512 个 2MB 大页。降级接口不适用于最小的大页大小。降级接口是

demote_size

是降级页面大小。当降级页面时,将创建相应数量的 demote_size 大页。默认情况下,demote_size 设置为下一个较小的大页大小。如果有多个较小的大页大小,则 demote_size 可以设置为任何这些较小的大小。仅允许小于当前大页大小的大页大小。

demote

用于降级多个大页。具有 root 权限的用户可以写入此文件。可能无法降级请求的大页数量。要确定实际降级的页面数量,请比较写入降级接口之前和之后 nr_hugepages 的值。 demote 是一个只写接口。

/proc 中相同的接口(除了 demote 和 demote_size 之外的所有接口)的函数与上述默认大页大小的情况类似。

任务内存策略与大页分配/释放的交互

无论是通过 /proc 接口还是通过使用 nr_hugepages_mempolicy 属性的 /sysfs 接口分配和释放大页到内核大页池,从中分配或释放大页的 NUMA 节点都由修改 nr_hugepages_mempolicy sysctl 或属性的任务的 NUMA 内存策略控制。当使用 nr_hugepages 属性时,mempolicy 将被忽略。

使用 nr_hugepages 的上述示例中,将大页分配或释放到/从内核大页池的推荐方法是

numactl --interleave <node-list> echo 20 \
                            >/proc/sys/vm/nr_hugepages_mempolicy

或者,更简洁地说

numactl -m <node-list> echo 20 >/proc/sys/vm/nr_hugepages_mempolicy

这将分配或释放 <node-list> 中指定的节点上的 abs(20 - nr_hugepages),具体取决于持久大页的数量最初是小于还是大于 20。不会在指定的 <node-list> 中未包含的任何节点上分配或释放大页。

当通过 nr_hugepages_mempolicy 调整持久大页计数时,可以使用任何内存策略模式——绑定、首选、本地或交错。对持久大页分配产生的效果如下

  1. 无论 mempolicy 模式如何 [请参阅 NUMA 内存策略],持久大页将分布在 mempolicy 中指定的节点上,就好像指定了 “交错”一样。但是,如果策略中的节点不包含足够连续的内存来容纳大页,则分配将不会“回退”到具有足够连续内存的最近邻居节点。这样做会导致大页池的分配出现不希望的失衡,或者可能在任务内存策略不允许的节点上分配持久大页。

  2. 可以使用绑定或交错策略指定一个或多个节点。如果使用首选策略指定了多个节点,则仅使用最低的数字 ID。本地策略将选择在构造 nodes_allowed 掩码时任务正在运行的节点。为了使本地策略具有确定性,必须将任务绑定到单个节点中的 CPU 或 CPU。否则,任务可能在启动后随时迁移到其他节点,并且生成的节点将是不确定的。因此,本地策略对于此目的不是很有用。可以使用任何其他 mempolicy 模式来指定单个节点。

  3. 允许的节点掩码将从任何非默认任务 mempolicy 派生,无论此策略是由任务本身还是其祖先(如 numactl)显式设置的。这意味着,如果任务是从具有非默认策略的 shell 调用的,则将使用该策略。可以使用 numactl --interleave 或 --membind [-m] 指定 “all” 的节点列表,以实现在系统或 cpuset 中的所有节点上进行交错。

  4. 任何指定的任务 mempolicy(例如,使用 numactl)都将受到任务运行的任何 cpuset 的资源限制的约束。因此,对于在具有系统节点子集的 cpuset 中运行的具有非默认策略的任务,如果没有首先移动到包含所有所需节点的 cpuset,则无法在 cpuset 之外分配大页。

  5. 启动时大页分配尝试将请求的大页数量分布在所有具有内存的在线节点上。

每个节点的大页属性

上面描述的 sysfs 中根大页控制目录的一部分内容将在每个 NUMA 节点的系统设备下复制,其中包含

/sys/devices/system/node/node[0-9]*/hugepages/

在此目录下,每个受支持的大页大小的子目录都包含以下属性文件

nr_hugepages
free_hugepages
surplus_hugepages

free_’ 和 surplus_’ 属性文件是只读的。它们分别返回父节点上空闲和剩余 [过度提交] 大页的数量。

nr_hugepages 属性返回指定节点上的大页总数。当写入此属性时,无论任务的 mempolicy 或 cpuset 约束如何,父节点上的持久大页数量都将调整为指定值(如果存在足够的资源)。

请注意,过度提交和保留页面的数量仍然是全局量,因为我们直到出现故障时才知道,此时将应用故障任务的 mempolicy,从哪个节点尝试进行大页分配。

hugetlb 可以在以下情况下在每个节点的大页池之间迁移:内存离线、内存故障、长期固定、系统调用(mbind、migrate_pages 和 move_pages)、alloc_contig_range()alloc_contig_pages()。现在只有内存离线、内存故障和系统调用允许回退以在不同的节点上分配新的 hugetlb,如果当前节点无法在 hugetlb 迁移期间分配,这意味着这 3 种情况可能会破坏每个节点的 hugepages 池。

使用大页

如果用户应用程序将使用 mmap 系统调用请求大页,则系统管理员需要挂载 hugetlbfs 类型的文件系统

mount -t hugetlbfs \
      -o uid=<value>,gid=<value>,mode=<value>,pagesize=<value>,size=<value>,\
      min_size=<value>,nr_inodes=<value> none /mnt/huge

此命令在目录 /mnt/huge 上挂载 hugetlbfs 类型的(伪)文件系统。在 /mnt/huge 上创建的任何文件都使用大页。

uidgid 选项设置文件系统根目录的所有者和组。默认情况下,采用当前进程的 uidgid

mode 选项将文件系统根目录的模式设置为 value & 01777。此值以八进制给出。默认情况下,选择值 0755。

如果平台支持多种大页大小,则可以使用 pagesize 选项来指定大页大小和关联的池。pagesize 以字节为单位指定。如果未指定 pagesize,则将使用平台的默认大页大小和关联的池。

size 选项设置该文件系统 (/mnt/huge) 允许的最大内存(大页)值。size 选项可以以字节为单位指定,也可以指定为指定大页池 (nr_hugepages) 的百分比。大小向下舍入到 HPAGE_SIZE 边界。

min_size 选项设置文件系统允许的最小内存(大页)值。 min_size 可以与 size 相同的方式指定,可以是字节数或大页池的百分比。在挂载时,将保留 min_size 指定的大页数量供文件系统使用。如果没有足够的空闲大页可用,则挂载将失败。当大页分配给文件系统并释放时,将调整保留计数,以便已分配和保留的大页的总和始终至少为 min_size

nr_inodes 选项设置 /mnt/huge 可以使用的最大 inode 数。

如果命令行上未提供 sizemin_sizenr_inodes 选项,则不会设置任何限制。

对于 pagesizesizemin_sizenr_inodes 选项,可以使用 [G|g]/[M|m]/[K|k] 来表示 giga/mega/kilo。例如,size=2K 与 size=2048 具有相同的含义。

虽然在 hugetlb 文件系统上的文件上支持读取系统调用,但不支持写入系统调用。

可以使用常规的 chown、chgrp 和 chmod 命令(具有正确的权限)来更改 hugetlbfs 上的文件属性。

另外,重要的是要注意,如果应用程序仅使用 shmat/shmget 系统调用或带有 MAP_HUGETLB 的 mmap,则不需要这样的挂载命令。有关如何使用带有 MAP_HUGETLB 的 mmap 的示例,请参见下面的 map_hugetlb

希望通过共享内存段使用 hugetlb 内存的用户应该是补充组的成员,并且系统管理员需要将该 gid 配置到 /proc/sys/vm/hugetlb_shm_group 中。相同或不同的应用程序可以使用 mmap 和 shm* 调用的任何组合,尽管在使用没有 MAP_HUGETLB 的 mmap 调用时需要挂载文件系统。

对 hugetlb 页面支持的内存执行操作的系统调用的长度仅与处理器的本机页面大小对齐;如果长度小于 hugepage 大小,它们通常会失败并将 errno 设置为 EINVAL 或排除超出长度范围的 hugetlb 页面(如果未与 hugepage 对齐)。例如,如果内存由 hugetlb 页面支持且长度小于 hugepage 大小,则 munmap(2) 将失败。

示例

map_hugetlb

请参阅 tools/testing/selftests/mm/map_hugetlb.c

hugepage-shm

请参阅 tools/testing/selftests/mm/hugepage-shm.c

hugepage-mmap

请参阅 tools/testing/selftests/mm/hugepage-mmap.c

libhugetlbfs 库提供了一系列广泛的用户空间工具,以帮助提高大页的可用性、环境设置和控制。