userfaultfd(2)

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 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 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 les opérations d'ioctl(2) 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 of event */
        ...
        union {
            struct {
                __u64 flags;    /* Flags describing fault */
                __u64 address;  /* Faulting address */
                union {
                    __u32 ptid; /* Thread ID of the fault */
                } feat;
            } pagefault;

            struct {            /* Since Linux 4.11 */
                __u32 ufd;      /* Userfault file descriptor
                                   of the child process */
            } fork;

            struct {            /* Since Linux 4.11 */
                __u64 from;     /* Old address of remapped area */
                __u64 to;       /* New address of remapped area */
                __u64 len;      /* Original mapping length */
            } remap;

            struct {            /* Since Linux 4.11 */
                __u64 start;    /* Start address of removed area */
                __u64 end;      /* End address of removed area */
            } remove;
            ...
        } arg;

        /* Padding fields omitted */
    } __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;   /* userfaultfd file descriptor */
        ssize_t             nread;
        struct pollfd       pollfd;
        struct uffdio_copy  uffdio_copy;

        static int      fault_cnt = 0; /* Number of faults so far handled */
        static char     *page = NULL;
        static struct uffd_msg  msg;  /* Data read from userfaultfd */

        uffd = (long) arg;

        /* Create a page that will be copied into the faulting region. */

        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");
        }

        /* Loop, handling incoming events on the userfaultfd
           file descriptor. */

        for (;;) {

            /* See what poll() tells us about the 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);

            /* Read an event from the 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");

            /* We expect only one kind of event; verify that assumption. */

            if (msg.event != UFFD_EVENT_PAGEFAULT) {
                fprintf(stderr, "Unexpected event on userfaultfd\n");
                exit(EXIT_FAILURE);
            }

            /* Display info about the page-fault event. */

            printf("    UFFD_EVENT_PAGEFAULT event: ");
            printf("flags = %"PRIx64"; ", msg.arg.pagefault.flags);
            printf("address = %"PRIx64"\n", msg.arg.pagefault.address);

            /* Copy the page pointed to by 'page' into the faulting
               region. Vary the contents that are copied in, so that it
               is more obvious that each fault is handled separately. */

            memset(page, 'A' + fault_cnt % 20, page_size);
            fault_cnt++;

            uffdio_copy.src = (unsigned long) page;

            /* We need to handle page faults in units of pages(!).
               So, round faulting address down to page boundary. */

            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;   /* Start of region handled by userfaultfd */
        long       uffd;    /* userfaultfd file descriptor */
        size_t     len, l;  /* Length of region handled by userfaultfd */
        pthread_t  thr;     /* ID of thread that handles page faults */
        struct uffdio_api       uffdio_api;
        struct uffdio_register  uffdio_register;

        if (argc != 2) {
            fprintf(stderr, "Usage: %s num-pages\n", argv[0]);
            exit(EXIT_FAILURE);
        }

        page_size = sysconf(_SC_PAGE_SIZE);
        len = strtoull(argv[1], NULL, 0) * page_size;

        /* Create and enable userfaultfd object. */

        uffd = syscall(SYS_userfaultfd, O_CLOEXEC | O_NONBLOCK);
        if (uffd == -1)
            err(EXIT_FAILURE, "userfaultfd");

        uffdio_api.api = UFFD_API;
        uffdio_api.features = 0;
        if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)
            err(EXIT_FAILURE, "ioctl-UFFDIO_API");

        /* Create a private anonymous mapping. The memory will be
           demand-zero paged--that is, not yet allocated. When we
           actually touch the memory, it will be allocated via
           the 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);

        /* Register the memory range of the mapping we just created for
           handling by the userfaultfd object. In mode, we request to track
           missing pages (i.e., pages that have not yet been faulted in). */

        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");

        /* Create a thread that will process the userfaultfd events. */

        s = pthread_create(&thr, NULL, fault_handler_thread, (void *) uffd);
        if (s != 0) {
            errc(EXIT_FAILURE, s, "pthread_create");
        }

        /* Main thread now touches memory in the mapping, touching
           locations 1024 bytes apart. This will trigger userfaultfd
           events for all pages in the region. */

        l = 0xf;    /* Ensure that faulting address is not on a page
                       boundary, in order to test that we correctly
                       handle that case in 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);         /* Slow things down a little */
        }

        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 (https://www.gnu.org/licenses/gpl-3.0.html) 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.