Code: Select all
mmap()Code: Select all
munmap()Code: Select all
msync()Code: Select all
mremap()1. Introduction
Le mécanisme de memory mapping permet d'associer une région de l'espace d'adressage virtuel d'un processus à une ressource.
Cette ressource peut être :
- une portion de fichier ;
- une zone mémoire anonyme qui n'est associée à aucun fichier ;
- une région partagée entre plusieurs processus ;
- une zone servant à réaliser des entrées/sorties mémoire-mappées.
Code: Select all
mmap()Un mapping mémoire permet donc au programme de manipuler directement des octets via des adresses mémoire, sans nécessairement employer
Code: Select all
read()Code: Select all
write()Exemple mental :
Code: Select all
Fichier sur disque
|
v
Noyau / cache de pages
|
v
Espace virtuel du processus
|
v
pointeur retourné par mmap()
Les mappings sont utilisés pour :
- charger efficacement des fichiers ;
- partager de la mémoire entre processus ;
- implémenter des allocateurs ;
- charger des bibliothèques partagées ;
- charger les segments d'un exécutable ;
- faire du memory-mapped I/O ;
- partager une zone mémoire entre parent et enfant après ;
Code: Select all
fork() - réduire le nombre d'appels .
Code: Select all
read()/write()
2. Deux grandes familles de mappings
Il existe deux grandes catégories.
2.1 Mapping de fichier
Un mapping de fichier associe une région d'un fichier à une région de mémoire virtuelle.
Le noyau charge les pages nécessaires à la demande.
Le programme peut ensuite accéder au fichier avec de simples lectures/écritures mémoire.
Exemple :
Code: Select all
char *p = mmap(...);
printf("%c\n", p[0]);
Code: Select all
p[0]2.2 Mapping anonyme
Un mapping anonyme n'est associé à aucun fichier.
Les pages sont initialement remplies de zéros.
Il peut servir comme une sorte de mémoire virtuelle allouée directement auprès du noyau.
Exemple typique :
Code: Select all
void *p = mmap(NULL,
4096,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0);
3. Mapping privé et mapping partagé
Indépendamment du fait qu'un mapping soit basé sur un fichier ou anonyme, il peut être privé ou partagé.
3.1
Code: Select all
MAP_PRIVATECode: Select all
MAP_PRIVATELes modifications réalisées par le processus ne sont pas propagées vers le fichier sous-jacent.
Le système utilise typiquement le mécanisme Copy-On-Write.
Concept :
Code: Select all
Processus A
|
+---- page partagée initialement
|
+---- modification
|
v
copie privée de la page
Lors d'une écriture, le noyau crée une copie privée pour le processus.
Conséquences :
- les changements ne sont pas visibles dans le fichier ;
- les changements ne sont pas nécessairement visibles par d'autres processus ;
- ce mode est utilisé notamment pour charger des exécutables et bibliothèques.
Code: Select all
MAP_SHAREDCode: Select all
MAP_SHAREDLes modifications effectuées sur le mapping sont visibles par les autres processus qui mappent la même région en mode partagé.
Dans le cas d'un fichier, les changements peuvent également être propagés vers le fichier.
Exemple :
Code: Select all
Processus A ----\
+---- mêmes pages physiques ---- fichier
Processus B ----/
Attention :
Code: Select all
MAP_SHAREDSi plusieurs processus écrivent simultanément, il faut utiliser par exemple :
- sémaphores ;
- mutex inter-processus ;
- futex ;
- verrous de fichiers ;
- atomiques appropriés.
4. L'appel système
Code: Select all
mmap()Prototype :
Code: Select all
#include <sys/mman.h>
void *mmap(void *addr,
size_t length,
int prot,
int flags,
int fd,
off_t offset);
- succès : adresse de début du mapping ;
- échec : .
Code: Select all
MAP_FAILED
Code: Select all
void *p = mmap(...);
if (p == MAP_FAILED) {
perror("mmap");
}
Code: Select all
NULLCode: Select all
MAP_FAILEDCode: Select all
(void *) -1
5. Paramètre
Code: Select all
addrCode: Select all
addrDans la majorité des cas :
Code: Select all
addr = NULL
Exemple :
Code: Select all
void *p = mmap(NULL, 4096, ...);
Si
Code: Select all
addrCode: Select all
NULLSans
Code: Select all
MAP_FIXEDBonne pratique :
- utiliser sauf besoin très spécifique ;
Code: Select all
NULL - éviter de choisir soi-même des adresses fixes.
6. Paramètre
Code: Select all
lengthCode: Select all
lengthExemple :
Code: Select all
size_t length = 4096;
mmap(NULL, length, ...);
La taille de page peut être obtenue par :
Code: Select all
long page_size = sysconf(_SC_PAGESIZE);
Code: Select all
getpagesize();
Code: Select all
page_size = 4096
7. Paramètre
Code: Select all
protCode: Select all
protLes valeurs principales sont :
- : aucun accès autorisé ;
Code: Select all
PROT_NONE - : lecture autorisée ;
Code: Select all
PROT_READ - : écriture autorisée ;
Code: Select all
PROT_WRITE - : exécution autorisée.
Code: Select all
PROT_EXEC
Code: Select all
flagsExemple :
Code: Select all
PROT_READ | PROT_WRITE
Exemple lecture seule :
Code: Select all
PROT_READ
Code: Select all
PROT_READ | PROT_EXEC
Code: Select all
PROT_NONE
Code: Select all
PROT_NONEUne tentative d'accès à une page protégée peut provoquer
Code: Select all
SIGSEGVImportant :
les permissions demandées doivent être compatibles avec la manière dont le fichier a été ouvert.
Par exemple, un mapping partagé modifiable nécessite normalement un descripteur ouvert avec des droits d'écriture adaptés.
8. Paramètre
Code: Select all
flagsCode: Select all
flagsIl faut normalement choisir au moins un des deux :
- ;
Code: Select all
MAP_PRIVATE - .
Code: Select all
MAP_SHARED
Code: Select all
flagsExemple :
Code: Select all
MAP_PRIVATE | MAP_ANONYMOUS
9. Paramètre
Code: Select all
fdPour un mapping de fichier,
Code: Select all
fdCode: Select all
open()Exemple :
Code: Select all
int fd = open("data.bin", O_RDONLY);
void *p = mmap(NULL,
length,
PROT_READ,
MAP_PRIVATE,
fd,
0);
Code: Select all
mmap()Exemple :
Code: Select all
close(fd);
La durée de vie du mapping n'est donc pas liée à celle du descripteur de fichier.
10. Paramètre
Code: Select all
offsetCode: Select all
offsetExemple :
Code: Select all
offset = 0
L'
Code: Select all
offsetEn pratique, il doit être un multiple de la taille de page.
Exemple :
Code: Select all
long ps = sysconf(_SC_PAGESIZE);
off_t offset = 2 * ps;
11. Exemple simple : mapper un fichier en lecture
Code: Select all
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
int main(void)
{
int fd = open("test.txt", O_RDONLY);
if (fd == -1) {
perror("open");
return EXIT_FAILURE;
}
struct stat st;
if (fstat(fd, &st) == -1) {
perror("fstat");
close(fd);
return EXIT_FAILURE;
}
if (st.st_size == 0) {
printf("Fichier vide\n");
close(fd);
return EXIT_SUCCESS;
}
char *addr = mmap(NULL,
st.st_size,
PROT_READ,
MAP_PRIVATE,
fd,
0);
if (addr == MAP_FAILED) {
perror("mmap");
close(fd);
return EXIT_FAILURE;
}
close(fd);
write(STDOUT_FILENO, addr, st.st_size);
if (munmap(addr, st.st_size) == -1) {
perror("munmap");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
- ouvre le fichier ;
Code: Select all
open() - récupère sa taille ;
Code: Select all
fstat() - crée le mapping ;
Code: Select all
mmap() - le peut être fermé ;
Code: Select all
fd - le programme lit les données via ;
Code: Select all
addr - détruit le mapping.
Code: Select all
munmap()
12.
Code: Select all
munmap()Prototype :
Code: Select all
#include <sys/mman.h>
int munmap(void *addr, size_t length);
- : succès ;
Code: Select all
0 - : erreur.
Code: Select all
-1
Code: Select all
if (munmap(addr, length) == -1) {
perror("munmap");
}
Code: Select all
munmap()Accéder à la zone après
Code: Select all
munmap()Code: Select all
SIGSEGVIl est possible de supprimer :
- tout le mapping ;
- une partie du mapping ;
- plusieurs pages contiguës.
13. Mapping de fichier : procédure générale
La procédure classique est :
- 1. ouvrir le fichier avec ;
Code: Select all
open() - 2. récupérer éventuellement sa taille avec ;
Code: Select all
fstat() - 3. appeler ;
Code: Select all
mmap() - 4. utiliser la région comme une zone mémoire ;
- 5. éventuellement appeler ;
Code: Select all
msync() - 6. appeler ;
Code: Select all
munmap() - 7. fermer le s'il ne l'a pas déjà été.
Code: Select all
fd
14. Memory-mapped I/O
Avec un fichier mappé, les données peuvent être manipulées comme de la mémoire.
Au lieu de :
Code: Select all
read(fd, buffer, size);
Code: Select all
char *addr = mmap(...);
printf("%c\n", addr[100]);
Code: Select all
addr[100] = 'A';
- le chargement des pages ;
- les page faults ;
- le cache de pages ;
- la propagation éventuelle vers le fichier.
- interface simple pour accès aléatoires ;
- moins de copies explicites ;
- peut éviter certains appels ;
Code: Select all
read()/write() - partage naturel des pages du cache ;
- très pratique pour les gros fichiers structurés.
- gestion des limites de fichier ;
- possible lors de certains accès invalides ;
Code: Select all
SIGBUS - page faults moins visibles ;
- synchronisation plus délicate ;
- pas toujours plus performant pour de petits accès séquentiels ;
- plus difficile à maîtriser précisément que .
Code: Select all
read()/write()
15. Pourquoi
Code: Select all
mmap()Avec
Code: Select all
read()Code: Select all
fichier
|
v
cache noyau
|
v
buffer utilisateur
Code: Select all
mmap()Code: Select all
fichier
|
v
cache noyau / pages
|
v
pages directement visibles dans l'espace virtuel
Mais
Code: Select all
mmap()Le gain dépend :
- de la taille des données ;
- du type d'accès ;
- du nombre d'accès aléatoires ;
- de la pression mémoire ;
- du comportement du cache ;
- du coût des page faults.
16.
Code: Select all
MAP_SHAREDExemple :
Code: Select all
char *addr = mmap(NULL,
size,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd,
0);
addr[0] = 'X';
Les autres processus mappant la même zone en
Code: Select all
MAP_SHAREDImportant :
la visibilité mémoire et l'écriture physique sur disque sont deux choses différentes.
Le noyau peut garder les pages modifiées dans le cache avant de les écrire réellement sur le stockage.
17.
Code: Select all
msync()Prototype :
Code: Select all
#include <sys/mman.h>
int msync(void *addr, size_t length, int flags);
- : succès ;
Code: Select all
0 - : erreur.
Code: Select all
-1
Code: Select all
msync()Flags principaux :
- ;
Code: Select all
MS_SYNC - ;
Code: Select all
MS_ASYNC - .
Code: Select all
MS_INVALIDATE
Code: Select all
MS_SYNCDemande une synchronisation synchrone.
L'appel attend que les données concernées soient écrites conformément au comportement du système.
Exemple :
Code: Select all
msync(addr, length, MS_SYNC);
Code: Select all
MS_ASYNCDemande une synchronisation asynchrone.
L'appel peut retourner sans attendre la fin physique de l'écriture.
Code: Select all
MS_INVALIDATEDemande d'invalider les copies mises en cache lorsque cela est applicable, afin que les vues soient cohérentes avec les données synchronisées.
Exemple :
Code: Select all
if (msync(addr, length, MS_SYNC) == -1) {
perror("msync");
}
18.
Code: Select all
MAP_PRIVATEExemple :
Code: Select all
char *addr = mmap(NULL,
size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE,
fd,
0);
addr[0] = 'X';
Concept :
Code: Select all
avant écriture :
Processus
|
v
page fichier
après écriture :
Processus
|
v
copie privée modifiée
Fichier
|
v
page originale
19. Mapping anonyme avec
Code: Select all
MAP_ANONYMOUSCode: Select all
MAP_ANONYMOUSExemple :
Code: Select all
void *addr = mmap(NULL,
4096,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0);
if (addr == MAP_FAILED) {
perror("mmap");
}
Pour les mappings anonymes :
- est typiquement
Code: Select all
fd;Code: Select all
-1 - est
Code: Select all
offset;Code: Select all
0 - aucun fichier n'est associé.
Code: Select all
MAP_ANONCode: Select all
MAP_ANONYMOUS20.
Code: Select all
/dev/zeroHistoriquement, certaines applications créaient des mappings anonymes en ouvrant
Code: Select all
/dev/zero.Code: Select all
int fd = open("/dev/zero", O_RDWR);
void *p = mmap(NULL,
size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE,
fd,
0);
Code: Select all
MAP_ANONYMOUS21. Mapping anonyme privé
Code: Select all
MAP_PRIVATE | MAP_ANONYMOUSExemple :
Code: Select all
size_t size = 1024 * 1024;
void *p = mmap(NULL,
size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0);
- n'est pas associée à un fichier ;
- est initialisée à zéro ;
- est privée au processus ;
- peut être libérée avec .
Code: Select all
munmap()
Code: Select all
malloc()Code: Select all
mmap()22. Mapping anonyme partagé
Un mapping anonyme peut être partagé.
Exemple :
Code: Select all
int *value = mmap(NULL,
sizeof(int),
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS,
-1,
0);
Code: Select all
fork()Après
Code: Select all
fork()Code: Select all
MAP_SHARED23. Exemple IPC parent/enfant avec
Code: Select all
mmap()Code: Select all
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
int main(void)
{
int *value = mmap(NULL,
sizeof(int),
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS,
-1,
0);
if (value == MAP_FAILED) {
perror("mmap");
return EXIT_FAILURE;
}
*value = 1;
pid_t pid = fork();
if (pid == -1) {
perror("fork");
munmap(value, sizeof(int));
return EXIT_FAILURE;
}
if (pid == 0) {
(*value)++;
printf("Enfant : %d\n", *value);
munmap(value, sizeof(int));
_exit(EXIT_SUCCESS);
}
wait(NULL);
printf("Parent : %d\n", *value);
munmap(value, sizeof(int));
return EXIT_SUCCESS;
}
Code: Select all
valeur initiale = 1
enfant incrémente => 2
parent voit => 2
Code: Select all
MAP_PRIVATE24.
Code: Select all
mmap()Code: Select all
fork()Lors d'un
Code: Select all
fork()Le comportement dépend du type :
Code: Select all
MAP_PRIVATE- les pages utilisent Copy-On-Write ;
- les modifications privées ne sont pas partagées.
Code: Select all
MAP_SHARED- les processus peuvent continuer à partager les pages ;
- les modifications peuvent être visibles entre eux.
Code: Select all
mmap()25. Durée de vie d'un mapping
Un mapping reste normalement présent :
- jusqu'à ;
Code: Select all
munmap() - jusqu'à la fin du processus ;
- ou jusqu'à ce qu'il soit remplacé/modifié par une opération appropriée.
Code: Select all
exec()Les mappings précédents ne continuent donc normalement pas après
Code: Select all
exec()26. Alignement et taille de page
Les mappings sont gérés par pages.
Taille de page :
Code: Select all
long ps = sysconf(_SC_PAGESIZE);
Code: Select all
offsetSi on souhaite mapper un octet situé au milieu d'une page, on mappe généralement à partir de la page précédente puis on ajoute un décalage dans le pointeur.
Exemple conceptuel :
Code: Select all
wanted_offset = 5000
page_size = 4096
aligned_offset = 4096
delta = 5000 - 4096 = 904
Code: Select all
char *wanted = mapped + 904;
27. Cas limite : taille non multiple de la page
Supposons une page de 4096 octets.
On demande :
Code: Select all
mmap(..., 6000, ...);
La dernière page du mapping peut donc contenir une portion qui dépasse la taille logique demandée.
Il ne faut pas en conclure que toute la zone arrondie peut être utilisée librement.
Le programme ne doit accéder qu'à la région valide.
Les comportements au-delà des limites du fichier peuvent conduire à des fautes mémoire, notamment
Code: Select all
SIGBUS28.
Code: Select all
SIGSEGVCode: Select all
SIGBUSDeux signaux sont particulièrement importants avec
Code: Select all
mmap()Code: Select all
SIGSEGVTypiquement :
- accès à une adresse non mappée ;
- violation des permissions de la page ;
- écriture dans une zone non inscriptible ;
- exécution dans une zone non exécutable.
Code: Select all
SIGBUSPeut notamment apparaître lorsqu'on accède à une partie d'un mapping dont la correspondance avec le fichier n'est plus valide.
Exemple :
- fichier tronqué après ;
Code: Select all
mmap() - accès à une page dépassant réellement la fin du fichier.
29. Protections et mode d'ouverture du fichier
Les protections demandées doivent être compatibles avec le
Code: Select all
fdExemple :
Code: Select all
int fd = open("data.bin", O_RDONLY);
Code: Select all
PROT_READUn mapping
Code: Select all
MAP_SHAREDCode: Select all
PROT_WRITECode: Select all
fdIl faut distinguer :
- protection de la mémoire : ;
Code: Select all
PROT_* - mode d'ouverture du fichier : ;
Code: Select all
O_RDONLY/O_RDWR - type de mapping : .
Code: Select all
MAP_PRIVATE/MAP_SHARED
30. Flags
Code: Select all
mmap()Linux propose plusieurs
Code: Select all
flagsIls sont généralement spécifiques au système et doivent être utilisés avec prudence.
31.
Code: Select all
MAP_FIXEDCode: Select all
MAP_FIXEDExemple conceptuel :
Code: Select all
mmap(addr,
size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
-1,
0);
Si une région existe déjà à cette adresse, elle peut être remplacée.
Risques :
- écraser un mapping existant ;
- corrompre l'espace d'adressage ;
- provoquer un crash ;
- casser des bibliothèques ou structures internes.
Code: Select all
MAP_FIXEDApproche plus sûre :
- faire d'abord un mapping normal ;
- obtenir une zone libre ;
- ne recourir à que si nécessaire.
Code: Select all
MAP_FIXED
32.
Code: Select all
MAP_LOCKEDCode: Select all
MAP_LOCKEDCode: Select all
mlock()Objectif :
réduire les risques que les pages soient déplacées vers le swap.
Cela peut nécessiter des privilèges ou être soumis à des limites de ressources.
Ce flag peut être utile dans des applications temps réel ou sensibles à la latence, mais il ne faut pas verrouiller de grosses quantités de mémoire sans raison.
33.
Code: Select all
MAP_POPULATECode: Select all
MAP_POPULATEObjectif :
réduire les page faults futurs lors du premier accès.
Exemple :
Code: Select all
mmap(NULL,
size,
PROT_READ,
MAP_PRIVATE | MAP_POPULATE,
fd,
0);
Code: Select all
mmap()Avantage :
latence plus prévisible lors des accès suivants.
Inconvénient :
Code: Select all
mmap()34.
Code: Select all
MAP_NORESERVECode: Select all
MAP_NORESERVELinux peut utiliser l'overcommit : il autorise parfois des allocations virtuelles plus grandes que la RAM + swap réellement disponibles.
Avec
Code: Select all
MAP_NORESERVEConséquence :
Code: Select all
mmap()Si le système manque réellement de mémoire lors de l'utilisation, cela peut conduire à l'intervention de l'OOM killer.
35. Overcommit mémoire sous Linux
Linux permet de réserver virtuellement plus de mémoire que la mémoire physique disponible.
Pourquoi ?
Beaucoup de programmes :
- réservent de grosses zones sans tout utiliser ;
- utilisent des pages Copy-On-Write ;
- n'accèdent jamais à toutes les pages réservées.
Code: Select all
mmap()Code: Select all
malloc()L'overcommit repose sur l'idée :
mémoire virtuelle réservée != mémoire physique réellement consommée
Exemple :
un programme mappe 10 Go,
mais n'accède qu'à 100 Mo.
Le système ne doit pas nécessairement fournir 10 Go de RAM immédiatement.
36.
Code: Select all
vm.overcommit_memorySous Linux, la politique d'overcommit est configurable via :
Code: Select all
/proc/sys/vm/overcommit_memory
- : politique heuristique ;
Code: Select all
0 - : overcommit permissif ;
Code: Select all
1 - : politique stricte basée sur une limite de commit.
Code: Select all
2
On peut consulter :
Code: Select all
cat /proc/sys/vm/overcommit_memory
37.
Code: Select all
vm.overcommit_ratioLorsque la politique stricte est utilisée, Linux peut tenir compte notamment de :
Code: Select all
/proc/sys/vm/overcommit_ratio
Selon les versions modernes du noyau, d'autres paramètres peuvent également intervenir.
Il ne faut donc pas considérer une formule simplifiée comme universelle pour toutes les versions.
38. OOM killer
OOM signifie Out Of Memory.
Si le système n'a plus assez de mémoire pour satisfaire les besoins réels, Linux peut déclencher l'OOM killer.
Le noyau sélectionne alors un ou plusieurs processus à tuer afin de libérer de la mémoire.
Le choix dépend de plusieurs facteurs internes.
Important :
le fait que
Code: Select all
mmap()Code: Select all
malloc()39.
Code: Select all
mremap()Code: Select all
mremap()Elle permet de modifier la taille et éventuellement l'emplacement d'un mapping existant.
Prototype typique :
Code: Select all
#define _GNU_SOURCE
#include <sys/mman.h>
void *mremap(void *old_address,
size_t old_size,
size_t new_size,
int flags,
...);
- succès : nouvelle adresse ;
- échec : .
Code: Select all
MAP_FAILED
40. Utilité de
Code: Select all
mremap()Supposons :
Code: Select all
ancienne taille = 4096
nouvelle taille = 8192
Code: Select all
munmap()
+
nouveau mmap()
+
copie des données
Code: Select all
mremap()Exemple :
Code: Select all
void *newp = mremap(p,
old_size,
new_size,
MREMAP_MAYMOVE);
if (newp == MAP_FAILED) {
perror("mremap");
}
p = newp;
41.
Code: Select all
MREMAP_MAYMOVECode: Select all
MREMAP_MAYMOVESans ce flag, l'agrandissement peut échouer si la région ne peut pas être étendue à son emplacement actuel.
Important :
Après
Code: Select all
mremap()Exemple dangereux :
Code: Select all
char *base = ...
char *element = base + 100;
base = mremap(...);
Code: Select all
elementBonne pratique :
stocker des offsets plutôt que des pointeurs absolus lorsqu'un mapping peut être déplacé.
42.
Code: Select all
MREMAP_FIXEDSur Linux,
Code: Select all
MREMAP_FIXEDIl est utilisé avec
Code: Select all
MREMAP_MAYMOVEComme
Code: Select all
MAP_FIXEDUne mauvaise adresse peut remplacer une région existante.
À réserver aux cas avancés.
43. Mappings non linéaires
Un mapping classique est linéaire.
Exemple :
Code: Select all
adresse virtuelle page 0 -> fichier page 0
adresse virtuelle page 1 -> fichier page 1
adresse virtuelle page 2 -> fichier page 2
Exemple conceptuel :
Code: Select all
VMA page 0 -> fichier page 2
VMA page 1 -> fichier page 1
VMA page 2 -> fichier page 0
Code: Select all
remap_file_pages()44.
Code: Select all
remap_file_pages()Prototype historique :
Code: Select all
int remap_file_pages(void *addr,
size_t size,
int prot,
size_t pgoff,
int flags);
Elle permettait de créer des mappings non linéaires sans multiplier les VMA.
Cependant, dans les Linux modernes,
Code: Select all
remap_file_pages()Pour du nouveau code, il vaut mieux éviter de bâtir une architecture autour de cette API.
Le concept reste néanmoins important pour comprendre le chapitre et l'évolution du sous-système mémoire Linux.
45. VMA : Virtual Memory Area
Le noyau représente les différentes régions de l'espace virtuel d'un processus sous forme de zones mémoire virtuelles.
On parle souvent de VMA : Virtual Memory Area.
Une VMA décrit une région ayant des caractéristiques communes :
- adresses de début et de fin ;
- permissions ;
- type de mapping ;
- fichier associé éventuellement ;
- ;
Code: Select all
offset - .
Code: Select all
flags
Code: Select all
mmap()46. Observer les mappings d'un processus
Sous Linux :
Code: Select all
/proc/PID/maps
Code: Select all
cat /proc/self/maps
Code: Select all
cat /proc/1234/maps
- plages d'adresses ;
- permissions ;
- ;
Code: Select all
offset - device ;
- inode ;
- fichier associé.
Code: Select all
r-xp
r = readable
w = writable
x = executable
p = private
s = shared
Code: Select all
mmap()47. Exemple d'entrée
Code: Select all
/proc/PID/mapsExemple fictif :
Code: Select all
7f1234000000-7f1234021000 rw-p 00000000 00:00 0
- 7f1234000000 : adresse de début ;
- 7f1234021000 : adresse de fin ;
- : lecture + écriture, privé ;
Code: Select all
rw-p - 00000000 : ;
Code: Select all
offset - 00:00 : device ;
- 0 : inode.
Code: Select all
7f...-7f... r-xp ... /usr/lib/libc.so.6
48. Relation avec les exécutables et bibliothèques
Lorsqu'un programme est lancé, Linux mappe plusieurs segments dans l'espace virtuel :
- code ;
- données ;
- bibliothèques partagées ;
- heap ;
- stack ;
- zones anonymes ;
- vdso/vvar selon l'architecture.
Code: Select all
r-x
Code: Select all
rw-
Code: Select all
mmap()49.
Code: Select all
mmap()Code: Select all
malloc()Code: Select all
malloc()Code: Select all
mmap()Code: Select all
malloc()- historiquement ;
Code: Select all
brk()/sbrk() - pour certaines allocations.
Code: Select all
mmap()
Code: Select all
malloc() != mmap()
Code: Select all
malloc()Code: Select all
mmap()50.
Code: Select all
mmap()Exemple :
Code: Select all
size_t size = 4096;
int *array = mmap(NULL,
size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0);
if (array == MAP_FAILED) {
perror("mmap");
}
array[0] = 42;
munmap(array, size);
contrôle direct sur :
- permissions ;
- taille ;
- type de mapping ;
- partage ;
- adresse ;
- .
Code: Select all
flags
beaucoup moins pratique qu'un allocateur généraliste pour de petites allocations.
51.
Code: Select all
mmap()L'ASLR randomise certaines adresses de l'espace virtuel.
Lorsque
Code: Select all
addr = NULLIl ne faut donc jamais supposer qu'un
Code: Select all
mmap(NULL, ...)C'est une raison supplémentaire pour éviter les pointeurs absolus persistants.
52. Fermeture du
Code: Select all
fdCode: Select all
mmap()Exemple :
Code: Select all
int fd = open(...);
void *p = mmap(..., fd, 0);
close(fd);
Code: Select all
mmap()Pourquoi ?
Le noyau maintient les références internes nécessaires au mapping.
Le
Code: Select all
fd53. Troncature d'un fichier mappé
Attention à
Code: Select all
ftruncate()Si un fichier est réduit alors qu'une partie supérieure est encore mappée, l'accès à une région qui n'existe plus réellement dans le fichier peut provoquer
Code: Select all
SIGBUSExemple :
Code: Select all
mapping : 0 -> 1 Mo
ftruncate(fd, 4096)
Code: Select all
addr[500000]Il faut coordonner les opérations de redimensionnement avec les utilisateurs du mapping.
54. Agrandir un fichier avant un mapping writable
Si on veut créer un fichier de 1 Mo puis le mapper :
Code: Select all
int fd = open("data.bin", O_RDWR | O_CREAT, 0644);
if (ftruncate(fd, 1024 * 1024) == -1) {
perror("ftruncate");
}
char *p = mmap(NULL,
1024 * 1024,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd,
0);
55. Synchronisation inter-processus
Code: Select all
MAP_SHAREDExemple dangereux :
Code: Select all
Processus A :
counter++;
Processus B :
counter++;
Code: Select all
counter++Solutions :
- sémaphore System V ;
- sémaphore POSIX ;
- avec
Code: Select all
pthread_mutex_t;Code: Select all
PTHREAD_PROCESS_SHARED - atomiques C ;
- futex ;
- autres primitives appropriées.
56. Mutex partagé dans
Code: Select all
mmap()Un
Code: Select all
pthread_mutex_tCode: Select all
MAP_SHAREDIl faut configurer son attribut :
Code: Select all
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
Exemple structure :
Code: Select all
struct Shared {
pthread_mutex_t mutex;
int counter;
};
57. Exemple : fichier partagé entre processus
Processus A :
Code: Select all
int fd = open("shared.bin", O_RDWR);
int *p = mmap(NULL,
sizeof(int),
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd,
0);
*p = 123;
msync(p, sizeof(int), MS_SYNC);
C'est un mécanisme IPC persistant puisque le fichier continue d'exister après la fin des processus.
58. Mapping anonyme vs shared memory POSIX/System V
Mapping anonyme
Code: Select all
MAP_SHARED- très pratique entre processus apparentés après ;
Code: Select all
fork() - pas de nom global ;
- pas d'objet fichier classique.
- ;
Code: Select all
shm_open() - ;
Code: Select all
ftruncate() - ;
Code: Select all
mmap() - objet nommable ;
- pratique entre processus non apparentés.
- ;
Code: Select all
shmget() - ;
Code: Select all
shmat() - ;
Code: Select all
shmdt() - .
Code: Select all
shmctl()
59.
Code: Select all
mmap()Code: Select all
shm_open()Exemple conceptuel POSIX :
Code: Select all
int fd = shm_open("/demo",
O_CREAT | O_RDWR,
0600);
ftruncate(fd, 4096);
void *p = mmap(NULL,
4096,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd,
0);
Code: Select all
mmap()60. Protection temporaire avec
Code: Select all
PROT_NONEUne technique utile consiste à créer ou modifier une région en
Code: Select all
PROT_NONEExemple :
Code: Select all
mmap(NULL,
size,
PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0);
Cela permet notamment :
- guard pages ;
- détection de débordement de stack ;
- zones réservées mais non accessibles.
Code: Select all
mprotect()61. W^X : Write XOR Execute
En sécurité moderne, on évite idéalement les pages simultanément :
Code: Select all
PROT_WRITE | PROT_EXEC
- écrivable ;
- ou exécutable ;
- mais pas les deux simultanément.
Code: Select all
écriture :
PROT_READ | PROT_WRITE
puis :
mprotect(..., PROT_READ | PROT_EXEC)
62. Erreurs fréquentes
- tester contre
Code: Select all
mmap()au lieu deCode: Select all
NULL;Code: Select all
MAP_FAILED - oublier ;
Code: Select all
munmap() - utiliser un non aligné ;
Code: Select all
offset - écrire dans un mapping ;
Code: Select all
PROT_READ - confondre et
Code: Select all
MAP_PRIVATE;Code: Select all
MAP_SHARED - croire que fournit une synchronisation ;
Code: Select all
MAP_SHARED - accéder après ;
Code: Select all
munmap() - utiliser un pointeur ancien après ;
Code: Select all
mremap() - mapper au-delà de la taille du fichier et accéder à des pages invalides ;
- oublier avant de mapper un nouveau fichier writable ;
Code: Select all
ftruncate() - utiliser sans maîtriser les conséquences ;
Code: Select all
MAP_FIXED - supposer que charge immédiatement toutes les pages en RAM ;
Code: Select all
mmap() - supposer qu'un réussi garantit que toute la mémoire pourra être matérialisée ;
Code: Select all
mmap() - oublier de vérifier les valeurs de retour.
63. Lazy allocation et page faults
Un
Code: Select all
mmap()Linux utilise largement le chargement à la demande.
Exemple :
Code: Select all
void *p = mmap(NULL,
1ULL << 30,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0);
Mais toutes les pages ne sont pas nécessairement allouées physiquement immédiatement.
Lors du premier accès :
Code: Select all
p[0] = 1;
Le noyau crée ou associe alors la page nécessaire.
Page fault ne signifie donc pas forcément erreur.
Il existe des page faults normaux faisant partie du fonctionnement de la mémoire virtuelle.
64. Minor et major page faults
De manière simplifiée :
Minor fault :
la page peut être obtenue sans lecture disque coûteuse.
Major fault :
le système doit typiquement récupérer les données depuis un stockage.
Code: Select all
mmap()65. Quand utiliser
Code: Select all
mmap()Code: Select all
mmap()- gros fichiers ;
- accès aléatoires ;
- bases de données ;
- structures directement représentées en mémoire ;
- IPC par mémoire partagée ;
- chargement d'exécutables/bibliothèques ;
- allocations mémoire avancées ;
- fichiers consultés très souvent ;
- situations où l'on veut contrôler les permissions des pages.
Code: Select all
read()/write()- petits fichiers ;
- I/O séquentielles simples ;
- protocoles de streaming ;
- code où la simplicité prime ;
- cas où l'on veut contrôler précisément chaque opération d'I/O.
66. Comparaison
Code: Select all
mmap()Code: Select all
read()Code: Select all
read() :
fd -> read() -> buffer utilisateur
mmap() :
fd -> mapping -> accès mémoire
Code: Select all
read()- explicite ;
- facile à raisonner ;
- très adapté au streaming ;
- contrôle direct des tailles lues.
Code: Select all
mmap()- accès via pointeurs ;
- intégration avec mémoire virtuelle ;
- très pratique pour accès aléatoire ;
- peut simplifier certaines applications.
67. Nettoyage correct
Exemple général :
Code: Select all
int fd = open(...);
if (fd == -1)
...
void *addr = mmap(...);
if (addr == MAP_FAILED) {
close(fd);
...
}
close(fd);
/* utilisation */
if (msync(...) == -1)
...
if (munmap(addr, length) == -1)
...
68. Exemple complet : modifier un fichier avec
Code: Select all
MAP_SHAREDCode: Select all
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <string.h>
int main(void)
{
const char *path = "data.txt";
int fd = open(path, O_RDWR);
if (fd == -1) {
perror("open");
return EXIT_FAILURE;
}
struct stat st;
if (fstat(fd, &st) == -1) {
perror("fstat");
close(fd);
return EXIT_FAILURE;
}
if (st.st_size == 0) {
fprintf(stderr, "Fichier vide\n");
close(fd);
return EXIT_FAILURE;
}
char *addr = mmap(NULL,
st.st_size,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd,
0);
if (addr == MAP_FAILED) {
perror("mmap");
close(fd);
return EXIT_FAILURE;
}
close(fd);
addr[0] = 'X';
if (msync(addr, st.st_size, MS_SYNC) == -1) {
perror("msync");
munmap(addr, st.st_size);
return EXIT_FAILURE;
}
if (munmap(addr, st.st_size) == -1) {
perror("munmap");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
69. Exemple complet : mapping anonyme redimensionné avec
Code: Select all
mremap()Code: Select all
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
int main(void)
{
size_t old_size = 4096;
size_t new_size = 8192;
int *p = mmap(NULL,
old_size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0);
if (p == MAP_FAILED) {
perror("mmap");
return EXIT_FAILURE;
}
p[0] = 42;
void *tmp = mremap(p,
old_size,
new_size,
MREMAP_MAYMOVE);
if (tmp == MAP_FAILED) {
perror("mremap");
munmap(p, old_size);
return EXIT_FAILURE;
}
p = tmp;
printf("%d\n", p[0]);
if (munmap(p, new_size) == -1) {
perror("munmap");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
70. Carte mentale du chapitre
Code: Select all
mmap()
|
+-- source
| |
| +-- fichier
| |
| +-- anonyme
|
+-- visibilité
| |
| +-- MAP_PRIVATE
| | |
| | +-- Copy-On-Write
| |
| +-- MAP_SHARED
| |
| +-- IPC
| +-- fichier partagé
|
+-- protections
| |
| +-- PROT_NONE
| +-- PROT_READ
| +-- PROT_WRITE
| +-- PROT_EXEC
|
+-- gestion
| |
| +-- munmap()
| +-- msync()
| +-- mremap()
|
+-- flags Linux
|
+-- MAP_ANONYMOUS
+-- MAP_FIXED
+-- MAP_LOCKED
+-- MAP_POPULATE
+-- MAP_NORESERVE
71. Tableau mental des combinaisons principales
Mapping fichier +
Code: Select all
MAP_PRIVATE- lecture/traitement privé d'un fichier ;
- modifications non propagées comme écritures normales vers le fichier ;
- Copy-On-Write.
Code: Select all
MAP_SHARED- memory-mapped I/O ;
- IPC ;
- modifications partageables ;
- possibilité de synchronisation vers le fichier.
Code: Select all
MAP_PRIVATE- allocation mémoire privée ;
- pages initialisées à zéro.
Code: Select all
MAP_SHARED- mémoire partagée ;
- très pratique après .
Code: Select all
fork()
72. API à connaître absolument
À retenir pour ce chapitre :
- : créer un mapping ;
Code: Select all
mmap() - : supprimer un mapping ;
Code: Select all
munmap() - : synchroniser un mapping partagé avec son backing file ;
Code: Select all
msync() - : redimensionner/déplacer un mapping sur Linux ;
Code: Select all
mremap() - : ouvrir le fichier ;
Code: Select all
open() - : fermer le descripteur ;
Code: Select all
close() - : récupérer la taille du fichier ;
Code: Select all
fstat() - : modifier la taille du fichier ;
Code: Select all
ftruncate() - : obtenir la taille de page ;
Code: Select all
sysconf(_SC_PAGESIZE) - : hériter des mappings dans un processus fils.
Code: Select all
fork()
73. Constantes à connaître
Protections :
Code: Select all
PROT_NONECode: Select all
PROT_READCode: Select all
PROT_WRITECode: Select all
PROT_EXEC
Code: Select all
MAP_PRIVATECode: Select all
MAP_SHARED
Code: Select all
MAP_ANONYMOUS
Code: Select all
flagsCode: Select all
MAP_FIXEDCode: Select all
MAP_LOCKEDCode: Select all
MAP_POPULATECode: Select all
MAP_NORESERVE
Code: Select all
msyncCode: Select all
MS_SYNCCode: Select all
MS_ASYNCCode: Select all
MS_INVALIDATE
Code: Select all
mremapCode: Select all
MREMAP_MAYMOVECode: Select all
MREMAP_FIXED
74. Ce qu'il faut savoir expliquer sans documentation
Après ce chapitre, il faut être capable d'expliquer :
- ce qu'est un mapping mémoire ;
- la différence entre mapping fichier et anonyme ;
- la différence /
Code: Select all
MAP_PRIVATE;Code: Select all
MAP_SHARED - le Copy-On-Write ;
- le rôle de ;
Code: Select all
PROT_READ/WRITE/EXEC/NONE - les six paramètres de ;
Code: Select all
mmap() - pourquoi retourne
Code: Select all
mmap();Code: Select all
MAP_FAILED - pourquoi l'doit être aligné ;
Code: Select all
offset - le rôle de ;
Code: Select all
munmap() - le rôle de ;
Code: Select all
msync() - comment partager de la mémoire parent/enfant avec ;
Code: Select all
fork() - pourquoi ne remplace pas un sémaphore/mutex ;
Code: Select all
MAP_SHARED - le principe des page faults ;
- le principe de l'overcommit ;
- le risque de l'OOM killer ;
- le rôle de ;
Code: Select all
MAP_NORESERVE - le danger de ;
Code: Select all
MAP_FIXED - le rôle de ;
Code: Select all
MAP_POPULATE - le rôle de ;
Code: Select all
MAP_LOCKED - le fonctionnement de ;
Code: Select all
mremap() - pourquoi un pointeur peut devenir invalide après ;
Code: Select all
mremap() - la différence générale entre et
Code: Select all
mmap().Code: Select all
read()/write()
75. Mini questions de révision
1. Que retourne
Code: Select all
mmap()Code: Select all
MAP_FAILED2.
Code: Select all
mmap()Code: Select all
NULLNon.
3. Quel flag choisir pour partager les modifications ?
Code: Select all
MAP_SHARED4. Quel flag crée une vue privée ?
Code: Select all
MAP_PRIVATE5. Quelle technique est utilisée par
Code: Select all
MAP_PRIVATECopy-On-Write.
6. Quel flag permet de créer une zone sans fichier ?
Code: Select all
MAP_ANONYMOUS7. Quelle valeur de
Code: Select all
fdCode: Select all
MAP_ANONYMOUSCode: Select all
-18. Quelle valeur d'
Code: Select all
offsetCode: Select all
MAP_ANONYMOUSCode: Select all
09. Quel appel détruit un mapping ?
Code: Select all
munmap()10. Quel appel demande la synchronisation avec le fichier ?
Code: Select all
msync()11. Quel flag
Code: Select all
msyncCode: Select all
MS_SYNC12.
Code: Select all
mmap()Non.
13. Un page fault est-il toujours une erreur ?
Non.
14. Que se passe-t-il avec
Code: Select all
MAP_SHAREDCode: Select all
fork()Parent et enfant peuvent partager les mêmes pages.
15.
Code: Select all
MAP_SHAREDNon.
16. Quelle API Linux peut redimensionner un mapping ?
Code: Select all
mremap()17. Quel flag autorise
Code: Select all
mremap()Code: Select all
MREMAP_MAYMOVE18.
Code: Select all
MAP_FIXEDOui, car il peut remplacer des mappings existants.
19. À quoi sert
Code: Select all
MAP_POPULATEÀ précharger/préparer des pages afin de réduire certains page faults ultérieurs.
20. Que signifie l'overcommit ?
Le système peut accepter plus de mémoire virtuelle promise que la mémoire physique/swap immédiatement disponible.
Résumé final
Code: Select all
mmap()Il permet de transformer une ressource — fichier ou mémoire anonyme — en une région directement accessible dans l'espace virtuel du processus.
Les deux distinctions essentielles sont :
- fichier vs anonyme ;
- vs
Code: Select all
MAP_PRIVATE.Code: Select all
MAP_SHARED
Code: Select all
MAP_PRIVATECode: Select all
MAP_SHAREDCode: Select all
PROT_READCode: Select all
PROT_WRITECode: Select all
PROT_EXECCode: Select all
PROT_NONECode: Select all
munmap()Code: Select all
msync()Code: Select all
MAP_ANONYMOUSLes mappings
Code: Select all
MAP_SHAREDCode: Select all
fork()Linux fournit aussi des extensions telles que
Code: Select all
MAP_POPULATECode: Select all
MAP_LOCKEDCode: Select all
MAP_NORESERVECode: Select all
MAP_FIXEDCode: Select all
mremap()L'overcommit signifie qu'une allocation virtuelle réussie n'implique pas que toute la mémoire correspondante existe immédiatement en RAM ou en swap.
En cas de pénurie mémoire réelle, Linux peut déclencher l'OOM killer.
Enfin,
Code: Select all
mmap()