EPOLL(7)

NOM
epoll - Notifications d'événements d'entrées-sorties

SYNOPSIS
#include <sys/epoll.h>

DESCRIPTION
L'interface de programmation (API) epoll réalise une tâche similaire à poll(2) : la surveillance de plusieurs descripteurs de fichier pour voir si les E/S y sont possibles. L'API epoll peut être déclenchée par niveau ou par changement d'état, et s'adapte bien à un grand nombre de descripteurs simultanés. Les appels système suivants sont fournis pour créer et superviser une instance epoll :

*   epoll_create(2) créé une instance epoll et renvoie un descripteur de fichier référençant cette instance (la dernière version d'epoll_create1(2) étend les fonctionnalités d'epoll_create(2)).

*   L'intérêt pour un descripteur de fichier est ensuite enregistré avec epoll_ctl(2). L'ensemble des descripteurs de fichiers actuellement enregistrés pour une instance epoll est parfois appelé un ensemble epoll.

*   epoll_wait(2) attend les événements d'E/S, en bloquant le thread appelant si aucun événement n'est actuellement disponible.

  Détection de niveau et détection de transition
L'interface de distribution d'événements de epoll est capable de se comporter en détection de niveau (Level Triggered - LT) ou en détection de transition (Edge Triggered - ET). La différence entre ces deux mécanismes est décrite ci-dessous. Supposons que le scénario suivant se produise :

1.  Le descripteur de fichier qui représente le côté lecture d'un tube (rfd) est enregistré dans l'instance epoll.

2.  Celui qui écrit dans le tube envoie 2 Ko de données.

3.  Un appel à epoll_wait(2) est effectué et renvoie rfd comme descripteur de fichier prêt.

4.  Le lecteur du tube lit 1 Ko de données depuis rfd.

5.  Un appel de epoll_wait(2) est effectué.

Si le descripteur rfd a été ajouté à l'ensemble epoll en utilisant l'attribut EPOLLET (edge-triggered), l'appel epoll_wait(2), réalisé à l'étape 5, va probablement bloquer bien qu'il y ait des données toujours présentes dans les tampons d'entrée du fichier et le pair distant attendra une réponse basée sur les données qu'il a déjà envoyées. La raison en est que le mécanisme de distribution d'événements Edge Triggered délivre les événements seulement lorsque des événements surviennent sur le fichier supervisé. Ainsi, à l'étape 5, l'appelant peut attendre des données qui sont déjà présentes dans le tampon d'entrée. Dans l'exemple ci-dessus, un événement sur rfd sera déclenché à cause de l'écriture à l'étape 2, et l'événement est consommé dans 3. Comme l'opération de lecture de l'étape 4 ne consomme pas toutes les données du tampon, l'appel à epoll_wait(2) effectué à l'étape 5 peut verrouiller indéfiniment.

Une application qui emploie l'attribut EPOLLET de la fonction epoll devrait toujours utiliser des descripteurs non bloquants pour éviter qu'une lecture ou une écriture ne bloque, par une famine, une tâche qui gère plusieurs descripteurs de fichier. L'utilisation préconisée d'epoll avec l'interface en détection de changements (EPOLLET) est la suivante :

    i   avec des descripteurs non bloquants ; et

    ii  en attendant seulement après qu'un read(2) ou un write(2) a renvoyé EAGAIN.

Au contraire, lorsqu'il est utilisé avec l'interface en détection de niveau (par défaut si EPOLLET n'est pas spécifié), epoll est une alternative plus rapide à poll(2), et peut être employé chaque fois que ce dernier est utilisé, car il utilise la même sémantique.

Même dans un epoll de type Edge Triggered, plusieurs événements peuvent être générés à la réception de nombreux blocs de données. L'appelant peut, en spécifiant l'attribut EPOLLONESHOT, faire désactiver par epoll le descripteur de fichier associé, après la réception d'un événement avec epoll_wait(2). Lorsque l'attribut EPOLLONESHOT est spécifié, il est de la responsabilité de l'appelant de réarmer le descripteur en utilisant epoll_ctl(2) avec EPOLL_CTL_MOD.

  Interfaces /proc
Les interfaces suivantes peuvent être utilisées pour limiter la quantité de mémoire du noyau utilisée par epoll :

/proc/sys/fs/epoll/max_user_watches (depuis Linux 2.6.28)
    Cela définit une limite au nombre total de descripteurs de fichiers qu'un utilisateur peut enregistrer au travers de toutes les instances epoll du système. La limite est imposée par identifiant d'utilisateur réel. Chaque descripteur de fichier enregistré coûte environ 90 octets sur un noyau 32 bits et environ 160 octets sur un noyau 64 bits. Actuellement la valeur par défaut pour max_user_watches est de 1/25 (4%) de la mémoire basse disponible, divisé par le coût d'allocation en octets.

  Exemple d'utilisation
Tandis que l'utilisation de epoll avec un déclenchement par niveau correspond à la même sémantique que poll(2), le déclenchement par changement d'état nécessite plus d'explication pour éviter les cas de blocage. Dans cet exemple, le lecteur emploie une socket non bloquante sur laquelle listen(2) a été appelée. La fonction do_use_fd() va utiliser le nouveau descripteur de fichier, jusqu'à ce que EAGAIN soit renvoyé par read(2) ou par write(2). Une application fonctionnant par transition d'état devrait, après réception d'EAGAIN, enregistrer l'état en cours, afin que l'appel suivant de do_use_fd() continue avec le read(2) ou le write(2) où il s'est arrêté.

#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;

/* Set up listening socket, 'listen_sock' (socket(),
   bind(), listen()) */

epollfd = epoll_create(10);
if (epollfd == -1) {
    perror("epoll_create");
    exit(EXIT_FAILURE);
}

ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
    perror("epoll_ctl: listen_sock");
    exit(EXIT_FAILURE);
}

