HugePages su Raspberry Pi 4
Ho bisogno di aiuto per la gestione di Hugepages su Raspberry Pi 4 con sistema operativo Raspberry Pi a 64 bit.
Non ho trovato molte informazioni affidabili online.
Per prima cosa ho ricompilato l' Memory Management options --->Transparent Hugepage Supportopzione di abilitazione del sorgente del kernel . Quando eseguo il comando:
grep -i huge /proc/meminfo
L'output è:
AnonHugePages: 319488 kB
ShmemHugePages: 0 kB
FileHugePages: 0 k
ed eseguendo il comando:
cat /sys/kernel/mm/transparent_hugepage/enabled
l'output è:
[always] madvise never
Quindi penso che dovrebbe essere impostato Transparent Huge Pages (AnonHugePages). Ho bisogno di usare HugePages per mappare il più grande blocco di memoria contiguo usando la funzione mmap, codice c.
mem = mmap(NULL,buf_size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
Guardando https://www.man7.org/linux/man-pages/man2/mmap.2.html ci sono due flag per gestire le pagine enormi: flag MAP_HUGETLB e flag MAP_HUGE_2MB, MAP_HUGE_1GB.
La mia domanda è: per usare HugePages devo mappare in questo modo?
mem = mmap(NULL,buf_size,PROT_READ|PROT_WRITE,MAP_SHARED,MAP_HUGETLB,fd,0);
Configurazione del kernel:
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
Grazie.
Risposte
Le pagine enormi sono un modo per migliorare le prestazioni delle applicazioni riducendo il numero di TLB mancate. Il meccanismo unisce pagine fisiche standard contigue (dimensione tipica di 4 KB) in una grande (ad esempio 2 MB). Linux implementa questa funzionalità in due versioni: pagine enormi trasparenti e pagine enormi esplicite.
Pagine enormi trasparenti
Le pagine enormi trasparenti (THP) sono gestite in modo trasparente dal kernel. Le applicazioni dello spazio utente non hanno alcun controllo su di esse. Il kernel fa del suo meglio per allocare pagine enormi ogni volta che è possibile, ma non è garantito. Inoltre, THP può introdurre overhead in quanto un demone del kernel "garbage collector" sottostante chiamato khugepaged è responsabile della fusione delle pagine fisiche per creare pagine enormi. Ciò può consumare tempo della CPU con effetti indesiderati sulle prestazioni delle applicazioni in esecuzione. Nei sistemi con applicazioni critiche in termini di tempo, si consiglia generalmente di disattivare THP.
THP può essere disabilitato dalla riga di comando di avvio (cfr. La fine di questa risposta) o dalla shell in 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]
NB : Esistono alcuni documenti interessanti sulla valutazione delle prestazioni / problemi del THP:
- Enormi pagine trasparenti: misurazione dell'impatto sulle prestazioni ;
- Stabilire il mito delle pagine enormi trasparenti per i database .
Pagine enormi esplicite
Se le pagine enormi sono richieste a livello di applicazione (cioè dallo spazio utente). La configurazione del kernel HUGETLBFS deve essere impostata per attivare lo pseudo-filesystem hugetlbfs (il menu nel configuratore del kernel è qualcosa come: "File system" -> "Pseudo filesystem" -> "Supporto per il file system HugeTLB"). Nell'albero dei sorgenti del kernel questo parametro è in 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.
Ad esempio, su un sistema Ubuntu, possiamo controllare:
$ cat /boot/config-5.4.0-53-generic | grep HUGETLBFS
CONFIG_HUGETLBFS=y
NB : Su Raspberry Pi è possibile configurare l'apparizione di /proc/config.gz e fare lo stesso con zcat per controllare il parametro. Per farlo, il menu di configurazione è: "Configurazione generale" -> "Supporto .config del kernel" + "Abilita l'accesso a .config tramite /proc/config.gz"
Quando questo parametro è impostato, lo pseudo-filesystem hugetlbfs viene aggiunto alla build del kernel (cfr. Fs / Makefile ):
obj-$(CONFIG_HUGETLBFS) += hugetlbfs/
Il codice sorgente di hugetlbfs si trova in fs / hugetlbfs / inode.c . All'avvio, il kernel monterà file system interni hugetlbfs per supportare tutte le enormi dimensioni di pagina disponibili per l'architettura su cui è in esecuzione:
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;
}
Un file system hugetlbfs è una sorta di file system RAM in cui il kernel crea file per supportare le regioni di memoria mappate dalle applicazioni.
La quantità di pagine enormi necessarie può essere riservata scrivendo il numero di pagine enormi necessarie in / sys / kernel / mm / hugepages / hugepages- hugepagesize / nr_hugepages .
Quindi, mmap () è in grado di mappare una parte dello spazio degli indirizzi dell'applicazione su pagine enormi. Ecco un esempio che mostra come farlo:
#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;
}
Nel programma precedente, la memoria puntata da addr è basata su pagine enormi. Esempio di utilizzo:
$ 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
In un altro terminale, è possibile osservare la mappa del processo per verificare la dimensione della pagina di memoria (è bloccata nella chiamata di sistema 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
[...]
Nella mappa precedente, il nome del file / anon_hugepage per la regione della pagina enorme è creato internamente dal kernel. È contrassegnato come cancellato perché il kernel rimuove il file di memoria associato che farà scomparire il file non appena non ci saranno più riferimenti su di esso (es. Quando il processo chiamante termina, il file sottostante viene chiuso all'uscita () , il contatore dei riferimenti su il file scende a 0 e l'operazione di rimozione termina per farlo scomparire).
Allocazione di altre dimensioni di pagina enormi
Su Raspberry Pi 4B, la dimensione della pagina enorme predefinita è 2 MB ma la scheda supporta molte altre dimensioni di pagina enormi:
$ 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
Per usarli, è necessario montare un file system di tipo hugetlbfs corrispondente alla dimensione della pagina enorme desiderata. La documentazione del kernel fornisce dettagli sulle opzioni di montaggio disponibili. Ad esempio, per montare un file system hugetlbfs su / mnt / huge con 8 pagine enormi di dimensione 64 KB, il comando è:
mount -t hugetlbfs -o pagesize=64K,size=512K,min_size=512K none /mnt/huge
Quindi è possibile mappare pagine enormi di 64 KB in un programma utente. Il seguente programma crea la directory / tmp / hpfs su cui monta un file system hugetlbfs con una dimensione di 4 pagine enormi di 64 KB. Viene creato un file denominato / memfile_01 ed esteso alla dimensione di 2 pagine enormi. Il file viene mappato in memoria grazie alla chiamata di sistema mmap () . Non viene passato il flag MAP_HUGETLB poiché il descrittore di file fornito è per un file creato su un filesystem hugetlbfs . Quindi, il programma chiama pause () per sospendere la sua esecuzione al fine di fare alcune osservazioni in un altro terminale:
#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
Il programma precedente deve essere eseguito come root poiché chiama mount () :
$ 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
In un altro terminale, il file / proc / [pid] / smaps può essere visualizzato per controllare l'enorme allocazione della pagina. Non appena il programma scrive nelle pagine enormi, il meccanismo di allocazione Lazy attiva l'allocazione effettiva delle pagine enormi.
Cfr. Questo articolo per i dettagli futuri
Prenotazione anticipata
Le pagine enormi sono realizzate con pagine di memoria fisica consecutive. La prenotazione dovrebbe essere eseguita all'inizio del sistema (specialmente su sistemi con carichi pesanti) poiché la memoria fisica può essere così frammentata che a volte è impossibile allocare pagine enormi in seguito. Per prenotare il prima possibile, questo può essere fatto dalla riga di comando di avvio del kernel :
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.
Su Raspberry Pi, la riga di comando di avvio può essere in genere aggiornata in /boot/cmdline.txt e la riga di comando di avvio corrente utilizzata dal kernel in esecuzione può essere visualizzata in / proc / cmdline .
NB :
- Questa ricetta è spiegata più in dettaglio qui e qui
- Esiste una libreria dello spazio utente chiamata libhugetlbfs che offre uno strato di astrazione in cima al meccanismo hugetlbfs del kernel descritto qui. Viene fornito con servizi di libreria come get_huge_pages () e strumenti di accompagnamento come hugectl . L'obiettivo di questo servizio di spazio utente è quello di mappare l'heap e i segmenti di testo + dati di eseguibili collegati STATICAMENTE in pagine enormi (la mappatura di programmi collegati dinamicamente non è supportata). Tutto ciò si basa sulle funzionalità del kernel descritte in questa risposta.