Résumé — Ce qu’il faut retenir
Ce chapitre présente plusieurs appels système qui permettent de contrôler plus finement des régions de l’espace d’adressage virtuel d’un processus.
- modifie les permissions d’accès d’une région mémoire déjà mappée.
Code: Select all
mprotect() - verrouille des pages en RAM afin d’éviter qu’elles soient évincées vers le swap.
Code: Select all
mlock() - retire ce verrouillage.
Code: Select all
munlock() - verrouille tout ou partie de l’espace mémoire du processus.
Code: Select all
mlockall() - retire les verrouillages établis par
Code: Select all
munlockall().Code: Select all
mlockall() - permet de savoir quelles pages d’une région sont actuellement résidentes en mémoire physique.
Code: Select all
mincore() - permet d’indiquer au noyau la manière dont une région mémoire devrait probablement être utilisée.
Code: Select all
madvise()
Code: Select all
mmap()1. Modifier les protections mémoire avec mprotect()
Prototype
Code: Select all
#include <sys/mman.h>
int mprotect(void *addr, size_t len, int prot);
Code: Select all
mprotect()Code: Select all
addrCode: Select all
lenLa région doit déjà appartenir à l’espace d’adressage du processus.
Sur Linux,
Code: Select all
addrLa taille
Code: Select all
lenPour connaître la taille d’une page :
Code: Select all
long page_size = sysconf(_SC_PAGESIZE);
Code: Select all
long page_size = sysconf(_SC_PAGE_SIZE);
Code: Select all
PROT_NONE
Code: Select all
PROT_READ
Code: Select all
PROT_WRITE
Code: Select all
PROT_EXEC
Ces flags peuvent être combinés avec l’opérateur binaire :
Code: Select all
|
Code: Select all
PROT_READ | PROT_WRITE
- en cas de succès.
Code: Select all
0 - en cas d’erreur, avec
Code: Select all
-1positionné.Code: Select all
errno
Code: Select all
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
int main(void)
{
long page_size = sysconf(_SC_PAGESIZE);
void *mem = mmap(
NULL,
page_size,
PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0
);
if (mem == MAP_FAILED) {
perror("mmap");
return EXIT_FAILURE;
}
if (mprotect(mem, page_size, PROT_READ | PROT_WRITE) == -1) {
perror("mprotect");
munmap(mem, page_size);
return EXIT_FAILURE;
}
((char *)mem)[0] = 'A';
printf("%c\n", ((char *)mem)[0]);
munmap(mem, page_size);
return EXIT_SUCCESS;
}
- la région est créée avec ;
Code: Select all
PROT_NONE - tout accès est initialement interdit ;
- ajoute ensuite
Code: Select all
mprotect();Code: Select all
PROT_READ | PROT_WRITE - le programme peut alors lire et écrire dans cette région.
- créer des pages gardes ;
- empêcher temporairement l’écriture dans une région ;
- passer une zone en lecture seule ;
- rendre une zone exécutable ou non exécutable ;
- implémenter certains allocateurs mémoire ;
- protéger des données sensibles ;
- détecter des dépassements mémoire ;
- modifier les permissions d’un mapping créé par .
Code: Select all
mmap()
Une tentative d’accès incompatible avec les protections d’une page peut provoquer un signal tel que :
Code: Select all
SIGSEGV
Code: Select all
mprotect(mem, page_size, PROT_NONE);
Code: Select all
char c = *((char *)mem);
Code: Select all
SIGSEGV
Prototype
Code: Select all
#include <sys/mman.h>
int mlock(const void *addr, size_t len);
Code: Select all
mlock()Ces pages ne doivent normalement pas être évincées vers le swap tant qu’elles restent verrouillées.
Contrairement à
Code: Select all
mprotect()Code: Select all
mlock()Le noyau verrouille les pages complètes qui intersectent la région demandée.
Pourquoi verrouiller une région ?
- éviter une latence causée par un défaut de page ;
- garder certaines données immédiatement accessibles ;
- réduire le risque qu’une donnée sensible se retrouve dans le swap ;
- répondre aux contraintes de certains programmes temps réel ;
- garder certaines pages critiques résidentes.
Code: Select all
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
int main(void)
{
long page_size = sysconf(_SC_PAGESIZE);
void *mem = mmap(
NULL,
page_size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0
);
if (mem == MAP_FAILED) {
perror("mmap");
return EXIT_FAILURE;
}
if (mlock(mem, page_size) == -1) {
perror("mlock");
}
((char *)mem)[0] = 'X';
munlock(mem, page_size);
munmap(mem, page_size);
return EXIT_SUCCESS;
}
- en cas de succès.
Code: Select all
0 - en cas d’erreur.
Code: Select all
-1
Linux limite la quantité de mémoire qu’un processus non privilégié peut verrouiller.
Cette limite peut être consultée ou modifiée avec :
Code: Select all
getrlimit()
Code: Select all
setrlimit()
Code: Select all
RLIMIT_MEMLOCK
Code: Select all
#include <stdio.h>
#include <sys/resource.h>
int main(void)
{
struct rlimit lim;
if (getrlimit(RLIMIT_MEMLOCK, &lim) == -1) {
perror("getrlimit");
return 1;
}
printf("soft = %llu\n",
(unsigned long long)lim.rlim_cur);
printf("hard = %llu\n",
(unsigned long long)lim.rlim_max);
return 0;
}
- la limite souple est dans ;
Code: Select all
rlim_cur - la limite maximale est dans ;
Code: Select all
rlim_max - un processus non privilégié ne peut normalement pas dépasser sa limite ;
- la quantité de mémoire verrouillée est comptabilisée par pages.
Sur Linux, certaines limitations peuvent être levées ou étendues pour les processus possédant les capacités appropriées.
Une capacité souvent liée au verrouillage mémoire est :
Code: Select all
CAP_IPC_LOCK
Prototype
Code: Select all
#include <sys/mman.h>
int munlock(const void *addr, size_t len);
Code: Select all
munlock()Cela ne signifie pas que les pages sont immédiatement supprimées de la RAM.
Cela signifie simplement qu’elles peuvent à nouveau être évincées si le noyau en a besoin.
Exemple :
Code: Select all
mlock(mem, size);
/* travail */
munlock(mem, size);
Le verrouillage concerne les pages virtuelles de la région, pas uniquement le contenu logique que le programme considère comme « utile ».
Deux appels portant sur des intervalles qui se recouvrent peuvent donc concerner les mêmes pages physiques.
5. Verrouiller l’espace mémoire avec mlockall()
Prototype
Code: Select all
#include <sys/mman.h>
int mlockall(int flags);
Code: Select all
MCL_CURRENT
Code: Select all
MCL_FUTURE
Les deux peuvent être combinés :
Code: Select all
MCL_CURRENT | MCL_FUTURE
Code: Select all
#include <stdio.h>
#include <sys/mman.h>
int main(void)
{
if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) {
perror("mlockall");
return 1;
}
/* code critique */
munlockall();
return 0;
}
Si le processus atteint sa limite de mémoire verrouillable, certaines opérations futures susceptibles d’augmenter son espace mémoire peuvent échouer.
Cela peut concerner par exemple :
Code: Select all
mmap()
6. munlockall()
Prototype
Code: Select all
#include <sys/mman.h>
int munlockall(void);
Code: Select all
munlockall()Valeur de retour :
- en cas de succès ;
Code: Select all
0 - en cas d’erreur.
Code: Select all
-1
Les verrous mémoire sont également retirés lorsque les pages ne font plus partie de l’espace d’adressage du processus.
Par exemple après :
Code: Select all
munmap()
7. Sémantique importante du verrouillage mémoire
Le verrouillage est appliqué par pages.
Supposons une taille de page de :
Code: Select all
4096
Si l’on verrouille une petite zone située au milieu d’une page :
Code: Select all
mlock(ptr + 100, 20);
Même si la demande ne porte que sur quelques octets, le verrouillage mémoire agit à la granularité des pages.
Autre point important
Le verrouillage n’est pas un mécanisme d’exclusion mutuelle.
Code: Select all
mlock()
Code: Select all
pthread_mutex_lock()
Il empêche l’éviction des pages, mais n’empêche pas plusieurs threads ou processus d’accéder simultanément aux données.
8. Savoir si des pages sont résidentes avec mincore()
Prototype
Code: Select all
#define _DEFAULT_SOURCE
#include <sys/mman.h>
int mincore(void *addr, size_t length, unsigned char *vec);
Code: Select all
mincore()Elle ne verrouille rien.
Elle ne charge pas nécessairement les pages.
Elle fournit seulement un état de résidence au moment de l’appel.
Contraintes
L’adresse :
Code: Select all
addr
Le tableau :
Code: Select all
vec
Nombre de pages :
Code: Select all
pages = (length + page_size - 1) / page_size;
Code: Select all
unsigned char *vec = calloc(pages, sizeof(unsigned char));
Pour chaque page, le bit de poids faible indique si la page est résidente.
Test typique :
Code: Select all
if (vec[i] & 1) {
printf("page presente en RAM\n");
}
Code: Select all
printf("page non residente\n");
Code: Select all
#define _DEFAULT_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
int main(void)
{
long page_size = sysconf(_SC_PAGESIZE);
size_t size = (size_t)page_size * 4;
char *mem = mmap(
NULL,
size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0
);
if (mem == MAP_FAILED) {
perror("mmap");
return EXIT_FAILURE;
}
size_t pages = size / (size_t)page_size;
unsigned char *vec = calloc(pages, sizeof(unsigned char));
if (vec == NULL) {
perror("calloc");
munmap(mem, size);
return EXIT_FAILURE;
}
if (mincore(mem, size, vec) == -1) {
perror("mincore");
free(vec);
munmap(mem, size);
return EXIT_FAILURE;
}
for (size_t i = 0; i < pages; i++) {
printf(
"page %zu : %s\n",
i,
(vec[i] & 1) ? "residente" : "non residente"
);
}
free(vec);
munmap(mem, size);
return EXIT_SUCCESS;
}
Parce que Linux utilise largement la pagination à la demande.
Un mapping peut exister dans l’espace virtuel sans que toutes ses pages aient déjà reçu une page physique.
Une page est souvent matérialisée lors du premier accès.
Expérience simple
Créer un mapping :
Code: Select all
char *mem = mmap(...);
Code: Select all
mincore()
Code: Select all
mem[0] = 1;
mem[page_size] = 2;
Code: Select all
mincore()
9. Conseiller le noyau avec madvise()
Prototype
Code: Select all
#define _DEFAULT_SOURCE
#include <sys/mman.h>
int madvise(void *addr, size_t length, int advice);
Code: Select all
madvise()Il s’agit d’un conseil.
Le noyau peut utiliser cette information pour optimiser notamment :
- le read-ahead ;
- la mise en cache ;
- la pagination ;
- la récupération de pages ;
- le comportement de certaines régions mappées.
Code: Select all
MADV_NORMAL
C’est le comportement par défaut.
Code: Select all
MADV_RANDOM
Le noyau peut réduire ou désactiver certaines stratégies de lecture anticipée.
Code: Select all
MADV_SEQUENTIAL
Le noyau peut adapter son read-ahead et recycler plus rapidement certaines pages déjà parcourues.
Code: Select all
MADV_WILLNEED
Le noyau peut essayer de préparer les données afin de réduire les futurs défauts de page.
Code: Select all
MADV_DONTNEED
La sémantique exacte dépend du type de mapping et du système.
Sur Linux, ce conseil peut permettre au noyau de libérer certaines pages et de les recharger ou recréer ultérieurement si elles sont accédées à nouveau.
Exemple MADV_SEQUENTIAL
Code: Select all
if (madvise(mem, size, MADV_SEQUENTIAL) == -1) {
perror("madvise");
}
- gros fichier parcouru du début à la fin ;
- scan séquentiel d’une base de données ;
- analyse linéaire d’un mapping.
Code: Select all
if (madvise(mem, size, MADV_RANDOM) == -1) {
perror("madvise");
}
- index ;
- table de hachage mappée ;
- base de données avec lectures non séquentielles ;
- accès pseudo-aléatoires dans un fichier.
Code: Select all
if (madvise(mem, size, MADV_WILLNEED) == -1) {
perror("madvise");
}
Code: Select all
"Je vais probablement avoir besoin de ces pages prochainement."
Code: Select all
if (madvise(mem, size, MADV_DONTNEED) == -1) {
perror("madvise");
}
Il ne faut cependant pas interpréter cela comme un équivalent direct de :
Code: Select all
free()
Code: Select all
munmap()
10. Différence entre madvise(), mincore(), mlock() et mprotect()
Code: Select all
mprotect()
Question :
Code: Select all
Qui peut lire, écrire ou exécuter cette mémoire ?
Code: Select all
mlock()
Question :
Code: Select all
Puis-je empêcher ces pages d'être évincées ?
Code: Select all
mincore()
Question :
Code: Select all
Ces pages sont-elles actuellement présentes en RAM ?
Code: Select all
madvise()
Question :
Code: Select all
Comment vais-je probablement utiliser ces pages ?
Code: Select all
mmap()
Code: Select all
mlock()
On peut donc avoir :
Code: Select all
mmap()
Code: Select all
mlock()
Exemple logique :
Code: Select all
mmap()
|
+--> région virtuelle créée
|
+--> mprotect() : changer les permissions
|
+--> mlock() : verrouiller en RAM
|
+--> mincore() : vérifier la résidence
|
+--> madvise() : donner un conseil au noyau
|
+--> munmap() : supprimer le mapping
Une adresse virtuelle peut être valide sans que la page correspondante soit immédiatement présente en RAM.
Lorsqu’un thread accède à une page absente, le processeur déclenche un défaut de page.
Le noyau détermine alors si l’accès est valide.
Si l’accès est valide, il peut :
- attribuer une page physique ;
- charger des données depuis un fichier ;
- restaurer une page ;
- mettre à jour les tables de pages ;
- reprendre l’exécution du thread.
Code: Select all
SIGSEGV
Exemple :
Code: Select all
void *p = mmap(
NULL,
4096,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0
);
Les permissions sont alors :
Code: Select all
READ
WRITE
Code: Select all
mprotect(p, 4096, PROT_READ);
Code: Select all
READ
Code: Select all
((char *)p)[0] = 'X';
Code: Select all
SIGSEGV
Code: Select all
#define _DEFAULT_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>
int main(void)
{
long page_size = sysconf(_SC_PAGESIZE);
size_t size = (size_t)page_size * 2;
char *mem = mmap(
NULL,
size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0
);
if (mem == MAP_FAILED) {
perror("mmap");
return EXIT_FAILURE;
}
strcpy(mem, "Inside The System");
if (mlock(mem, size) == -1) {
perror("mlock");
}
if (madvise(mem, size, MADV_SEQUENTIAL) == -1) {
perror("madvise");
}
size_t pages = size / (size_t)page_size;
unsigned char *vec = calloc(pages, sizeof(unsigned char));
if (vec != NULL) {
if (mincore(mem, size, vec) == 0) {
for (size_t i = 0; i < pages; i++) {
printf(
"page %zu : %s\n",
i,
(vec[i] & 1) ? "RAM" : "non residente"
);
}
}
free(vec);
}
if (mprotect(mem, size, PROT_READ) == -1) {
perror("mprotect");
}
printf("%s\n", mem);
munlock(mem, size);
munmap(mem, size);
return EXIT_SUCCESS;
}
- crée le mapping ;
Code: Select all
mmap() - tente de maintenir ses pages en RAM ;
Code: Select all
mlock() - fournit un conseil d’utilisation ;
Code: Select all
madvise() - consulte la résidence des pages ;
Code: Select all
mincore() - passe ensuite le mapping en lecture seule ;
Code: Select all
mprotect() - retire le verrouillage ;
Code: Select all
munlock() - détruit le mapping.
Code: Select all
munmap()
mprotect()
Peut échouer si :
- l’adresse est mal alignée ;
- une partie de la région n’est pas mappée ;
- les permissions demandées sont invalides ;
- la protection demandée n’est pas autorisée.
Peut échouer si :
- la limite est dépassée ;
Code: Select all
RLIMIT_MEMLOCK - la plage mémoire n’est pas valide ;
- le processus ne possède pas les privilèges nécessaires.
Peut échouer si :
- n’est pas alignée sur une frontière de page ;
Code: Select all
addr - la région n’est pas mappée ;
- les paramètres sont invalides.
Peut échouer si :
- l’adresse ou la plage ne conviennent pas ;
- le conseil fourni n’est pas reconnu ;
- le mapping ne supporte pas l’opération demandée.
Code: Select all
perror()
Code: Select all
strerror(errno)
Code: Select all
MEMOIRE VIRTUELLE
|
+----------------+----------------+
| | |
permissions residence strategie
| | |
mprotect() mlock() madvise()
|
munlock()
|
mlockall()
|
munlockall()
observation
|
mincore()
Les fonctions principales à connaître après ce chapitre sont :
Code: Select all
mprotect()
Code: Select all
mlock()
Code: Select all
munlock()
Code: Select all
mlockall()
Code: Select all
munlockall()
Code: Select all
mincore()
Code: Select all
madvise()
Code: Select all
PROT_NONE
PROT_READ
PROT_WRITE
PROT_EXEC
Code: Select all
MCL_CURRENT
MCL_FUTURE
Code: Select all
MADV_NORMAL
MADV_RANDOM
MADV_SEQUENTIAL
MADV_WILLNEED
MADV_DONTNEED
Code: Select all
RLIMIT_MEMLOCK
Après ce chapitre, tu devrais être capable d’expliquer :
- la différence entre mémoire virtuelle et mémoire physiquement résidente ;
- pourquoi une page mappée peut ne pas encore être présente en RAM ;
- ce qu’est un défaut de page ;
- pourquoi les opérations mémoire travaillent souvent à la granularité des pages ;
- comment modifier les permissions d’un mapping avec ;
Code: Select all
mprotect() - comment empêcher des pages d’être évincées avec ;
Code: Select all
mlock() - pourquoi existe ;
Code: Select all
RLIMIT_MEMLOCK - la différence entre et
Code: Select all
MCL_CURRENT;Code: Select all
MCL_FUTURE - comment tester la résidence avec ;
Code: Select all
mincore() - pourquoi est un conseil et non un ordre absolu ;
Code: Select all
madvise() - la différence entre et
Code: Select all
MADV_RANDOM;Code: Select all
MADV_SEQUENTIAL - la différence entre et
Code: Select all
MADV_WILLNEED;Code: Select all
MADV_DONTNEED - pourquoi ne supprime pas une région mémoire ;
Code: Select all
munlock() - pourquoi et
Code: Select all
munmap()ne servent pas à la même chose.Code: Select all
munlock()
Code: Select all
int mprotect(void *addr, size_t len, int prot);
Code: Select all
int mlock(const void *addr, size_t len);
Code: Select all
int munlock(const void *addr, size_t len);
Code: Select all
int mlockall(int flags);
Code: Select all
int munlockall(void);
Code: Select all
int mincore(void *addr, size_t length, unsigned char *vec);
Code: Select all
int madvise(void *addr, size_t length, int advice);
20. Pièges classiques
- Confondre avec
Code: Select all
mprotect().Code: Select all
mlock() - Croire que protège contre les écritures.
Code: Select all
mlock() - Croire que libère la mémoire.
Code: Select all
munlock() - Croire que charge les pages en RAM.
Code: Select all
mincore() - Croire que oblige toujours le noyau à suivre le conseil.
Code: Select all
madvise() - Oublier l’alignement de et
Code: Select all
mprotect().Code: Select all
mincore() - Oublier que la mémoire est gérée par pages.
- Oublier de vérifier après
Code: Select all
MAP_FAILED.Code: Select all
mmap() - Oublier de vérifier les retours .
Code: Select all
-1 - Oublier lors de l’utilisation de
Code: Select all
RLIMIT_MEMLOCK.Code: Select all
mlock()
Le chapitre 50 complète très bien l’étude de
Code: Select all
mmap()Avec
Code: Select all
mmap()Avec
Code: Select all
mprotect()Avec
Code: Select all
mlock()Code: Select all
mlockall()Avec
Code: Select all
mincore()Avec
Code: Select all
madvise()La logique générale à retenir est donc :
Code: Select all
mmap()
-> creer une region virtuelle
mprotect()
-> modifier les droits
mlock()
-> conserver des pages en RAM
mincore()
-> observer la residence
madvise()
-> conseiller le noyau
munmap()
-> supprimer le mapping
