J'ai vu beaucoup de comparaisons qui disent que select doit parcourir la liste fd, et c'est lent. Mais pourquoi epoll n'a-t-il pas à le faire?
Il y a beaucoup de désinformation à ce sujet, mais la vraie raison est la suivante:
Un serveur typique peut traiter, disons, 200 connexions. Il desservira toutes les connexions qui doivent avoir des données écrites ou lues, puis il faudra attendre qu'il y ait plus de travail à faire. Pendant qu'il attend, il doit être interrompu si des données sont reçues sur l'une de ces 200 connexions.
Avec select
, le noyau doit ajouter le processus à 200 listes d'attente, une pour chaque connexion. Pour ce faire, il a besoin d'un "thunk" pour attacher le processus à la liste d'attente. Lorsque le processus se réveille enfin, il doit être supprimé des 200 listes d'attente et tous ces thunks doivent être libérés.
En revanche, avec epoll
, le socket epoll
lui-même a une liste d'attente. Le processus doit être mis sur cette seule liste d'attente en utilisant un seul thunk. Lorsque le processus se réveille, il doit être supprimé d'une seule liste d'attente et un seul thunk doit être libéré.
Pour être clair, avec epoll
, le socket epoll
lui-même doit être attaché à chacune de ces 200 connexions. Mais cela se fait une fois, pour chaque connexion, lorsqu'elle est acceptée en premier lieu. Et cela est supprimé une fois, pour chaque connexion, lorsqu'il est supprimé. En revanche, chaque appel à select
que les blocs doivent ajouter le processus à chaque file d'attente pour chaque socket surveillé.
Ironiquement, avec select
, le plus gros coût vient de la vérification si les sockets qui n'ont eu aucune activité ont eu une activité. Avec epoll
, il n'est pas nécessaire de vérifier les sockets qui n'ont eu aucune activité car s'ils avaient une activité, ils auraient informé la socket epoll
quand cette activité s'est produite. Dans un sens, select
interroge chaque socket à chaque fois que vous appelez select
pour voir s'il y a une activité tandis que epoll
la structure de sorte que l'activité de socket elle-même notifie le processus.
La principale différence entre epoll
et select
est que dans select()
la liste des descripteurs de fichiers à attendre n'existe que pour la durée d'une seule select()
appel et la tâche d'appel ne reste dans les files d'attente d'attente des sockets que pendant la durée d'un seul appel. Dans epoll
, d'un autre côté, vous créez un descripteur de fichier unique qui agrège les événements de plusieurs autres descripteurs de fichier que vous souhaitez attendre, et donc la liste des fd surveillés est longue et les tâches restent sur le socket attendre des files d'attente sur plusieurs appels système. De plus, puisqu'un epoll
fd peut être partagé entre plusieurs tâches, il ne s'agit plus d'une seule tâche dans la file d'attente, mais d'une structure qui elle-même contient une autre file d'attente, contenant tous les processus actuellement en attente sur le epoll
fd. (En termes d'implémentation, ceci est résumé par les files d'attente des sockets contenant un pointeur de fonction et un pointeur de données void*
Pour passer à cette fonction).
Donc, pour expliquer un peu plus la mécanique:
epoll
a un struct eventpoll
Privé qui garde la trace des fd attachés à ce fd. struct eventpoll
Possède également une file d'attente qui conserve la trace de tous les processus qui sont actuellement epoll_wait
Sur ce fd. struct epoll
Possède également une liste de tous les descripteurs de fichiers actuellement disponibles en lecture ou en écriture.epoll
fd à l'aide de epoll_ctl()
, epoll
ajoute le struct eventpoll
À la file d'attente de ce fd. Il vérifie également si le fd est actuellement prêt pour le traitement et l'ajoute à la liste des fichiers prêts, le cas échéant.epoll
fd à l'aide de epoll_wait
, Le noyau vérifie d'abord la liste des éléments prêts et renvoie immédiatement si des descripteurs de fichiers sont déjà prêts. Sinon, il s'ajoute à la file d'attente unique à l'intérieur de struct eventpoll
Et se met en veille.epoll()
ed, il appelle le rappel epoll
, qui ajoute le descripteur de fichier à la liste des prêts, et réveille également tous les serveurs en attente que struct eventpoll
.De toute évidence, un verrouillage minutieux est nécessaire sur struct eventpoll
Et les différentes listes et files d'attente, mais c'est un détail d'implémentation.
La chose importante à noter est qu'à aucun moment ci-dessus, je n'ai décrit une étape qui parcourt tous les descripteurs de fichiers d'intérêt. En étant entièrement basé sur les événements et en utilisant un ensemble durable de fd et une liste prête, epoll peut éviter de prendre O (n) le temps d'une opération, où n est le nombre de descripteurs de fichier surveillé.