epoll
Table des matières
Retour à l'index
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).
/proc interfaces
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 pour régler le socket à l’écoute, 'listen_sock',
(socket(), bind(), listen()) omis. */
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 (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
- -
-
Famine (détection par changement d'état)
-
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.
- -
-
En cas d’utilisation d'un cache d'événements...
-
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
Certains autres systèmes fournissent des mécanismes similaires. Par exemple,
FreeBSD propose kqueue et Solaris /dev/poll.
STANDARDS
Linux.
HISTORIQUE
Linux 2.5.44. glibc 2.3.2.
NOTES
L’ensemble des descripteurs de fichier qui sont supervisés par un
descripteur de fichier d’epoll peut être visualisé à l’aide de l’entrée
pour le descripteur de fichier d’epoll dans le répertoire
/proc/pid/fdinfo du processus. Consultez proc(5) pour plus de
détails.
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),
ioctl_eventpoll(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 à
Index
- NOM
-
- SYNOPSIS
-
- DESCRIPTION
-
- Détection par changement de niveau ou d’état
-
- Interaction avec autosleep
-
- /proc interfaces
-
- Exemple d'utilisation
-
- Questions/Réponses
-
- Erreurs possibles et moyens de les éviter
-
- VERSIONS
-
- STANDARDS
-
- HISTORIQUE
-
- NOTES
-
- VOIR AUSSI
-
- TRADUCTION
-
This document was created by
man2html,
using the manual pages.
Time: 05:06:36 GMT, September 19, 2025