HugeTLB—support des pages mémoire de grande taille dans le noyau Linux

Gazette Linux n°155 — Octobre 2008

Adaptation française: Nicolas Provost

Relecture de la version française : Deny

Article paru dans le n°155 de la Gazette Linux d'octobre 2008.

Article publié sous Open Publication License. La Linux Gazette n'est ni produite, ni sponsorisée, ni avalisée par notre hébergeur principal, SSC, Inc.


Table des matières

Sommaire
Introduction
Activer le support des pages de grande taille
Comment utiliser la pagination étendue
Exemple d'exécution du programme utilisant la pagination étendue
Gestion interne de la pagination étendue
Initialisation des pages de grande taille
Fautes de pagination et allocation des pages physiques
Aller plus loin
Liens
Conclusion
Remerciements

Sommaire

Cet article constitue une introduction à la fonctionnalité HugeTLB du noyau Linux, qui permet d'utiliser des pages de mémoire virtuelle de grande taille. Nous commencerons par une introduction autour de cette fonctionnalité du noyau, puis nous verrons comment l'activer et utiliser des pages mémoire de grande taille. Enfin nous examinerons la gestion interne de cette pagination étendue au sein du noyau Linux.

Nous utiliserons indifféremment les expressions « pages larges », « pages mémoire de grande taille », « pagination étendue », HugeTLB, etc. dans cet article. Cet article couvre les aspects de la pagination étendue sur architecture x86, bien que cela soit directement transposable à d'autres architectures.

Introduction

Du point de vue du gestionnaire de mémoire, toute la mémoire physique est divisée en « segments », (frames) et la mémoire virtuelle est divisée en « pages ». L'unité de gestion de la mémoire opère une conversion des adresses de mémoire virtuelle en adresses de mémoire physique. Les informations permettant de faire correspondre les pages de mémoire virtuelle en segments de mémoire physique sont contenues dans une structure de données appelée « table des pages ». Les recherches dans cette table sont coûteuses en terme de performances. Pour éviter les problèmes de performance liés à ces recherches, une mémoire cache appelée « tampon de translation » (Translation Lookaside Buffer ou TLB) existe dans la plupart des architectures. Ce tampon contient les correspondances entre les adresses de mémoire virtuelle et de mémoire physique. Ainsi, pour chaque adresse de mémoire virtuelle devant être convertie en adresse de mémoire physique, une recherche de correspondance est effectuée dans le tampon TLB. Lorsqu'une telle correspondance valide n'existe pas, on parle d' « échec TLB ». Dans ce cas, le gestionnaire de mémoire doit se référer à la table des pages pour obtenir la correspondance. Cela se traduit par un surcoût en terme de performances, et donc il est important de limiter ces échecs.

Sur les configurations courantes des machines x86, la taille de page mémoire est de 4 Ko, mais le matériel permet la gestion de pages de plus grande taille. Par exemple, les machines x86 32 bits (Pentium et ultérieurs) autorisent des pages de 2 et 4 Mo. D'autres architectures, telle IA64, prennent en charge de multiples tailles de pages. Par le passé, Linux ne gérait pas les pages larges, mais avec l'arrivée de la fonctionnalité HugeTLB dans le noyau, les applications peuvent maintenant bénéficier de cette pagination étendue. L'utilisation des pages de grande taille limite les échecs TLB. En effet, quand la taille d'une page est grande, une seule entrée du tampon TLB correspond à une grande portion de mémoire. Les applications très gourmandes en mémoire telles que les bases de données, les applications hautes performances, etc. peuvent potentiellement en bénéficier.

Activer le support des pages de grande taille

Le support de la pagination étendue peut être ajouté au noyau Linux en choisissant CONFIG_HUGETLB_PAGE et CONFIG_HUGETLBFS durant la configuration du noyau. Sur une machine dont le noyau a été configuré pour utiliser HugeTLB, les informations concernant les pages larges peuvent être consultées à partir de /proc/meminfo. L'exemple ci-dessous correspond à un portable utilisant un AMD™ Sempron, un noyau 2.6.20.7 avec HugeTLB activé. Les informations concernant la pagination étendue sont contenues dans les lignes commençant par Huge.

