Scroll to navigation

epoll(7) Miscellaneous Information Manual epoll(7)

NOM

epoll – Notifications d'événements d'entrées et sorties

SYNOPSIS

#include <sys/epoll.h>

DESCRIPTION

L'interface de programmation (API) epoll réalise une tâche similaire à poll(2) : surveiller plusieurs descripteurs de fichier pour voir si des E/S y sont possibles. L'API epoll peut être déclenchée par changement de niveau ou par changement d'état et s'adapte bien à un grand nombre de descripteurs surveillés.

Le concept central de l’API epoll est l’instance d’epoll, une structure interne au noyau qui, du point de vue espace utilisateur, peut être considérée comme un conteneur pour deux listes :

La liste interest (parfois appelée l’ensemble epoll) : l’ensemble des descripteurs de fichier que le processus a enregistrés comme intéressants à surveiller.
La liste ready : l’ensemble des descripteurs de fichier prêts (ready) pour des E/S. Cette liste est un sous-ensemble de (plus précisément, un ensemble de références de) descripteurs de fichier de la liste interest. La liste ready est alimentée dynamiquement par le noyau selon le résultat des activités d’E/S de ces descripteurs de fichier.

Les appels système suivants sont fournis pour créer et gérer une instance d’epoll :

epoll_create(2) crée une instance d’epoll et renvoie un descripteur de fichier référençant cette instance. La version plus récente d'epoll_create1(2) étend les fonctionnalités d'epoll_create(2).
L'intérêt pour des descripteurs de fichier particuliers est ensuite enregistré avec epoll_ctl(2), qui ajoute les articles dans la liste interest de l’instance d’epoll.
epoll_wait(2) attend les événements d'E/S, en bloquant le thread appelant si aucun événement n'est actuellement disponible. Cet appel système peut être considéré comme recherchant des articles dans la liste ready de l’instance d’epoll.

Détection par changement de niveau ou d’état

