Implémentation d'Epoll, partie 4

Il s'agit du dernier d'une série de quatre articles ( partie 1 , partie 2 , partie 3 ) sur la mise en œuvre epoll. Ici, nous allons parler de la façon dont il epolltransfère les événements de l'espace noyau à l'espace utilisateur, et comment les modes de déclenchement de bord et de niveau sont implémentés. Cet article a été écrit plus tard que les autres. Quand j'ai commencé à travailler sur le premier matériel, le noyau Linux stable le plus récent était 3.16.1. Et au moment d'écrire ces lignes, il s'agit déjà de la version 4.1. Cet article est basé sur le code de cette version du noyau. Le code, cependant, n'a pas beaucoup changé, donc les lecteurs des articles précédents ne devraient pas s'inquiéter du fait que quelque chose dans l'implémentation a beaucoup changé.







epoll



Interagir avec l'espace utilisateur



Dans les articles précédents, j'ai passé pas mal de temps à expliquer le fonctionnement du système de gestion des événements dans le noyau. Mais, comme vous le savez, le noyau a besoin de transmettre des informations sur les événements à un programme s'exécutant dans l'espace utilisateur pour que le programme puisse utiliser ces informations. Ceci est principalement fait avec l'appel système epoll_wait (2) .



Le code de cette fonction se trouve à la ligne 1961 du fichier fs/eventpoll.c. La fonction elle-même est très simple. Après des vérifications tout à fait normales, il obtient simplement le pointeur vers eventpolldu descripteur de fichier et appelle la fonction suivante:



error = ep_poll(ep, events, maxevents, timeout);


Fonction Ep_poll ()



La fonction est ep_poll()déclarée à la ligne 1585 du même fichier. Il commence par vérifier si l'utilisateur a défini une valeur timeout. Si tel est le cas, la fonction initialise la file d'attente et définit le délai d'expiration sur la valeur spécifiée par l'utilisateur. Si l'utilisateur ne veut pas attendre, c'est-à-dire que , timeout = 0la fonction passe immédiatement au bloc de code avec une étiquette check_events:, qui est responsable de la copie de l'événement.



Si l'utilisateur a spécifié une valeur timeout, et qu'aucun événement ne peut lui être signalé (leur présence est déterminée à l'aide d'un appel ep_events_available(ep)), la fonction ep_poll()s'ajoute à la file d'attente ep->wq(rappelez-vous de ce dont nous avons parlé dans le troisième article de cette série). Là, nous avons mentionné que ep_poll_callback()dans le processus, il active tous les processus en attente dans la file d'attente.ep->wq...



La fonction se met alors en veille en appelant schedule_hrtimeout_range(). Voici les circonstances dans lesquelles un processus "en veille" peut "se réveiller":



  1. Le délai a expiré.
  2. Le processus a reçu un signal.
  3. Un nouvel événement est survenu.
  4. Rien ne s'est passé et le planificateur a simplement décidé d'activer le processus.


Dans les scénarios 1, 2 et 3, la fonction définit les indicateurs appropriés et quitte la boucle d'attente. Dans ce dernier cas, la fonction passe simplement à nouveau en mode veille.



Une fois cette partie du travail terminée, il ep_poll()continue d'exécuter le code de bloc check_events:.



Dans ce bloc, la présence d'événements est d'abord vérifiée, puis l'appel suivant est effectué, là où se produit le plus intéressant.



ep_send_events(ep, events, maxevents)


Fonction ep_send_events()déclarée en ligne 1546. Il est, après l'appel, appelle la fonction ep_scan_ready_list(), en passant dans un rappel, ep_send_events_proc(). La fonction ep_scan_ready_list()parcourt la liste des descripteurs de fichiers prêts et appelle ep_send_events_proc()chaque événement prêt qu'elle trouve. Il deviendra clair ci-dessous qu'un mécanisme impliquant l'utilisation d'un rappel est nécessaire pour assurer la sécurité et la réutilisation du code.



La fonction ep_send_events()place d'abord les données de la liste des descripteurs de fichiers prêts à l'emploi de la structure eventpooldans sa variable locale. Il définit ensuite la ovfliststructure de champ eventpoolà NULL(et sa valeur par défaut est EP_UNACTIVE_PTR).



