HugePages sur Raspberry Pi 4

Nov 19 2020

J'ai besoin d'aide pour gérer Hugepages sur raspberry pi 4 exécutant raspberry pi OS 64 bits.
Je n'ai pas trouvé beaucoup d'informations fiables en ligne.
J'ai d'abord recompilé l' Memory Management options --->Transparent Hugepage Supportoption d' activation de la source du noyau . Quand j'exécute la commande:

grep -i huge /proc/meminfo

La sortie est:

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

et exécutez la commande:

cat /sys/kernel/mm/transparent_hugepage/enabled

la sortie est:

[always] madvise never

Je pense donc que Transparent Huge Pages (AnonHugePages) devrait être défini. Je dois utiliser HugePages pour mapper le plus grand bloc de mémoire contigu en utilisant la fonction mmap, code c.

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

Regarder https://www.man7.org/linux/man-pages/man2/mmap.2.html il existe deux indicateurs pour gérer les pages gigantesques: l'indicateur MAP_HUGETLB et l'indicateur MAP_HUGE_2MB, MAP_HUGE_1GB.

Ma question est la suivante: pour utiliser HugePages, dois-je mapper de cette manière?

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

Configuration du noyau:

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

Je vous remercie.

Réponses

1 RachidK. Nov 19 2020 at 22:06

Les grandes pages sont un moyen d'améliorer les performances des applications en réduisant le nombre de ratés TLB. Le mécanisme fusionne des pages physiques standard contiguës (taille typique de 4 Ko) en une grande (par exemple 2 Mo). Linux implémente cette fonctionnalité de deux manières: des pages transparentes énormes et des pages énormes explicites.

Pages énormes transparentes

Les énormes pages transparentes (THP) sont gérées de manière transparente par le noyau. Les applications de l'espace utilisateur n'ont aucun contrôle sur elles. Le noyau fait de son mieux pour allouer d'énormes pages chaque fois que c'est possible mais ce n'est pas garanti. De plus, THP peut introduire une surcharge en tant que démon du noyau "garbage collector" sous-jacent nommé khugepaged est en charge de la fusion des pages physiques pour créer d'énormes pages. Cela peut consommer du temps CPU avec des effets indésirables sur les performances des applications en cours d'exécution. Dans les systèmes avec des applications à temps critique, il est généralement conseillé de désactiver THP.

THP peut être désactivé sur la ligne de commande de démarrage (cf. la fin de cette réponse) ou depuis le shell dans 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 : Il existe des articles intéressants sur l'évaluation des performances / les enjeux du THP:

  • Transparent Hugepages: mesure de l'impact sur les performances ;
  • Résoudre le mythe des HugePages transparentes pour les bases de données .

Pages énormes explicites

Si les énormes pages sont nécessaires au niveau de l'application (c'est-à-dire depuis l'espace utilisateur). La configuration du noyau HUGETLBFS doit être définie pour activer le pseudo-système de fichiers hugetlbfs (le menu du configurateur de noyau est quelque chose comme: "Systèmes de fichiers" -> "Pseudo systèmes de fichiers" -> "Support du système de fichiers HugeTLB"). Dans l'arborescence des sources du noyau, ce paramètre est dans 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.

Par exemple, sur un système Ubuntu, nous pouvons vérifier:

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

NB : Sur Raspberry Pi, il est possible de configurer l'apparition de /proc/config.gz et de faire de même avec zcat pour vérifier le paramètre. Pour ce faire, le menu de configuration est: "General setup" -> "Kernel .config support" + "Activer l'accès à .config via /proc/config.gz"

Lorsque ce paramètre est défini, le pseudo-système de fichiers hugetlbfs est ajouté dans la construction du noyau (cf. fs / Makefile ):

obj-$(CONFIG_HUGETLBFS)     += hugetlbfs/

Le code source de hugetlbfs se trouve dans fs / hugetlbfs / inode.c . Au démarrage, le noyau montera les systèmes de fichiers internes hugetlbfs pour prendre en charge toutes les grandes tailles de page disponibles pour l'architecture sur laquelle il s'exécute:

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 système de fichiers hugetlbfs est une sorte de système de fichiers RAM dans lequel le noyau crée des fichiers pour sauvegarder les régions de mémoire mappées par les applications.

