userfaultfd
Table des matières
Retour à l'index
NOM
userfaultfd - Créer un descripteur de fichier pour gérer les erreurs de page
en espace utilisateur
BIBLIOTHÈQUE
Bibliothèque C standard (libc, -lc)
SYNOPSIS
#include <fcntl.h> /* Définition des constantes O_* */
#include <sys/syscall.h> /* Définition des constantes SYS_* */
#include <linux/userfaultfd.h> /* Définition des constantes UFFD_* */
#include <unistd.h>
int syscall(SYS_userfaultfd, int flags);
Note : la glibc ne fournit pas d'enveloppe pour userfaultfd(),
imposant l'utilisation de syscall(2).
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() :
- O_CLOEXEC
-
Activer l'attribut close-on-exec pour le nouveau descripteur de fichier
userfaultfd. Consultez la description de l'attribut O_CLOEXEC dans
open(2).
- O_NONBLOCK
-
Permettre une opération non bloquante pour l'objet userfaultfd. Voir la
description de l'attribut O_NONBLOCK dans open(2).
- UFFD_USER_MODE_ONLY
-
C'est un attribut spécifique à userfaultfd qui a été introduit dans Linux 5.11. Quand il est défini, l'objet userfaultfd ne pourra gérer que les
erreurs de page provenant de l'espace utilisateur dans les régions
enregistrées. Quand une erreur provenant du noyau est déclenchée dans
l'intervalle enregistré avec cet userfaultfd, un signal SIGBUS sera
envoyé.
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.
Userfaultfd gère trois modes d'enregistrement :
- UFFDIO_REGISTER_MODE_MISSING (depuis Linux 4.10)
-
Quand il est enregistré avec le mode UFFDIO_REGISTER_MODE_MISSING,
l'espace utilisateur recevra une notification d'erreur de page lors de
l'accès à une page manquante. L'exécution du thread fautif sera arrêtée
jusqu'à ce que l'erreur de page soit résolue à partir de l'espace
utilisateur par un ioctl UFFDIO_COPY ou UFFDIO_ZEROPAGE.
- UFFDIO_REGISTER_MODE_MINOR (depuis Linux 5.13)
-
Quand il est enregistré avec le mode UFFDIO_REGISTER_MODE_MINOR, l'espace
utilisateur recevra une notification d'erreur de page lorsqu'une erreur de
page mineure survient. C'est-à-dire quand une page de sauvegarde est dans le
cache de page, mais les entrées dans la table de pages n'existent pas
encore. L'exécution du thread fautif sera arrêtée jusqu'à ce que l'erreur de
page soit résolue à partir de l'espace utilisateur par un ioctl
UFFDIO_CONTINUE.
- UFFDIO_REGISTER_MODE_WP (depuis Linux 5.7)
-
Quand il est enregistré avec le mode UFFDIO_REGISTER_MODE_WP, l'espace
utilisateur recevra une notification d'erreur de page lors d'une écriture
sur une page protégée en écriture. L'exécution du thread fautif sera arrêtée
jusqu'à ce que l'espace utilisateur supprime la protection de la page en
utilisant un ioctl UFFDIO_WRITEPROTECT.
Plusieurs modes peuvent être activés en même temps pour le même intervalle
de mémoire.
Depuis Linux 4.14, une notification d'erreur de page d'userfaultfd peut
incorporer de façon sélective des informations d'identifiant des threads en
erreur dans une notification. Il est nécessaire d'activer cette
fonctionnalité explicitement en utilisant le bit de fonction
UFFD_FEATURE_THREAD_ID lors de l'initialisation du contexte
d'userfaultfd. Par défaut, la déclaration de l'identifiant du thread est
désactivée.
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.'
Depuis Linux 4.11, userfaultfd peut aussi informer les threads gérant les
erreurs des modifications dans la disposition de la mémoire virtuelle du
processus en erreur. 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.
Depuis Linux 5.7, userfaultfd peut effectuer le suivi synchrone de page sale
en utilisant le nouveau mode d'enregistrement de page protégée en
écriture. Il faut vérifier le bit de fonction
UFFD_FEATURE_PAGEFAULT_FLAG_WP avant d'utiliser cette fonctionnalité. Le
mode protection en écriture, similaire au mode d'origine page manquante
d'userfaultfd, génère une notification d'userfaultfd quand la page protégée
en écriture est écrite. L'utilisateur doit résoudre l'erreur de page en
déprotégeant la page fautive et en forçant le thread fautif à
continuer. Pour plus d'informations, consultez la section « Mode protection
d'écriture d'userfaultfd »
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 en deux étapes entre le
noyau et l'espace utilisateur pour déterminer quelle version de l'API et
quelles fonctions sont prises en charge par le noyau. et ensuite pour
activer les fonctions voulues par l'espace utilisateur. 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
d'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 diverses opérations
d'ioctl(2) (parexemple, UFFDIO_COPY, UFFDIO_ZEROPAGE ou
UFFDIO_CONTINUE) pour résoudre l'erreur de page.
Depuis Linux 4.4, si l'application définit le bit de la fonction
UFFD_FEATURE_SIGBUS en utilisant l'ioctl(2) UFFDIO_API, aucune
notification d'erreur d 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.
Mode protection d'écriture d'userfaultfd (depuis Linux 5.7)
Depuis Linux 5.7, userfaultfd prend en charge le mode protection d'écriture
pour la mémoire anonyme. L'utilisateur doit d'abord vérifier la
disponibilité de cette fonctionnalité en utilisant l'ioctl UFFDIO_API sur
le bit de fonction UFFD_FEATURE_PAGEFAULT_FLAG_WP avant d'utiliser cette
fonctionnalité.
Depuis Linux 5.19, le mode protection d'écriture est aussi pris en charge
sur la mémoire de type shmem ou hugetlbfs. Il peut être détecté avec le bit
de fonction UFFD_FEATURE_WP_HUGETLBFS_SHMEM.
Pour enregistrer avec le mode page protégée en écriture de userfaultfd,
l'utilisateur doit initier l'ioctl UFFDIO_REGISTER avec le mode
UFFDIO_REGISTER_MODE_WP défini. Notez qu'il est permis de surveiller le
même intervalle de mémoire avec plusieurs modes. Par exemple, un utilisateur
peut effectuer UFFDIO_REGISTER avec le mode défini à
UFFDIO_REGISTER_MODE_MISSING | UFFDIO_REGISTER_MODE_WP. Quand seul le
mode UFFDIO_REGISTER_MODE_WP est enregistré, l'espace utilisateur ne
recevra aucune notification quand une page manquante est écrite. À la
place, l'espace utilisateur ne recevra une notification d'erreur de page
protégée en écriture que quand une page existante et protégée en écriture
est écrite.
Après que l'ioctl UFFDIO_REGISTER s'est terminé avec le mode
UFFDIO_REGISTER_MODE_WP défini, l'utilisateur peut protéger en écriture
toute mémoire dans l'intervalle en utilisant l'ioctl UFFDIO_WRITEPROTECT
où uffdio_writeprotect.mode devrait être défini à
UFFDIO_WRITEPROTECT_MODE_WP.
Quand un événement de protection en écriture survient, l'espace utilisateur
recevra une notification d'erreur de page dont l'uffd_msg.pagefault.flags
aura l'attribut UFFD_PAGEFAULT_FLAG_WP défini. Notez : dans la mesure où
seulement les écritures peuvent déclencher ce genre d'erreur, les
notifications de protection en écriture auront toujours le bit
UFFD_PAGEFAULT_FLAG_WRITE défini en même temps que le bit
UFFD_PAGEFAULT_FLAG_WP.
Pour résoudre une erreur de page de protection d'écriture, l'utilisateur
doit initier un autre ioctl UFFDIO_WRITEPROTECT dont
l'uffd_msg.pagefault.flags doit avoir l'attribut
UFFDIO_WRITEPROTECT_MODE_WP effacé après la page ou l'intervalle fautif.
Mode erreur mineure d'userfaultfd (depuis Linux 5.13)
Depuis Linux 5.13, userfaultfd prend en charge le mode erreur mineure. Dans
ce mode, les messages d’erreur ne sont pas produits pour des erreurs
majeures (où les pages étaient absentes), mais plutôt pour des erreurs
mineures où une page existe dans le cache de page, mais où les entrées de la
table de pages ne sont pas encore présentes. L'utilisateur doit d'abord
vérifier la disponibilité de cette fonctionnalité en utilisant l'ioctl
UFFDIO_API avec les bits de fonction appropriés avant d'utiliser cette
fonctionnalité : UFFD_FEATURE_MINOR_HUGETLBFS depuis Linux 5.13 ou
UFFD_FEATURE_MINOR_SHMEM depuis Linux 5.14.
Pour enregistrer avec le mode erreur mineure d'userfaultfd, l'utilisateur
doit initier l'ioctl UFFDIO_REGISTER avec le mode
UFFD_REGISTER_MODE_MINOR défini.
Quand une erreur mineure survient, l'espace utilisateur recevra une
notification d'erreur de page dont l'uffd_msg.pagefault.flags aura
l'attribut UFFD_PAGEFAULT_FLAG_MINOR défini.
Pour résoudre une erreur de page mineure, le gestionnaire doit décider si le
contenu de la page existante doit être modifiée d'abord, ou non. Si c'est le
cas, cela doit être fait à son emplacement au moyen d'un second mappage non
enregistré par userfaultfd vers la même page de sauvegarde (par exemple en
mappant deux fois le fichier shmem ou hugetlbfs). Une fois que la page est
considérée « à jour », l'erreur peut être résolue en initiant un ioctl
UFFDIO_CONTINUE qui installe les entrées de la table de pages et (par
défaut) réveille le ou les threads en erreur.
Le mode erreur mineure ne prend en charge que la mémoire s'appuyant sur
hugetlbfs (depuis Linux 5.13) et sur shmem (depuis Linux 5.14).
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 */
union {
__u32 ptid; /* ID du thread de l'erreur */
} feat;
} 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 :
- event
-
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 :
-
- UFFD_EVENT_PAGEFAULT (depuis Linux 4.3)
-
Un événement d'erreur de page. Les détails de l'erreur de page sont
disponibles dans le champ pagefault.
- UFFD_EVENT_FORK (depuis Linux 4.11)
-
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.
- UFFD_EVENT_REMAP (depuis Linux 4.11)
-
Généré lorsque le processus en erreur invoque mremap(2). Les détails de
l'événement sont disponibles dans le champ remap.
- UFFD_EVENT_REMOVE (depuis Linux 4.11)
-
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.
- UFFD_EVENT_UNMAP (depuis Linux 4.11)
-
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.
- pagefault.address
-
L'adresse qui a déclenché l'erreur de page.
- pagefault.flags
-
Un masque de bits qui décrit l'événement. Pour UFFD_EVENT_PAGEFAULT, les
attributs suivants peuvent apparaître :
-
- UFFD_PAGEFAULT_FLAG_WP
-
Si cet attribut est défini, alors l'erreur était une erreur de protection en
écriture.
- UFFD_PAGEFAULT_FLAG_MINOR
-
Si cet attribut est défini, alors l'erreur était une erreur mineure.
- UFFD_PAGEFAULT_FLAG_WRITE
-
Si cet attribut est défini, alors l'erreur était une erreur d'écriture.
Si ni UFFD_PAGEFAULT_FLAG_WP ni UFFD_PAGEFAULT_FLAG_MINOR ne sont
définis, l'erreur était une erreur d'absence.
- pagefault.feat.pid
-
L'identifiant du thread qui a déclenché l'erreur de page.
- fork.ufd
-
Le descripteur de fichier associé à l'objet userfault créé pour l'enfant
créé par fork(2).
- remap.from
-
L'adresse d'origine de la plage de mémoire dont le mappage a été modifié en
utilisant madvise(2).
- remap.to
-
La nouvelle adresse de la plage de mémoire dont le mappage a été modifié en
utilisant madvise(2).
- remap.len
-
La taille d'origine de la plage de mémoire dont le mappage a été modifié en
utilisant madvise(2).
- remove.start
-
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é.
- remove.end
-
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 :
- EINVAL
-
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 à l'objet userfaultfd. En cas d'erreur, la fonction
renvoie -1 et errno est défini pour indiquer l'erreur.
ERREURS
- EINVAL
-
Une valeur non prise en compte a été spécifiée dans flags.
- EMFILE
-
La limite par processus du nombre de descripteurs de fichier ouverts a été
atteinte.
- ENFILE
-
La limite du nombre total de fichiers ouverts pour le système entier a été
atteinte.
- ENOMEM
-
La mémoire disponible du noyau n'était pas suffisante.
- EPERM (depuis Linux 5.2)
-
L'appelant n'est pas privilégié (il n'a pas la capacité CAP_SYS_PTRACE
dans l'espace de noms initial) et /proc/sys/vm/unprivileged_userfaultfd a
la valeur 0.
STANDARDS
Linux.
HISTORIQUE
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
NOTES
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.
EXEMPLES
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 <err.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <unistd.h>
static int page_size;
static void *
fault_handler_thread(void *arg)
{
int nready;
long uffd; /* descripteur du fichier userfaultfd */
ssize_t nread;
struct pollfd pollfd;
struct uffdio_copy uffdio_copy;
static int fault_cnt = 0; /* Nombres d'erreurs déjà gérées */
static char *page = NULL;
static struct uffd_msg msg; /* Données lues à partir de userfaultfd */
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)
err(EXIT_FAILURE, "mmap");
}
/* Boucle gérant les événements entrants sur le descripteur
de fichier userfaultfd. */
for (;;) {
/* Voir ce que poll() nous dit sur l'userfaultfd. */
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
if (nready == -1)
err(EXIT_FAILURE, "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)
err(EXIT_FAILURE, "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 = %"PRIx64"; ", msg.arg.pagefault.flags);
printf("address = %"PRIx64"\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)
err(EXIT_FAILURE, "ioctl-UFFDIO_COPY");
printf(" (uffdio_copy.copy returned %"PRId64")\n",
uffdio_copy.copy);
}
}
int
main(int argc, char *argv[])
{
int s;
char c;
char *addr; /* Début de la région gérée par userfaultfd */
long uffd; /* Descripteur de fichier userfaultfd */
size_t len, l; /* 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;
if (argc != 2) {
fprintf(stderr, "Utilisation : %s num-pages\n", argv[0]);
exit(EXIT_FAILURE);
}
page_size = sysconf(_SC_PAGE_SIZE);
len = strtoull(argv[1], NULL, 0) * page_size;
/* Créer et activer un objet userfaultfd. */
uffd = syscall(SYS_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1)
err(EXIT_FAILURE, "userfaultfd");
/* NOTE : Une connexion de fonction en deux étapes est inutile ici,
dans la mesure où l'exemple n'a besoin d'aucune fonction
particulière.
Les programmes qui *agissent* doivent appeler UFFDIO_API deux fois :
une fois avec « features = 0 » pour détecter les fonctions prises en
charge par ce noyau, puis avec le sous-ensemble de fonctions que le
programme veut vraiment activer. */
uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)
err(EXIT_FAILURE, "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)
err(EXIT_FAILURE, "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)
err(EXIT_FAILURE, "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) {
errc(EXIT_FAILURE, s, "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. */
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) {
c = addr[l];
printf("Read address %p in %s(): ", addr + l, __func__);
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/admin-guide/mm/userfaultfd.rst dans l'arborescence des
sources du noyau Linux
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 à
Index
- NOM
-
- BIBLIOTHÈQUE
-
- SYNOPSIS
-
- DESCRIPTION
-
- Utilisation
-
- Fonctionnement d'userfaultfd
-
- Mode protection d'écriture d'userfaultfd (depuis Linux 5.7)
-
- Mode erreur mineure d'userfaultfd (depuis Linux 5.13)
-
- Lire à partir de la structure userfaultfd
-
- VALEUR RENVOYÉE
-
- ERREURS
-
- STANDARDS
-
- HISTORIQUE
-
- NOTES
-
- BOGUES
-
- EXEMPLES
-
- Source du programme
-
- VOIR AUSSI
-
- TRADUCTION
-
This document was created by
man2html,
using the manual pages.
Time: 05:06:06 GMT, September 19, 2025