Scroll to navigation

USERFAULTFD(2) Manuel du programmeur Linux USERFAULTFD(2)

NOM

userfaultfd - Créer un descripteur de fichier pour gérer les erreurs de page en espace utilisateur

SYNOPSIS

#include <sys/types.h>
#include <linux/userfaultfd.h>
int userfaultfd(int flags);

Note : il n'existe pas d'enveloppe pour cet appel système dans la glibc ; voir NOTES.

DESCRIPTION

userfaultfd() crée un nouvel objet userfaultfd qui peut être utilisé pour la délégation de la gestion des erreurs de page à une application de l'espace utilisateur et renvoie un descripteur de fichier qui fait référence au nouvel objet. Le nouvel objet userfaultfd est configuré en utilisant ioctl(2).

Une fois l'objet userfaultfd configuré, l'application peut utiliser read(2) pour recevoir des notification d'userfaultfd. Les lectures à partir d'userfaultfd peuvent être bloquantes ou non bloquantes en fonction de la valeur des attributs (flags) utilisés pour la création de l'userfaultfd ou des appels suivants à fcntl(2).

Les valeurs suivantes peuvent être combinées dans flags par un OU binaire pour modifier le comportement d'userfaultfd() :

Activer l'attribut close-on-exec pour le nouveau descripteur de fichier userfaultfd. Consultez la description de l'attribut O_CLOEXEC dans open(2).
Permettre une opération non bloquante pour l'objet userfaultfd. Voir la description de l'attribut O_NONBLOCK dans open(2).

Quand le dernier descripteur de fichier faisant référence à un objet userfaultfd est fermé, tous les intervalles de mémoire qui ont été enregistrés avec l'objet sont désenregistrés et les événements non lus sont vidés.

Utilisation

Le mécanisme d'userfaultfd est conçu pour permettre à un thread dans un programme multi-thread de réaliser la pagination en espace utilisateur pour d'autres threads dans le processus. Lorsqu'un erreur de page survient pour une des régions enregistrées dans l'objet userfaultfd, le thread en erreur est mis en sommeil et un événement est généré qui peut être lu au moyen du descripteur de fichier userfaultfd. Le thread de gestion d'erreur lit les événements à partir de ce descripteur de fichier et les corrige en utilisant les opérations décrites dans ioctl_userfaultfd(2). Lors de l'intervention sur les événements d'erreur de page, le thread de gestion d'erreur peut déclencher le réveil d'un thread endormi.

Il est possible que les threads en erreur et les threads traitant les erreurs soient exécutés dans le contexte de processus différents. Dans ce cas, ces threads peuvent appartenir à différents programmes, et le programme qui exécute les threads en erreur ne collaborera pas nécessairement avec le programme qui gère les erreurs de page. Dans ce mode non coopératif, le processus qui contrôle userfaultfd et gère les erreurs de page a besoin d'avoir connaissance des modifications dans la disposition de la mémoire virtuelle du processus en erreur pour éviter une corruption de mémoire.'

À partir de Linux 4.11, userfaultfd peut aussi notifier les threads gérant les erreurs des modifications dans la disposition de la mémoire virtuelle du processus fautif. De plus, si le processus en erreur invoque fork(2), les objets userfaultfd associés au parent peuvent être dupliqués dans le processus enfant et le contrôleur d'userfaultfd sera informé (au moyen de UFFD_EVENT_FORK décrit plus bas) sur le descripteur de fichier associé aux objets userfault créés pour le processus enfant, ce qui permet au contrôleur d'userfaultfd de réaliser la pagination de l'espace utilisateur pour le processus enfant. À la différence des erreurs de page qui doivent être synchrones et réclament un réveil explicite ou explicite, tous les autres événements sont envoyés de façon asynchrone et le processus non coopératif reprend son exécution dès que le gestionnaire d'userfaultfd exécute read(2). Le gestionnaire d'userfaultfd doit soigneusement synchroniser les appels à UFFDIO_COPY avec le traitement des événements.