La quantité d'énormes pages nécessaires peut être réservée en écrivant le nombre d'énormes pages nécessaires dans / sys / kernel / mm / massivepages / énormepages- énormepagesize / nr_hugepages .

Ensuite, mmap () est capable de mapper une partie de l'espace d'adressage de l'application sur d'énormes pages. Voici un exemple montrant comment procéder:

#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;
}

Dans le programme précédent, la mémoire pointée par addr est basée sur d'énormes pages. Exemple d'utilisation:

$ 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

Dans un autre terminal, la carte de processus peut être observée pour vérifier la taille de la page mémoire (elle est bloquée dans l' appel système 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
[...]

Dans la carte précédente, le nom de fichier / anon_hugepage pour la grande région de page est créé en interne par le noyau. Il est marqué comme supprimé car le noyau supprime le fichier mémoire associé qui fera disparaître le fichier dès qu'il n'y aura plus de références dessus (par exemple lorsque le processus appelant se termine, le fichier sous-jacent est fermé à exit () , le compteur de le fichier tombe à 0 et l'opération de suppression se termine pour le faire disparaître).

Attribution d'autres grandes tailles de page

Sur Raspberry Pi 4B, la taille de page énorme par défaut est de 2 Mo mais la carte prend en charge plusieurs autres tailles de page énormes:

$ 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

Pour les utiliser, il est nécessaire de monter un système de fichiers de type hugetlbfs correspondant à la taille de la grande page souhaitée. La documentation du noyau fournit des détails sur les options de montage disponibles. Par exemple, pour monter un système de fichiers hugetlbfs sur / mnt / énorme avec 8 Huge Pages de 64 Ko, la commande est:

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

Ensuite, il est possible de mapper d'énormes pages de 64 Ko dans un programme utilisateur. Le programme suivant crée le répertoire / tmp / hpfs sur lequel il monte un système de fichiers hugetlbfs d'une taille de 4 énormes pages de 64 Ko. Un fichier nommé / memfile_01 est créé et étendu à la taille de 2 grandes pages. Le fichier est mappé en mémoire grâce à l' appel système mmap () . L' indicateur MAP_HUGETLB n'est pas transmis car le descripteur de fichier fourni est pour un fichier créé sur un système de fichiers hugetlbfs . Ensuite, le programme appelle pause () pour suspendre son exécution afin de faire quelques observations dans un autre terminal:

#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

Le programme précédent doit être exécuté en tant que root car il appelle 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 

Dans un autre terminal, le fichier / proc / [pid] / smaps peut être affiché pour vérifier l'énorme allocation de pages. Dès que le programme écrit dans les énormes pages, le mécanisme d'allocation Lazy déclenche l'allocation effective des énormes pages.

Cf. Cet article pour les détails futurs

Réservation anticipée

Les énormes pages sont constituées de pages de mémoire physique consécutives. La réservation doit être effectuée au début du démarrage du système (en particulier sur les systèmes à forte charge) car la mémoire physique peut être si fragmentée qu'il est parfois impossible d'allouer de grandes pages par la suite. Pour réserver le plus tôt possible, cela peut être fait sur la ligne de commande de démarrage du noyau :

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.

Sur Raspberry Pi, la ligne de commande de démarrage peut généralement être mise à jour dans /boot/cmdline.txt et la ligne de commande de démarrage actuelle utilisée par le noyau en cours d'exécution peut être vue dans / proc / cmdline .

NB :

  • Cette recette est expliquée plus en détail ici et ici
  • Il existe une bibliothèque d'espace utilisateur appelée libhugetlbfs qui offre une couche d'abstraction en plus du mécanisme hugetlbfs du noyau décrit ici. Il est livré avec des services de bibliothèque comme get_huge_pages () et des outils d'accompagnement comme hugectl . Le but de ce service d'espace utilisateur est de mapper les segments de tas et de texte + données d' exécutables liés STATIQUEMENT en grandes pages (le mappage de programmes liés dynamiquement n'est pas pris en charge). Tout cela repose sur les fonctionnalités du noyau décrites dans cette réponse.