Raspberry Pi4のHugePages
ラズベリーパイOS64ビットを実行しているラズベリーパイ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 ヒュージページを管理するための2つのフラグがあります。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
ありがとうございました。
回答
巨大なページは、TLBミスの数を減らすことにより、アプリケーションのパフォーマンスを向上させる方法です。このメカニズムは、隣接する標準の物理ページ(通常は4 KBのサイズ)を大きなページ(2 MBなど)に結合します。Linuxは、この機能を2つのフレーバーで実装します。透過的な巨大ページと明示的な巨大ページです。
透明な巨大なページ
透過的巨大ページ(THP)は、カーネルによって透過的に管理されます。ユーザースペースアプリケーションはそれらを制御できません。カーネルは可能な限り巨大なページを割り当てるように最善を尽くしますが、それは保証されていません。さらに、THPは、khugepagedという名前の基盤となる「ガベージコレクタ」カーネルデーモンが物理ページの合体を担当して巨大なページを作成するため、オーバーヘッドを導入する可能性があります。これはCPU時間を消費し、実行中のアプリケーションのパフォーマンスに望ましくない影響を与える可能性があります。タイムクリティカルなアプリケーションを使用するシステムでは、通常、THPを非アクティブ化することをお勧めします。
THPは、ブートコマンドライン(この回答の最後を参照)またはsysfsのシェルから無効にできます。
$ 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のパフォーマンス評価/問題に関する興味深い論文がいくつかあります。
- 透明な巨大ページ:パフォーマンスへの影響の測定;
- データベースの透過的なHugePagesの神話を解決する。
明示的な巨大ページ
巨大なページがアプリケーションレベルで(つまり、ユーザースペースから)必要な場合。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
注意:Raspberry Piでは、/ proc / config.gzの出現を構成し、zcatで同じことを行ってパラメーターを確認することができます。これを行うには、構成メニューは次のとおりです。「一般設定」->「カーネル.configサポート」+「/ proc /config.gzを介した.configへのアクセスを有効にする」
このパラメーターを設定すると、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;
}
A 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()で基になるファイルが閉じられ、参照カウンターがファイルは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タイプのファイルシステムをマウントする必要があります。カーネルのドキュメントには、使用可能なマウントオプションの詳細を提供します。たとえば、サイズ64KBの8つの巨大なページを持つ/ mnt / hugeにhugetlbfsファイルシステムをマウントするには、コマンドは次のとおりです。
mount -t hugetlbfs -o pagesize=64K,size=512K,min_size=512K none /mnt/huge
次に、ユーザープログラムで64KBの巨大なページをマッピングすることが可能です。次のプログラムは、64KBの4つの巨大なページのサイズのhugetlbfsファイルシステムをマウントする/ tmp / hpfsディレクトリを作成します。/ memfile_01という名前のファイルが作成され、2つの巨大なページのサイズに拡張されます。mmap()システムコールのおかげで、ファイルはメモリにマップされます。提供されているファイル記述子はhugetlbfsファイルシステムで作成されたファイル用であるため、MAP_HUGETLBフラグは渡されません。次に、プログラムは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ファイルを表示して、膨大なページ割り当てを確認できます。プログラムが巨大なページに書き込むとすぐに、レイジー割り当てメカニズムが巨大なページの効果的な割り当てをトリガーします。
Cf. 今後の詳細については、この記事
早期予約
巨大なページは、連続した物理メモリページで作成されます。物理メモリが非常に断片化されているため、後で巨大なページを割り当てることができない場合があるため、予約はシステムの起動の早い段階で行う必要があります(特に負荷の高いシステムの場合)。できるだけ早く予約するには、カーネルブートコマンドラインでこれを行うことができます。
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 / cmdlineで確認できます。
注意:
- このレシピはこことここでより詳細に説明されています
- ここで説明するカーネルのhugetlbfsメカニズムの上に抽象化レイヤーを提供するlibhugetlbfsと呼ばれるユーザースペースライブラリがあります。get_huge_pages()などのライブラリサービスと、hugectlなどの付属ツールが付属しています。このユーザースペースサービスの目的は、静的にリンクされた実行可能ファイルのヒープセグメントとテキスト+データセグメントを巨大なページにマッピングすることです(動的にリンクされたプログラムのマッピングはサポートされていません)。これはすべて、この回答で説明されているカーネル機能に依存しています。