Le modèle asynchrone actuel d'envoi d'événement est optimal pour des implémentations de gestionnaire userfaultfd non coopératif à thread unique.

Fonctionnement d'userfaultfd

Après la création de l'objet userfaultfd avec userfaultfd(), l'application doit l'activer en utilisant l'opération UFFDIO_API de ioctl(2). Cette opération permet une connexion entre le noyau et l'espace utilisateur pour déterminer la version de l'API et les fonctions prises en charge. Cette opération doit être réalisée avant toutes les autres opérations ioctl(2) décrites plus bas (ou ces opérations échouent avec l'erreur EINVAL.)

Après le succès d'une opération UFFDIO_API, l'application enregistre alors les intervalles d'adresses mémoire en utilisant l'opération ioctl(2)UFFDIO_REGISTER. Quand l'opération UFFDIO_REGISTER s'est achevée avec succès, une erreur de page se produisant dans l'intervalle de mémoire requis et satisfaisant au mode défini au moment de l'enregistrement, sera transmis par le noyau à l'application de l'espace utilisateur. L'application peut alors utiliser les opérations d'ioctl(2)UFFDIO_COPY ou UFFDIO_ZERO pour résoudre l'erreur de page.

À partir de Linux 4.14, si l'application définit le bit de la fonction UFFD_FEATURE_SIGBUS en utilisant l'ioctl(2) UFFDIO_API, aucune notification d'erruer de page ne sera transmise à l'espace utilisateur. Un signal est envoyé à la place au processus en erreur. Avec cette fonction, userfaultfd peut être utilisé à des fins de robustesse pour capturer simplement tout accès aux zones dans l'intervalle d'adresses enregistré qui n'ont pas de pages allouées sans avoir à écouter les événements d'userfaultfd. Aucun contrôleur d'userfaultfd ne sera requis pour traiter ce type d'accès mémoire. Par exemple, cette fonction peut être utile à des applications qui désirent empêcher le noyau d'allouer des pages automatiquement et de remplir des trous dans des fichiers creux quand c'est un mappage mémoire qui permet l'accès aux trous.

La fonction UFFD_FEATURE_SIGBUS est héritée de façon implicite avec fork(2) si elle est utilisée en combinaison avec UFFD_FEATURE_FORK.

Des détails sur les différentes opérations d'ioctl(2) sont disponibles dans ioctl_userfaultfd(2).

Depuis Linux 4.11, les événements autres que les erreurs de page peuvent être activés pendant l'opération UFFDIO_API.

Jusqu'à Linux 4.11, userfaultfd ne peut être utilisé qu'avec des mappages de mémoire privée anonyme. Depuis Linux 4.11, userfaultfd peut aussi être utilisé avec des mappages de mémoire hugelbfs et partagée.

Lire à partir de la structure userfaultfd

Chaque read(2) à partir du descripteur de fichier userfaultfd renvoie une ou plusieurs structures uffd_msg, chacune d'elles décrit un événement d'erreur de page ou un événement requis pour l'utilisation non coopérative d'userfaultfd :


struct uffd_msg {

__u8 event; /* Type d'événement' */
...
union {
struct {
__u64 flags; /* Attributs décrivant l'erreur */
__u64 address; /* Adresse fautive */
} pagefault;
struct { /* Depuis Linux 4.11 */
__u32 ufd; /* Descripteur de ficher d'userfault
du processus enfant */
} fork;
struct { /* Depuis Linux 4.11 */
__u64 from; /* Ancienne adresse de la zone remappée */
__u64 to; /* Nouvelle adresse de la zone remappée */
__u64 len; /* Taille originale du mappage */
} remap;
struct { /* Depuis Linux 4.11 */
__u64 start; /* Adresse de début de la zone supprimée */
__u64 end; /* Adresse de fin de la zone supprimée */
} remove;
...
} arg;
/* Remplissage des champs omis */ } __packed;

Si plusieurs événements sont disponibles et si le tampon fourni est suffisamment grand, read(2) renvoie autant d'événements qu'il en tient dans le tampon fourni. Si le tampon fourni à read(2) est plus petit que la taille de la structure uffd_msg, read(2) échoue avec l'erreur EINVAL.

Les champs définis dans la structure uffd_msg sont les suivants :

Le type d'événement. Selon le type d'événement, différents champs de l'union arg représentent les détails nécessaires au traitement de l'événement. Les événements qui ne sont pas des erreurs de page ne sont générés que quand la fonctionnalité appropriée est activée durant la connexion de l'API à l'ioctl(2) UFFDIO_API.
Les valeurs suivantes peuvent apparaître dans le champ event :
Un événement d'erreur de page. Les détails de l'erreur de page sont disponibles dans le champ pagefault.
Généré lorsque le processus en erreur invoque fork(2) (ou clone(2) sans l'attribut CLONE_VM). Les détails de l'événement sont disponibles dans le champ fork.
Généré lorsque le processus en erreur invoque mremap(2). Les détails de l'événement sont disponibles dans le champ remap.
Généré lorsque le processus en erreur invoque madvise(2) avec les conseils MADV_DONTNEED ou MADV_REMOVE. Les détails de l'événement sont disponibles dans le champ remove.
Généré lorsque le processus en erreur supprime le mappage d'un intervalle de mémoire soit explicitement avec munmap(2), soit implicitement durant l'exécution de mmap(2) ou mremap(2). Les détails de l'événement sont disponibles dans le champ remove.
L'adresse qui a déclenché l'erreur de page.
Un masque de bits qui décrit l'événement. Pour UFFD_EVENT_PAGEFAULT, les attributs suivants peuvent apparaître :
Si l'adresse est dans un intervalle qui est enregistré avec l'attribut UFFDIO_REGISTER_MODE_MISSING (voir ioctl_userfaultfd(2)) et si cet attribut est défini, c'est une erreur d'écriture ; autrement c'est une erreur de lecture.
Le descripteur de fichier associé à l'objet userfault créé pour l'enfant créé par fork(2).
L'adresse d'origine de la plage de mémoire dont le mappage a été modifié en utilisant madvise(2).
La nouvelle adresse de la plage de mémoire dont le mappage a été modifié en utilisant madvise(2).
La taille d'origine de la plage de mémoire dont le mappage a été modifié en utilisant madvise(2).
L'adresse de début de la plage de mémoire qui a été libérée en utilisant madvise(2) ou dont le mappage a été supprimé.
L'adresse terminale de la plage de mémoire qui a été libérée en utilisant madvise(2) ou dont le mappage a été supprimé.

read(2) sur un descripteur de fichier userfaultfd peut échouer pour les raisons suivantes :

L'objet userfaultfd n'a pas encore été activé avec l'opération d'ioctl(2) UFFDIO_API.

Si l'attribut O_NONBLOCK est activé dans la description de fichier ouvert associée, le descripteur de fichier userfaultfd peut être surveillé avec poll(2), select(2) et epoll(7). Quand les événements sont disponibles, le descripteur de fichier l'indique comme lisible. Si l'attribut O_NONBLOCK n'est pas activé, alors poll(2) indique (toujours) que le fichier comme ayant une condition POLLERR et select(2) indique que le descripteur de fichier est à la fois accessible en lecture et en écriture.

VALEUR RENVOYÉE

En cas de succès, userfaultfd() renvoie un nouveau descripteur de fichier qui fait référence à un objet userfaultfd. En cas d'échec, la fonction renvoie -1 et errno contient le code d'erreur.

ERREURS

Une valeur non prise en compte a été spécifiée dans flags.
La limite par processus du nombre de descripteurs de fichier ouverts a été atteinte.
La limite du nombre total de fichiers ouverts pour le système entier a été atteinte.
La mémoire disponible du noyau n'était pas suffisante.

VERSIONS

L'appel système userfaultfd() est apparu pour la première fois dans Linux 4.3.

La prise en charge des zones de mémoire hugetlbfs et partagée et des événements qui ne sont pas des erreurs de page a été ajoutée dans Linux 4.11

CONFORMITÉ

userfaultfd() est spécifique à Linux et ne devrait pas être employé dans des programmes destinés à être portables.

NOTES

La glibc ne fournit pas de fonction autour de cet appel système ; appelez-le avec syscall(2).

Le mécanisme d'userfaultfd peut être utilisé comme une alternative aux techniques traditionnelles de pagination de l'espace utilisateur basées sur l'utilisation du signal SIGSEGV et de mmap(2). Il peut aussi être utilisé pour implémenter la restauration en mode paresseux (« lazy restore ») pour les mécanismes de la fonctionnalité de gel des applications (checkpoint/restore), aussi bien que la migration après copie pour permettre une exécution (presque) ininterrompue lors du transfert de machines virtuelles et de conteneurs Linux d'un hôte à un autre.

BOGUES

Si UFFD_FEATURE_EVENT_FORK est activé et si un appel système issu de la famille de fork(2) est interrompu par un signal ou échoue, un descripteur périmé d'userfaultfd peut être créé. Dans ce cas, un faux UFFD_EVENT_FORK sera fourni au surveillant d'userfaultfd.

EXEMPLE

Le programme ci-dessous démontre l'utilisation du mécanisme userfaultfd. Le programme crée deux threads, un qui agit comme gestionnaire d'erreur de page pour le processus, pour les pages dans une région sans demande de page en utilisant mmap(2).

Le programme prend un argument en ligne de commande, qui est le nombre de pages qui seront créées dans un mappage dont les erreurs de pages seront gérées au moyen d'userfaultfd. Après la création d'un objet userfaultfd, le programme crée alors un mappage privé anonyme de la taille spécifiée et enregistre l'intervalle d'adresses de ce mappage en utilisant l'opération d'ioctl(2) UFFDIO_REGISTER. Le programme crée alors un second thread qui exécutera la tâche de gestion des erreurs de page.

Le thread principal parcourt les pages du mappage à la recherche des octets des pages successives. Comme il n'y a pas eu encore d'accès aux pages, le premier accès à un octet de chaque page déclenchera un événement d'erreur de page sur le descripteur de fichier userfaultfd.

Chaque événement d'erreur de page est géré par le second thread qui s'installe dans une boucle traitant l'entrée du descripteur de fichier userfaultfd. À chaque itération de la boucle, le second thread appelle poll(2) pour vérifier l'état du descripteur de fichier puis lit un événement à partir de ce descripteur de fichier. Tout ce type d'événements doit être un événement UFFD_EVENT_PAGEFAULT que le thread traite en copiant un page de données dans la région en erreur en utilisant l'opération d'ioctl(2) UFFDIO_COPY.

La suite est un exemple de ce qui est observé lors de l'exécution du programme :


$ ./userfaultfd_demo 3
Address returned by mmap() = 0x7fd30106c000
fault_handler_thread():

poll() returns: nready = 1; POLLIN = 1; POLLERR = 0
UFFD_EVENT_PAGEFAULT event: flags = 0; address = 7fd30106c00f
(uffdio_copy.copy returned 4096) Read address 0x7fd30106c00f in main(): A Read address 0x7fd30106c40f in main(): A Read address 0x7fd30106c80f in main(): A Read address 0x7fd30106cc0f in main(): A fault_handler_thread():
poll() returns: nready = 1; POLLIN = 1; POLLERR = 0
UFFD_EVENT_PAGEFAULT event: flags = 0; address = 7fd30106d00f
(uffdio_copy.copy returned 4096) Read address 0x7fd30106d00f in main(): B Read address 0x7fd30106d40f in main(): B Read address 0x7fd30106d80f in main(): B Read address 0x7fd30106dc0f in main(): B fault_handler_thread():
poll() returns: nready = 1; POLLIN = 1; POLLERR = 0
UFFD_EVENT_PAGEFAULT event: flags = 0; address = 7fd30106e00f
(uffdio_copy.copy returned 4096) Read address 0x7fd30106e00f in main(): C Read address 0x7fd30106e40f in main(): C Read address 0x7fd30106e80f in main(): C Read address 0x7fd30106ec0f in main(): C

Source du programme

/* userfaultfd_demo.c

Licensed under the GNU General Public License version 2 or later. */ #define _GNU_SOURCE #include <sys/types.h> #include <stdio.h> #include <linux/userfaultfd.h> #include <pthread.h> #include <errno.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <signal.h> #include <poll.h> #include <string.h> #include <sys/mman.h> #include <sys/syscall.h> #include <sys/ioctl.h> #include <poll.h> #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
} while (0) static int page_size; static void * fault_handler_thread(void *arg) {
static struct uffd_msg msg; /* Données lues à partir de userfaultfd */
static int fault_cnt = 0; /* Nombres d'erreurs déjà gérées */
long uffd; /* Descripteur de fichier userfaultfd */
static char *page = NULL;
struct uffdio_copy uffdio_copy;
ssize_t nread;
uffd = (long) arg;
/* Créer une page qui sera copiée dans la région en erreur */
if (page == NULL) {
page = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED)
errExit("mmap");
}
/* Boucle gérant les événements entrants sur le descripteur
de fichier userfaultfd */
for (;;) {
/* Voir ce que poll() nous dit sur l'userfaultfd */
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
if (nready == -1)
errExit("poll");
printf("\nfault_handler_thread():\n");
printf(" poll() returns: nready = %d; "
"POLLIN = %d; POLLERR = %d\n", nready,
(pollfd.revents & POLLIN) != 0,
(pollfd.revents & POLLERR) != 0);
/* Lire un événement à partir de l'userfaultfd */
nread = read(uffd, &msg, sizeof(msg));
if (nread == 0) {
printf("EOF on userfaultfd!\n");
exit(EXIT_FAILURE);
}
if (nread == -1)
errExit("read");
/* Un seul type d'événement est attendu ; il faut vérifier
cette supposition */
if (msg.event != UFFD_EVENT_PAGEFAULT) {
fprintf(stderr, "Unexpected event on userfaultfd\n");
exit(EXIT_FAILURE);
}
/* Afficher une information sur l'événement erreur de page */
printf(" UFFD_EVENT_PAGEFAULT event: ");
printf("flags = %llx; ", msg.arg.pagefault.flags);
printf("address = %llx\n", msg.arg.pagefault.address);
/* Copier la page sur laquelle pointe la « page » dans la région
fautive. Varier le contenu copié, afin qu'il soit plus
évident que chaque erreur soit gérée séparément. */
memset(page, 'A' + fault_cnt % 20, page_size);
fault_cnt++;
uffdio_copy.src = (unsigned long) page;
/* Il est nécessaire de gérer les erreurs de page en
unités de pages(!). Aussi, il faut arrondir les
adresses fautives à la limite de page */
uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address &
~(page_size - 1);
uffdio_copy.len = page_size;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
errExit("ioctl-UFFDIO_COPY");
printf(" (uffdio_copy.copy returned %lld)\n",
uffdio_copy.copy);
} } int main(int argc, char *argv[]) {
long uffd; /* Descripteur de fichier userfaultfd */
char *addr; /* Début de la région gérée par userfaultfd */
unsigned long len; /* Taille de la région gérée par userfaultfd */
pthread_t thr; /* ID du thread qui gère les erreurs de page */
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
int s;
if (argc != 2) {
fprintf(stderr, "Usage : %s num-pages\n", argv[0]);
exit(EXIT_FAILURE);
}
page_size = sysconf(_SC_PAGE_SIZE);
len = strtoul(argv[1], NULL, 0) * page_size;
/* Créer et activer un objet userfaultfd */
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1)
errExit("userfaultfd");
uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)
errExit("ioctl-UFFDIO_API");
/* Créer un mappage anonyme privé. La mémoire sera paginée
avec aucune demande — c'est-à-dire, sans être encore
allouée. Quand la mémoire sera réellement utilisée,
elle sera allouée au moyen de l'userfaultfd. */
addr = mmap(NULL, len, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED)
errExit("mmap");
printf("Address returned by mmap() = %p\n", addr);
/* Enregistrer l'intervalle de mémoire du mappage qui vient d'être
créé pour le traitement par l'objet userfaultfd. Dans mode,
suivre les pages manquantes (c'est-à-dire, les pages qui ne sont
pas encore fautives). */
uffdio_register.range.start = (unsigned long) addr;
uffdio_register.range.len = len;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)
errExit("ioctl-UFFDIO_REGISTER");
/* Créer un thread qui traitera les événements userfaultfd */
s = pthread_create(&thr, NULL, fault_handler_thread, (void *) uffd);
if (s != 0) {
errno = s;
errExit("pthread_create");
}
/* Le thread principal utilise la mémoire dans le mappage,
utilisant des emplacements séparés de 1024 octets. Cela va
déclencher des événements userfaultfd pour toutes les pages
dans la région. */
int l;
l = 0xf; /* Assurer que l'adresse fautive n'est pas sur une
limite de page afin de vérifier que ce cas est
correctement géré dans le fault_handling_thread() */
while (l < len) {
char c = addr[l];
printf("Read address %p in main(): ", addr + l);
printf("%c\n", c);
l += 1024;
usleep(100000); /* Ralentir un peu le traitement */
}
exit(EXIT_SUCCESS); }