#cat /proc/meminfo | grep Huge
HugePages_Total:     0
HugePages_Free:      0
HugePages_Rsvd:      0
Hugepagesize:     4096 kB

Nous devons indiquer au noyau le nombre de pages larges qui doivent être utilisées. Pour cela on utilise la commande echo avec le nombre de pages à réserver vers l'entrée nr_hugepages de /proc/sys. Dans l'exemple suivant, nous pré-réservons un maximum de 4 pages de grande taille :

#echo 4 > /proc/sys/vm/nr_hugepages

Dés lors, le noyau allouera les pages de grande taille nécessaires (selon la disponibilité de mémoire). Nous pouvons une nouvelle fois consulter /proc/meminfo pour avoir la confirmation que le noyau a bien alloué les pages larges.

#cat /proc/meminfo | grep Huge
HugePages_Total:     4
HugePages_Free:      4
HugePages_Rsvd:      0
Hugepagesize:     4096 kB

Il est également possible d'activer les pages de grande taille en précisant le paramètre hugepages= lors du démarrage du noyau. Ou encore utiliser sysctl pour fixer le nombre de pages de grande taille.

Comment utiliser la pagination étendue

Une application peut utiliser les pages mémoire de grande taille de deux façons. L'une est l'utilisation d'une zone spéciale de mémoire partagée, et l'autre est l'utilisation de fichiers mappés avec mmap depuis le système de fichiers HugeTLB. Il est particulièrement recommandé dans le cas de mappages privés HugeTLB d'utiliser les techniques mmap HugeTLB. Dans le cadre de cet article, nous nous concentrerons sur l'utilisation de mémoire partagée. Voyons donc maintenant comment utiliser un tableau stocké par une application dans des pages de grande taille.

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>

#define MB_1 (1024*1024)
#define MB_8 (8*MB_1)

char  *a;
int shmid1;

void init_hugetlb_seg()
{
  shmid1 = shmget(2, MB_8, SHM_HUGETLB
         | IPC_CREAT | SHM_R
         | SHM_W);
  if ( shmid1 < 0 ) {
    perror("shmget");
    exit(1);
  }
  printf("HugeTLB shmid: 0x%x\n", shmid1);
  a = shmat(shmid1, 0, 0);
  if (a == (char *)-1) {
    perror("Echec d'attachement de mémoire partagée");
    shmctl(shmid1, IPC_RMID, NULL);
    exit(2);
  }
}

void wr_to_array()
{
  int i;
  for( i=0 ; i<MB_8 ; i++) {
    a[i] = 'A';
  }
}

void rd_from_array()
{
  int i, count = 0;
  for( i=0 ; i<MB_8 ; i++)
    if (a[i] == 'A') count++;
  if (count==i)
    printf("Lecture HugeTLB réussie :-)\n");
  else
    printf("Echec de lecture HugeTLB :-(\n");
}

int main(int argc, char *argv[])
{
  init_hugetlb_seg();
  printf("Zone de mémoire HugeTLB initialisée !\n");
  printf("Appuyez sur une touche pour écrire dans la mémoire\n");
  getchar();
  wr_to_array();
  printf("Appuyez sur une touche pour lire la mémoire\n");
  getchar();
  rd_from_array();
  shmctl(shmid1, IPC_RMID, NULL);
  return 0;
}

Le programme ci-dessus se comporte comme tout programme utilisant de la mémoire partagée. Tout d'abord, nous initialisons la zone de mémoire partagée avec un drapeau supplémentaire SHM_HUGETLB afin d'obtenir de la mémoire partagée à partir des pages de grande taille. Puis nous attachons cette zone de mémoire à notre programme. Ensuite, nous écrivons dans la mémoire partagée par un appel à la fonction wr_to_array. Enfin, nous vérifions que les données ont bien été écrites proprement en relisant celles-ci à l'aide de la fonction rd_from_array.