for (;;) {
    nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
    if (nfds == -1) {
        perror("epoll_pwait");
        exit(EXIT_FAILURE);
    }

    for (n = 0; n < nfds; ++n) {
        if (events[n].data.fd == listen_sock) {
            conn_sock = accept(listen_sock,
                            (struct sockaddr *) &local, &addrlen);
            if (conn_sock == -1) {
                perror("accept");
                exit(EXIT_FAILURE);
            }
            setnonblocking(conn_sock);
            ev.events = EPOLLIN | EPOLLET;
            ev.data.fd = conn_sock;
            if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
                        &ev) == -1) {
                perror("epoll_ctl: conn_sock");
                exit(EXIT_FAILURE);
            }
        } else {
            do_use_fd(events[n].data.fd);
        }
    }
}

Lorsqu'on utilise une détection de changement d'états, pour des raisons de performances, il est possible d'ajouter le descripteur de fichier dans l'interface epoll (EPOLL_CTL_ADD) une fois, en spécifiant (EPOLLIN|EPOLLOUT). Cela évite de basculer sans cesse entre EPOLLIN et EPOLLOUT lors des appels epoll_ctl(2) avec EPOLL_CTL_MOD.

  Questions/Réponses

Q0  Quelle est la clé utilisée pour distinguer les descripteurs de fichier enregistrés dans un ensemble epoll ?