VOIR AUSSI

fcntl(2), ioctl(2), ioctl_userfaultfd(2), madvise(2), mmap(2)

Documentation/vm/userfaultfd.txt dans l'arborescence des sources du noyau Linux

COLOPHON

Cette page fait partie de la publication 4.16 du projet man-pages Linux. Une description du projet et des instructions pour signaler des anomalies et la dernière version de cette page peuvent être trouvées à l'adresse https://www.kernel.org/doc/man-pages/.

TRADUCTION

La traduction française de cette page de manuel a été créée par Christophe Blaess <https://www.blaess.fr/christophe/>, Stéphan Rafin <stephan.rafin@laposte.net>, Thierry Vignaud <tvignaud@mandriva.com>, François Micaux, Alain Portal <aportal@univ-montp2.fr>, Jean-Philippe Guérard <fevrier@tigreraye.org>, Jean-Luc Coulon (f5ibh) <jean-luc.coulon@wanadoo.fr>, Julien Cristau <jcristau@debian.org>, Thomas Huriaux <thomas.huriaux@gmail.com>, Nicolas François <nicolas.francois@centraliens.net>, Florentin Duneau <fduneau@gmail.com>, Simon Paillard <simon.paillard@resel.enst-bretagne.fr>, Denis Barbier <barbier@debian.org>, David Prévot <david@tilapin.org> et Jean-Pierre Giraud <jean-pierregiraud@neuf.fr>

Cette traduction est une documentation libre ; veuillez vous reporter à la GNU General Public License version 3 concernant les conditions de copie et de distribution. Il n'y a aucune RESPONSABILITÉ LÉGALE.

Si vous découvrez un bogue dans la traduction de cette page de manuel, veuillez envoyer un message à debian-l10n-french@lists.debian.org.

15 septembre 2017 Linux