Pourquoi les auteurs epollutilisent-ilsovflist? Ceci est fait pour assurer une efficacité élevée epoll! Vous remarquerez peut-être qu'une fois que la liste des descripteurs de fichiers prêts a été extraite de la structure eventpool, elle est ep_scan_ready_list()définie ovflistsur NULL. Cela a pour conséquence de ep_poll_callback()ne pas essayer de rattacher l'événement qui est passé à l'espace utilisateur ep->rdllist, ce qui peut entraîner de gros problèmes. En utilisant la ovflistfonction, il n'est ep_scan_ready_list()pas nécessaire de maintenir un verrou ep->locklors de la copie d'événements dans l'espace utilisateur. En conséquence, les performances globales de la solution sont améliorées.



Après cela, il ep_send_events_proc()contournera la liste des descripteurs de fichiers prêts dont il dispose et appellera à nouveau leurs méthodes.poll()afin de s'assurer que l'événement s'est réellement produit. Pourquoi epollrevoir les événements ici? Ceci est fait pour s'assurer que l'événement (ou les événements) enregistré par l'utilisateur est toujours disponible. Prenons l'exemple d'une situation dans laquelle un descripteur de fichier a été ajouté à la liste des descripteurs prêts à l'emploi par événement EPOLLOUTpendant que le programme utilisateur écrit dans ce descripteur. Une fois l'écriture du programme terminée, le descripteur de fichier peut ne plus être accessible en écriture. Epollvous devez gérer correctement des situations comme celle-ci. Sinon, l'utilisateur recevra EPOLLOUTau moment où l'opération d'écriture est bloquée.



Ici, cependant, il convient de mentionner un détail. Fonctionep_send_events_proc()met tout en œuvre pour que les programmes de l'espace utilisateur reçoivent des notifications d'événements précises. Il est possible, bien que peu probable, que la disponibilité d'un ensemble d'événements change après le ep_send_events_proc()déclenchement poll(). Dans ce cas, un programme d'espace utilisateur peut recevoir une notification d'un événement qui n'existe plus. C'est pourquoi il est considéré comme correct de toujours utiliser des sockets non bloquants lorsqu'ils sont appliqués epoll. Cela empêche votre application d'être bloquée de manière inattendue.



Après vérification du masque d'événement, il ep_send_events_proc()copie simplement la structure d'événement dans la mémoire tampon fournie par le programme de l'espace utilisateur.



Déclenchement par front et déclenché par niveau



Maintenant, nous pouvons enfin discuter de la différence entre Edge Triggering (ET) et Level Triggering (LT) en termes de leur implémentation.



else if (!(epi->event.events & EPOLLET)) {
    list_add_tail(&epi->rdllink, &ep->rdllist);
}


C'est très facile! La fonction ep_send_events_proc()rajoute l'événement à la liste des descripteurs de fichiers prêts. Par conséquent, lors du prochain appel, ep_poll()le même descripteur de fichier sera à nouveau vérifié. Puisqu'il ep_send_events_proc()appelle toujours un fichier poll()avant de le renvoyer à l'application de l'espace utilisateur, cela augmente légèrement la surcharge système (par rapport à ET) si le descripteur de fichier n'est plus disponible. Mais le but de tout cela est, comme mentionné ci-dessus, de ne pas signaler les événements qui ne sont plus disponibles.



Une fois la ep_send_events_proc()copie des événements terminée, la fonction renvoie le nombre d'événements qui y sont copiés, en gardant à jour l'application de l'espace utilisateur.



Lorsque la fonction est ep_send_events_proc()terminée, les fonctionsep_scan_ready_list()besoin de nettoyer un peu. Tout d'abord, il renvoie à la liste des descripteurs de fichiers prêts les événements qui n'ont pas été traités par la fonction ep_send_events_proc(). Cela peut se produire si le nombre d'événements disponibles dépasse la taille de la mémoire tampon fournie par le programme utilisateur. Il ep_send_events_proc()attache également rapidement tous les événements ovflist, le cas échéant, à la liste des descripteurs de fichiers prêts. De plus, in est à ovflistnouveau enregistré EP_UNACTIVE_PTR. En conséquence, les nouveaux événements seront attachés à la liste d'attente principale ( rdllist). La fonction se termine en activant tous les autres processus "en veille" dans le cas où d'autres événements sont disponibles.



Résultat



Ceci conclut le quatrième et dernier article de la série de mise en œuvre epoll. Au moment où j'écris ces articles, j'ai été impressionné par l'énorme travail mental que les auteurs du code du noyau Linux ont accompli pour atteindre une efficacité et une évolutivité maximales. Et je suis reconnaissant à tous les auteurs de code Linux d'avoir partagé leurs connaissances avec tous ceux qui en ont besoin en partageant les résultats de leur travail.



Que pensez-vous des logiciels open source?










All Articles