Exemple d'exécution du programme utilisant la pagination étendue

Compilons et exécutons maintenant le programme ci-dessus .

#cc hugetlb-array.c -o hugetlb-array -Wall
#./hugetlb-array
HugeTLB shmid: 0x40000
Zone de mémoire HugeTLB initialisée !
Appuyez sur une touche pour écrire dans la mémoire

A ce stade, si nous contrôlons l'état des pages HugeTLB dans /proc/meminfo, nous verrons 2 pages, c'est-à-dire 8 Mo de mémoire réservée. Toutes les pages de grande taille seront affichées comme disponibles, puisque nous n'avons pas encore utilisé cette zone de mémoire.

#cat /proc/meminfo | grep Huge
HugePages_Total:     4
HugePages_Free:      4
HugePages_Rsvd:      2
Hugepagesize:     4096 kB

Pressez une touche comme le demande l'entrée du programme, ce qui entraînera l'écriture dans les pages HugeTLB réservées. Maintenant, la zone de mémoire est utilisée. Ceci change l'état de 2 pages de grande taille en état alloué. Nous pouvons le vérifier via /proc/meminfo, la ligne HugePages_Free affichant bien 2.

#cat /proc/meminfo | grep Huge
HugePages_Total:     4
HugePages_Free:      2
HugePages_Rsvd:      0
Hugepagesize:     4096 kB

Le message suivant apparaît maintenant

Appuyez sur une touche pour lire la mémoire

Si nous pressons une touche, le programme contrôlera que les données écrites sont bien présentes dans la zone mémoire HugeTLB. Si tout va bien, nous obtiendrons le message :

Lecture HugeTLB réussie :-)

Gestion interne de la pagination étendue

Au sein du noyau Linux, la gestion des pages mémoire de grande taille fait l'objet de deux entités. La première constitue un ensemble global de pages de grande taille allouées et réservées pour fournir le support de pagination étendue aux applications. Cet ensemble global est construit en allouant des pages de mémoire physique contiguës de grande taille, en utilisant les fonctions habituelles d'allocation mémoire du noyau. La seconde entité concernée dans le noyau lui-même alloue les pages larges depuis cet ensemble global aux applications qui le demandent.

Nous verrons tout d'abord comment les pages de grande taille sont initialisées et comment l'ensemble global de pages est construit. Puis nous verrons comment les applications peuvent utiliser la mémoire partagée pour tirer partie des pages larges et comment les pages de mémoire physique sont allouées au moyen de fautes de pagination. Nous ne parcourerons toutefois pas le code ligne par ligne, nous identifierons plutôt les principales sections relatives à la pagination étendue.

Initialisation des pages de grande taille

Dans le code source du noyau Linux (fichier mm/hugetlb.c), nous trouvons la fonction hugetlb_init qui alloue plusieurs pages contiguës de mémoire physique de taille normale pour former un groupe utilisable pour des pages de grande taille. Le nombre de pages allouées de la sorte dépend de la variable max_huge_pages. Ce nombre peut être transmis en ligne de commande au noyau en utilisant le paramètre hugepages. La taille d'une page large dépend de la macro HUGETLB_PAGE_ORDER, dépendant elle-même de la macro HPAGE_SHIFT. Par exemple, cette macro prend la valeur 22 (lorsque l'extension d'adresse physique PAE n'est pas activée) sur les architectures de type x86. Cela signifie que les pages larges seront de taille 4 Mo. Notez que cela dépend de l'architecture et des tailles de pages supportées.

Les pages allouées comme précedemment sont mises dans des listes hugepage_freelists pour chaque noeud, selon la source d'allocation, par la fonction enqueue_huge_page. Chaque noeud mémoire (dans l'architecture NUMA, accès mémoire non uniforme) correspond à une seule des hugepage_freelists. Quand les pages de grande taille sont allouées dynamiquement comme dans l'exemple (en utilisant la commande echo vers /proc), ou par une autre méthode dynamique, une séquence similaire d'évènements se déroule, comme expliqué pour l'allocation statique de grandes pages.

