Adaptation française: Nicolas Provost
Relecture de la version française : Deny
Copyright © 2008 R. Krishnakumar
Copyright © 2009 Nicolas Provost
Copyright © 2009 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
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.
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.
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.
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
.
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 :-)
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.
Dans le code source du noyau Linux
(fichier
mm/hugetlb.c
), nous trouvons la fonction
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
hugetlb_init
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
.
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).
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.
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.
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.
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.