EPOLL
Section: Manuel du programmeur Linux (4) Updated: 25 juillet 2003 Index
NOM
SYNOPSIS
DESCRIPTION
NOTES
EXEMPLE D'UTILISATION CONSEILLÉE
QUESTIONS ET REPONSES (de la liste linux-kernel)
PIÈGES POSSIBLES, ET SOLUTIONS
CONFORMITÉ
VOIR AUSSI
TRADUCTION
NOM
epoll - Notifications d'évènements d'entrées/sorties.
SYNOPSIS
DESCRIPTION
epoll
est une variante de
poll(2)
que l'on peut déclencher par niveau ou par changement d'état, et monte
bien en charge pour un grand nombre de descripteurs simultanés. Trois appels-système
sont fournis pour configurer et commander un ensemble
epoll :
epoll_create(2),
epoll_ctl(2),
epoll_wait(2).
Un ensemble
epoll
est connecté à un descripteur de fichiers créé par
epoll_create(2).
L'interêt pour certains descripteurs est ensuite enregistré avec
epoll_ctl(2).
Enfin, l'attente effective démarre avec l'appel
epoll_wait(2).
NOTES
L'interface de distribution d'évènement de
epoll
est capable de se comporter en détection de niveau (Level Triggered - LT)
ou en détection de changement d'état (Edge Triggered - ET). La différence
entre ces 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
(fd_lect)
est ajouté dans un ensemble
epoll.
- 2
-
Celui qui écrie dans le tube envoie 2 Ko de données.
- 3
-
Un appel à
epoll_wait(2)
est effectué et renvoie
fd_lect
comme descripteur de fichier prêt.
- 4
-
Le lecture du tube lit 1 Ko de données depuis
fd_lect.
- 5
-
Un appel de
epoll_wait(2)
est effectué.
Si le descripteur
fd_lect
a été ajouté à l'ensemble
epoll
en utilisant l'attribut
EPOLLET,
l'appel
epoll_wait(2)
réalisé à l'étape
5
va bloquer malgré les données déjà présentes dans les buffers d'entrée
du fichier. La raison en est que le mécanisme ET détecte les changements
sur un périphérique supervisé entre l'état "aucune entrée/sortie possible"
( 0)
et l'état "entrée/sortie possible"
( 1).
Dans l'exemple ci-dessus, un événement sur
fd_lect
sera déclenché (en supposant que le buffer était vide à l'origine)
à 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 buffer (la condition "Entrées/sorties
possibles" persiste), aucune transition
0
->
1
ne peut se produire en
5.
Lorsqu'on emploie l'attribut
EPOLLET
(Edge Triggered)
de la fonction
epoll,
on devrait toujours utiliser des descripteurs non-bloquants pour éviter
qu'une lecture ou une écriture bloque une tâche qui gère plusieurs
descripteurs de fichiers.
L'utilisation suggérée d
epoll
avec l'interface en détection de changements
( EPOLLET)
est décrite ci-dessous, avec les pièges à éviter.
- i
-
ave des descripteurs non-bloquants ;
- ii
-
en attendant seulement après qu'un
read(2)
ou un
write(2)
ait renvoyé EAGAIN.
Au contraire, lorsqu'il est utilisé avec l'interface en détection de niveau
epoll
est une alternative plus rapide à
poll(2),
et peut être employé chaque fois que poll() est utilisé, car il utilise
la même sémantique.
EXEMPLE D'UTILISATION CONSEILLÉE
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é.
struct epoll_event ev, *events;
for(;;) {
nfds = epoll_wait(kdpfd, events, maxevents, -1);
for(n = 0; n < nfds; ++n) {
if(events[n].data.fd == listener) {
client = accept(listener, (struct sockaddr *) &local,
&addrlen);
if(client < 0){
perror("accept");
continue;
}
setnonblocking(client);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = client;
if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) {
fprintf(stderr, "epoll set insertion error: fd=%d,
client);
return -1;
}
}
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 descriptuer de fichier dans
l'interface epoll
( EPOLL_CTL_ADD)
une fois, en spécifiant
( EPOLLIN| EPOLLOUT).
Ceci évite de basculer sans
cesse entre
EPOLLIN
et
EPOLLOUT
lors des appels
epoll_ctl(2)
avec
EPOLL_CTL_MOD.
QUESTIONS ET REPONSES (de la liste linux-kernel)
- Q1
-
Que se passe-t-il si on ajoute deux fois le même fd dans un ensemble epoll ?
- A1
-
On aura probablement l'erreur EEXIST. Toutefois, il est possible que deux
threads puisse ajouter le même fd deux fois. Sans conséquences fâcheuses.
- Q2
-
Deux ensemples
epoll
peuvent-ils attendre le même fd? Si oui, les événements seront-t-ils
reportés sur des deux ensembles
epoll
en même temps ?
- A2
-
Oui. Toutefois, c'est peu recommandé. Oui, l'événement sera rapporté pour
les deux.
- Q3
-
Peut-on utiliser le descripteur
epoll
lui-même avec poll/epoll/select ?
- A3
-
Oui.
- Q4
-
Que se passe-t-il si le descripteur de
epoll
est inséré dans son propre ensemble ?
- A4
-
Cela échouera. Toutefois vous pouvez ajoutez le descripteur de
epoll
dans un autre ensemble epoll.
- Q5
-
Puis-je envoyer le descripteur
epoll
à travers une socket Unix vers un autre processus ?
- A5
-
Non.
- Q6
-
Est-ce que la feermeteur d'un descripteur le supprime
automatiquement d'un ensemble
epoll ?
- A6
-
Oui.
- Q7
-
Si plus d'un événement survient 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 re-vérifie les
entrées/sorties disponibles.
- Q9
-
Dois-je lire/écrire sans cesse un descripteur jusqu'à obtenir EAGAIN avec
l'attribut
EPOLLET
(Edge Triggered behaviour) ?
- A9
-
Non. 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'au prochain EAGAIN. Quand et comment
utiliser le descripteur dépend de vous. De plus, 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 buffer
d'entrée pour le descripteur. La même chose est vraie pour
l'appel-système
write(2).
PIÈGES POSSIBLES, ET SOLUTIONS
- o Faux Positifs (Edge Triggered)
-
Il est possible que durant une lecture (en supposant que vous lisez en
boucle en attendant EAGAIN), des données supplémentaires arrivent en
second événement. Bien que ces données soient lues tout de suite, l'appel
suivant de
epoll_wait(2)
sur le descripteur dira qu'il y a un événement "lecture possible" alors
qu'il a déjà été consommé.
- 1
-
Une certaine quantité de données arrive sur un descripteur surveillé.
- 2
-
Un appel à
epoll_wait(2)
renvoit le descripteur repéré.
- 3
-
Un autre bloc de données arrive sur la même descripteur.
- 4
-
Le descripteur est signalé en interne comme prêt.
- 5
-
Un appel à
read(2)
consomme toutes les données disponibles.
- 6
-
Un autre appel à
epoll_wait(2)
renverra le descripteur ci-dessus même si aucune donnée
n'est disponible, ainsi l'appel suivant de
read(2)
renverra EAGAIN.
Dans le cas de descripteurs non-bloquants, cela fera échouer immédiatement
la lecture suivante avec l'erreur EAGAIN. Dans le cas de descripteurs
bloquants, on restera en attente pour lire des données non encore présentes.
L'auteur recommande de ne pas utiliser de descripteur bloquant avec le
mécanisme de détection de changement d'état (ET).
Pour traiter ce cas, une possibilité est de marquer le descripteur comme
prêt dans sa structure de données associée après la réception du premier
événement, puis d'ignorer les événements tant qu'il est dans l'état prêt.
Lorsque vous lisez jusqu'à recevoir EAGAIN, effacez le bit d'état prêt
avant de rappeler
epoll_wait(2)
sur ce descripteur.
- 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 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. Ceci
permet aussi d'ignorer les événments ultérieurs sur un descripteur prêt.
- 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(), 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, 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édement, sans que cela ne prête à confusion.
CONFORMITÉ
epoll(4)
est une API introduie dans Linux 2.5.44. Son interface devrait être
finalisée depuis le 2.5.66.
VOIR AUSSI
TRADUCTION
Christophe Blaess, 2003
|