En vue de l'utilisation d'une zone de mémoire partagée, nous devons la créer. Ceci est réalisé, comme vu ci-dessus, par un appel système à shmget. Cet appel invoque la fonction du noyau sys_shmget qui à son tour appelle newseg. Dans newseg, un contrôle est effectué pour confirmer que l'utilisateur a demandé la création d'une zone de mémoire partagée HugeTLB. Si l'utilisateur a spécifié le drapeau SHM_HUGETLB, alors les opérations correspondant à cette structure de fichier seront assignées à hugetlb_file_operations. Les pages de grande taille sont réservées par la fonction hugetlb_reserve_pages qui incrèmente le compteur de pages réservées resv_huge_pages, dont le contenu est affiché comme HugePages_Rsvd lors de la consultation par /proc.

Lorsque le système appelle sys_shmat, un test d'alignement d'adresse et d'autres tests de validité sont effectués via la fonction hugetlb_get_unmapped_area.

Fautes de pagination et allocation des pages physiques

Lorsqu'une faute de pagination intervient, la VMA (zone de mémoire virtuelle) correspondant à l'adresse est trouvée. La zone VMA associée à une région de mémoire partagée HugeTLB aura le drapeau vma->vm_flags positionné à VM_HUGETLB, ce qui est détecté en appelant is_vm_hugetlb_page. Lorsqu'une VMA de type HugeTLB est identifiée, la fonction hugetlb_fault est appelée. Cette procédure positionne le drapeau de page large dans le répertoire des pages puis alloue une page de grande taille par un mécanisme copy-on-write (copie sur écriture optimisée) à partir de l'ensemble global de pages de grandes tailles initialisé auparavant. La page de grande taille elle-même est repérée au niveau matériel en positionnant le drapeau _PAGE_PSE dans le PGD, répertoire global des pages (7ème bit, en partant du bit 0, dans le cas x86 sans extension PAE).

Aller plus loin

Une documentation détaillée ainsi que des exemples avancés sont disponibles dans le fichier documentation /vm/hugetlbpage.txt fourni avec le code source du noyau Linux.

La fonctionnalité HugeTLB du noyau n'est pas transparente pour les applications, dans ce sens qu'elles doivent être explicitement modifiées pour en profiter (c'est-à-dire. ajout du code source pour utiliser la mémoire partagée ou le système de fichiers HugeTLB). Pour ceux qui seraient intéressés par une implémentation transparente du support des pages de grande taille, une recherche sur internet de « Transparent superpages » vous listera des sites webcontenant des détails sur ces implémentations.

Liens

Conclusion

Nous avons vu comment le noyau Linux fournit aux applications la possibilité d'utiliser la pagination étendue. Nous avons examiné les méthodes pour activer et utiliser ces pages mémoire de grande taille. Enfin, nous avons étudié l'implémentation interne de HugeTLB dans le noyau.

Remerciements

Je voudrais remercier sincèrement Kenneth Chen pour m'avoir éclairé sur le code source HugeTLB, avoir répondu à mes questions avec patience, et relu le brouillon initial de cet article. Merci également à Pramode Sir, Badri, Malay, Shijesta et Chitkala pour les relectures et commentaires.

Krishnakumar aime « bidouiller » le noyau Linux. Il travaille pour Hewlett-Packard® et est également titulaire d'une maîtrise de technologie du Collège gouvernemental d'Ingénierie de Thrissur (Inde). Ses centres d'intérêt incluent le calcul intensif, les systèmes de fichiers parallèles, la virtualisation et les interconnections hautes performances.

Adaptation française de la Gazette Linux

L'adaptation française de ce document a été réalisée dans le cadre du Projet de traduction de la Gazette Linux.

Vous pourrez lire d'autres articles traduits et en apprendre plus sur ce projet en visitant notre site : http://wiki.traduc.org/Gazette_Linux.

Si vous souhaitez apporter votre contribution, n'hésitez pas à nous rejoindre, nous serons heureux de vous accueillir.