A0  La clé est une combinaison du numéro du descripteur de fichier et de la description du fichier ouvert (aussi connue comme « open file handle », la représentation interne au noyau d'un fichier ouvert).

Q1  Que se passe-t-il si on enregistre deux fois le même descripteur de fichier dans une instance epoll ?

A1  Vous aurez probablement un EEXIST. Cependant il est possible d'ajouter un duplicat de descripteur (dup(2), dup2(2), fcntl(2) F_DUPFD) sur la même instance epoll. Cela peut être une technique utile pour le filtrage d'événements, si les descripteurs duplicats sont enregistrés avec un masque d'événements events différent.

Q2  Deux instances epoll peuvent-elles attendre le même descripteur de fichier ? Si oui, les événements seront-ils reportés sur les deux descripteurs de fichier epoll en même temps ?

A2  Oui, et les événements seront rapportés aux deux. Toutefois, une programmation soignée est nécessaire pour que cela soit fait correctement.

Q3  Peut-on utiliser le descripteur de epoll lui-même avec poll/epoll/select ?

A3  Oui. Si un descripteur de fichier epoll a des événements en attente, alors il indiquera qu'il est lisible.

Q4  Que se passe-t-il si on cherche à placer un descripteur de epoll dans son propre ensemble ?

A4  L'appel à epoll_ctl(2) échouera (EINVAL). Toutefois vous pouvez ajouter un descripteur de epoll dans un autre ensemble epoll.

Q5  Puis-je envoyer le descripteur de epoll à travers une socket UNIX vers un autre processus ?

A5  Oui, mais il n'y a aucune raison de faire ça, puisque le processus récepteur n'aura pas de copie des descripteurs de fichier de l'ensemble epoll.

Q6  Est-ce que la fermeture d'un descripteur le supprime automatiquement de tous les ensembles epoll ?

A6  Oui, mais prenez note des points suivants. Un descripteur de fichier est une référence vers la description d'un fichier ouvert (consultez open(2)). À chaque fois qu'un descripteur est dupliqué avec dup(2), dup2(2), fcntl(2) F_DUPFD ou fork(2), un nouveau descripteur de fichier qui se réfère au même fichier ouvert est créé. Une description de fichier ouvert continue d'exister jusqu'à ce que tous les descripteurs de fichier qui s'y réfèrent soient fermés. Un descripteur de fichier n'est retiré d'un ensemble epoll qu'après la fermeture de tous les descripteurs de fichier qui se réfèrent à la description de fichier ouvert sous-jacente (ou avant si le descripteur est explicitement retiré en utilisant epoll_ctl(2) EPOLL_CTL_DEL). Cela signifie que même après la fermeture d'un descripteur de fichier d'un ensemble epoll, des événements peuvent toujours être remontés pour ce descripteur de fichier si d'autres descripteurs de fichier, se référant à la même description de fichier sous-jacente, restent ouverts.

Q7  Si plus d'un événement surviennent entre deux appels epoll_wait(2), sont-ils combinés ou rapportés séparément ?

A7  Ils sont combinés.

Q8  Est-ce qu'une opération sur un descripteur affecte les événements déjà collectés mais pas encore rapportés ?

A8  Vous pouvez faire deux choses sur un descripteur existant. Une suppression serait sans signification dans ce cas. Une modification revérifie les entrées-sorties disponibles.

Q9  Dois-je lire/écrire sans cesse un descripteur jusqu'à obtenir EAGAIN avec l'attribut EPOLLET (comportement edge-triggered) ?

A9  La réception d'un événement depuis epoll_wait(2) suggère qu'un descripteur est prêt pour l'opération d'E/S désirée. Vous devez le considérer prêt jusqu'à ce que la prochaine lecture ou écriture (non bloquante) remonte un EAGAIN. Quand et comment utiliser le descripteur dépend de vous.

    Pour les fichiers orientés paquet ou jeton (par exemple, une socket datagramme ou un terminal en mode canonique), la seule façon de détecter la fin de l'espace d'entrée-sortie pour les lectures ou écritures est de continuer à lire ou écrire jusqu'à la réception d'un EAGAIN.

    Pour les fichiers orientés flux (par exemple, les tubes, FIFO ou sockets en mode flux), la disponibilité des entrées-sorties peut-être vérifiée par la quantité de données lues ou écrites avec le descripteur. Par exemple, si vous appelez read(2) en demandant la lecture d'une certaine quantité de données et que read(2) en renvoie moins, vous pouvez être sûrs d'avoir consommé tout le tampon d'entrée pour le descripteur. La même chose est vraie pour l'appel système write(2). (Évitez cette dernière technique si vous ne pouvez garantir que le descripteur de fichier surveillé correspond toujours à un fichier de type flux)

  Erreurs possibles et moyens de les éviter

o Famine (edge-triggered)

S'il y a un gros volume d'entrées-sorties, il est possible qu'en essayant de les traiter, d'autres fichiers ne soient pas pris en compte, ce qu'on appelle un cas de famine. Ce problème n'est pas spécifique à epoll.

La solution est de maintenir une liste de descripteurs prêts et de les marquer comme tels dans leur structure associée, permettant à l'application de savoir quels fichiers traiter, en organisant l'ordre au mieux. Cela permet aussi d'ignorer les événements ultérieurs sur des descripteurs prêts.

o Utilisation d'un cache d'événements...

Si vous utilisez un cache d'événement, ou stockez tous les descripteurs renvoyés par epoll_wait(2), alors assurez-vous de disposer d'un moyen de marquer dynamiquement leurs fermetures (causées par un événement précédent). Supposons que vous recevez 100 événements de epoll_wait(2), et que l'événement 47 implique de fermer le descripteur 13. Si vous supprimez la structure et utilisez close(2), alors votre cache peut encore contenir des événements pour ce descripteur, et poser des problèmes de cohérence.

Une solution est d'invoquer, pendant le traitement de l'événement 47, epoll_ctl(EPOLL_CTL_DEL) pour supprimer le descripteur 13, le fermer avec close(2), et marquer sa structure associée comme supprimée. Si vous rencontrez un autre événement pour le descripteur 13 dans votre traitement, vous verrez qu'il a été supprimé précédemment, sans que cela ne prête à confusion.

VERSIONS
L'API epoll a été introduite dans le noyau Linux 2.5.44. La prise en charge par la glibc a été ajoutée dans la version 2.3.2.

CONFORMITÉ
L'API epoll est spécifique à Linux. Certains autres systèmes fournissent des mécanismes similaires. Par exemple, FreeBSD propose kqueue et Solaris /dev/poll.

VOIR AUSSI
epoll_create(2), epoll_create1(2), epoll_ctl(2), epoll_wait(2)

COLOPHON
Cette page fait partie de la publication 3.65 du projet man-pages Linux. Une description du projet et des instructions pour signaler des anomalies peuvent être trouvées à l'adresse http://www.kernel.org/doc/man-pages/.

TRADUCTION
Depuis 2010, cette traduction est maintenue à l'aide de l'outil po4a <http://po4a.alioth.debian.org/> par l'équipe de traduction francophone au sein du projet perkamon <http://perkamon.alioth.debian.org/>.

Christophe Blaess <http://www.blaess.fr/christophe/> (1996-2003), Alain Portal <http://manpagesfr.free.fr/> (2003-2006). Julien Cristau et l'équipe francophone de traduction de Debian (2006-2009).

Veuillez signaler toute erreur de traduction en écrivant à <debian-l10n-french@lists.debian.org> ou par un rapport de bogue sur le paquet manpages-fr.

Vous pouvez toujours avoir accès à la version anglaise de ce document en utilisant la commande « man -L C <section> <page_de_man> ».