L'interface de distribution d'événements d’epoll est capable de se comporter en détection de changement de niveau (Level Triggered — LT) ou d’état (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 d’epoll.
(2)
Une écriture dans le tube envoie 2 Ko de données du côté écriture du tube.
(3)
Un appel à epoll_wait(2) est effectué et renvoie rfd comme descripteur de fichier prêt.
(4)
Un lecteur du tube lit 1 Ko de données depuis rfd.
(5)
Un appel d’epoll_wait(2) est effectué.

Si le descripteur rfd a été ajouté à l'ensemble epoll en utilisant l'attribut EPOLLET (détection de changement d'état), 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 le tampon d'entrée du fichier tandis que 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 détectés par changement d’état délivre les événements seulement lorsque des événements surviennent dans le descripteur de 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 l’étape 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 affame une tâche qui gère plusieurs descripteurs de fichier. L'utilisation préconisée d'epoll comme interface en détection par changement d’état (EPOLLET) est la suivante :

(1)
avec des descripteurs non bloquants ;
(2)
en attente d’évènement seulement après qu'un read(2) ou un write(2) ait renvoyé EAGAIN.

En revanche, lorsqu'il est utilisé avec l'interface en détection par changement 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.

Puisque même dans un epoll de type détection le changement d'état, 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.

Si plusieurs threads (ou processus si les processus enfant ont hérité du descripteur de fichier d’epoll à travers fork(2)) sont bloqués dans epoll_wait(2) en attente du même descripteur de fichier d’epoll et qu’un descripteur de fichier dans la liste interest, qui est marqué pour une notification par détection de changement d'état (EPOLLET), devienne prêt, seul un des threads (ou processus) est réveillé de epoll_wait(2). Cela fournit une optimisation utile pour éviter la bousculade de réveils (thundering herd) dans certain scénarios.

Interaction avec autosleep

Si le système est en mode autosleep à l’aide de /sys/power/autosleep et qu’un événement survient et sort le périphérique de sa veille, le pilote de périphérique ne gardera le périphérique actif que jusqu’à la mise en file d’attente de l’événement. Pour garder le périphérique actif jusqu’au traitement de l’événement, l’attribut EPOLLWAKEUP d’epoll_ctl(2) doit être utilisé.

Quand l’attribut EPOLLWAKEUP est défini dans le champ events pour une struct epoll_event, le système sera gardé actif à partir du moment où l’événement est mis en file d’attente, à l’aide de l’appel epoll_wait(2) qui renvoie l’événement jusqu’à l’appel epoll_wait(2) suivant. Si l’événement doit garder le système actif au delà de ce moment, alors un wake_lock séparé devrait être pris avant le second appel à epoll_wait(2).

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 d’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 d’epoll avec un déclenchement par changement de niveau correspond à la même sémantique que poll(2), le déclenchement par changement d'état nécessite plus de clarification pour éviter des décrochages dans la boucle d’évènements de l’application. Dans cet exemple, l’écouteur emploie un socket non bloquant sur lequel listen(2) a été appelé. La fonction do_use_fd() va utiliser le nouveau descripteur de fichier jusqu'à ce qu’EAGAIN soit renvoyé par read(2) ou par write(2). Une application d’automate fini piloté par les évènements devrait, après réception d'EAGAIN, enregistrer l'état en cours, afin que lors de l’appel suivant à do_use_fd(), elle continue avec le read(2) ou le write(2) là où elle s'est arrêtée.


#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;
/* Code to set up listening socket, 'listen_sock',

(socket(), bind(), listen()) omitted. */ epollfd = epoll_create1(0); if (epollfd == -1) {
perror("epoll_create1");
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_wait");
exit(EXIT_FAILURE);
}
for (n = 0; n < nfds; ++n) {
if (events[n].data.fd == listen_sock) {
conn_sock = accept(listen_sock,
(struct sockaddr *) &addr, &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 d’epoll (EPOLL_CTL_ADD) après, 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

Quelle est la clé utilisée pour distinguer les descripteurs de fichier enregistrés dans une liste interest ?
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).
Que se passe-t-il si on enregistre deux fois le même descripteur de fichier dans une instance d’epoll ?
Vous aurez probablement un EEXIST. Cependant il est possible d'ajouter un duplicata de descripteur (dup(2), dup2(2), F_DUPFD de fcntl(2)) sur la même instance d’epoll. Cela peut être une technique utile pour le filtrage d'événements, si les descripteurs dupliqués sont enregistrés avec des masques events différents.
Deux instances d’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 d’epoll ?
Oui, et les événements seront rapportés aux deux. Toutefois, une programmation soignée est nécessaire pour que cela soit fait correctement.
Est-ce que le descripteur d’epoll lui-même est sujet à poll/epoll/select ?
Oui. Si un descripteur de fichier d’epoll a des événements en attente, alors il indiquera qu'il est lisible.
Que se passe-t-il si on cherche à placer un descripteur d’epoll dans son propre ensemble de descripteurs de fichier  ?
L'appel epoll_ctl(2) échouera (EINVAL). Toutefois vous pouvez ajouter un descripteur d’epoll dans un autre ensemble de descripteurs de fichier d’epoll.
Puis-je envoyer le descripteur d’epoll à travers un socket UNIX vers un autre processus ?
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 la liste interest.
Est-ce que la fermeture d'un descripteur le supprime automatiquement de toutes les listes interest d’epoll ?
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), F_DUPFD de fcntl(2) 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'une liste interest qu'après la fermeture de tous les descripteurs de fichier qui se réfèrent à la description de fichier ouvert sous-jacente. Cela signifie que même après la fermeture d'un descripteur de fichier faisant partie de cette liste, des événements peuvent toujours être rapporté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. Pour empêcher cela, le descripteur de fichier doit être explicitement supprimé de la liste (en utilisant epoll_ctl(2) EPOLL_CTL_DEL) avant qu’il ne soit dupliqué. Autrement, l’application doit assurer que tous les descripteurs soient fermés (ce qui peut être difficile si les descripteurs ont été dupliqués en dehors du cadre par des fonctions de bibliothèque qui utilisent dup(2) ou fork(2))
Si plus d'un événement surviennent entre deux appels epoll_wait(2), sont-ils combinés ou rapportés séparément ?
Ils sont combinés.
Est-ce qu'une opération sur un descripteur affecte les événements déjà collectés mais pas encore rapportés ?
Vous pouvez faire deux choses sur un descripteur existant. Une suppression serait sans effet dans ce cas. Une modification revérifie les entrées et sorties disponibles.
Dois-je lire/écrire sans cesse un descripteur jusqu'à obtenir EAGAIN si l'attribut EPOLLET est utilisé (comportement par détection de changement d'état) ?
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. Il doit être considéré comme 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, un socket datagramme ou un terminal en mode canonique), la seule façon de détecter la fin de l'espace d'entrée et 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 et sorties peut aussi être détectée en vérifiant la quantité de données lues ou écrites sur 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ûr 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 pas garantir que le descripteur de fichier surveillé corresponde toujours à un fichier de type flux).

Erreurs possibles et moyens de les éviter

Starvation (edge-triggered)
S'il y a un gros volume d’espace d’E/S, il est possible qu'en essayant de les traiter, d'autres fichiers ne soient pas pris en compte provoquant une famine. Ce problème n'est pas spécifique à epoll.
La solution est de maintenir une liste de descripteurs prêts et de marquer le descripteur de fichier prêt dans leur structure associée, permettant à l'application de savoir quels fichiers traiter mais toujours en tourniquet englobant tous les fichiers prêts. Cela permet aussi d'ignorer les événements ultérieurs sur des descripteurs prêts.
If using an event cache...
Si vous utilisez un cache d'événements, ou stockez tous les descripteurs renvoyés par epoll_wait(2), alors assurez-vous de disposer d'un moyen de marquer dynamiquement leurs fermetures (c’est-à-dire causées par un traitement d’événement précédent). Supposons que vous recevez 100 événements d’epoll_wait(2) et que l'événement 47 implique de fermer l’évènement 13. Si vous supprimez la structure et utilisez close(2) pour le descripteur de fichier pour l’évènement 13, alors votre cache peut encore contenir des événements pour ce descripteur, posant alors des problèmes de confusion.
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), puis marquer sa structure associée comme supprimée et la lier à une liste de nettoyage. 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

The epoll API was introduced in Linux kernel 2.5.44. Support was added in glibc 2.3.2.

STANDARDS

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.

NOTES

The set of file descriptors that is being monitored via an epoll file descriptor can be viewed via the entry for the epoll file descriptor in the process's /proc/pid/fdinfo directory. See proc(5) for further details.

L’opération KCMP_EPOLL_TFD de kcmp(2) peut être utilisée pour tester si un descripteur de fichier est présent dans une instance d’epoll.

VOIR AUSSI

epoll_create(2), epoll_create1(2), epoll_ctl(2), epoll_wait(2), poll(2), select(2)

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-Paul Guillonneau <guillonneau.jeanpaul@free.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.

5 février 2023 Pages du manuel de Linux 6.03