linux_eventloop.tex 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. \section{Gestion asynchrone}
  2. Maintenant que j'ai choisi les solutions de communication entre les différents
  3. processus, on peut s'intéresser à la manière de gérer les échanges de messages
  4. venant potentiellement de plusieurs sources.
  5. Linux dispose de plusieurs mécanismes pour effectuer des opérations asynchrones,
  6. tous n'ayant pas les mêmes caractéristiques en terme de consommation mémoire ou
  7. complexité.
  8. \subsection{poll, select}
  9. Parmi les solutions pour manipuler plusieurs descripteurs de fichier en même
  10. temps, les solutions classiques sont \inltype{poll} et \inltype{select}. Les
  11. deux fonctions marquent leur ancienneté par une mauvaise complexité
  12. algorithmique dans leur exploitation, c'est-à-dire un temps linéaire en le
  13. nombre de descripteurs lors de l'attente d'un événement.
  14. Dans un premier temps, nous allons voir \inltype{poll}, à qui nous pouvons
  15. indiquer l'ensemble des événements à surveiller et qui nous signalera lorsque
  16. l'un des événements a été notifié. Le vériable descripteur ayant reçu la
  17. notification n'est cependant pas précisé et il faut parcourir le tableau des
  18. événements pour pouvoir le trouver.
  19. \begin{code}{c}{Prototype de poll dans man 2 poll}
  20. int poll(struct pollfd *fds, nfds_t nfds, int délai);
  21. \end{code}
  22. Cette méthode est aujourd'hui relativement remplacée par \inltype{epoll} et ne
  23. nous intéressera pas.
  24. Ensuite, \inltype{select} fournit le même mécanisme en indiquant par des
  25. opérations bit à bit quels sont les descripteurs de fichier ayant reçu une
  26. notification. En plus de souffrir du problème précédent obligeant à parcourir
  27. l'ensemble des descripteurs, elle est donc également limitée en nombre de
  28. descripteur de fichier possible.
  29. \begin{code}{c}{Prototype de select et macros dans man 2 select}
  30. int select(int nfds, fd_set *readfds, fd_set *writefds,
  31. fd_set *exceptfds, struct timeval *timeout);
  32. void FD_CLR(int fd, fd_set *set);
  33. int FD_ISSET(int fd, fd_set *set);
  34. void FD_SET(int fd, fd_set *set);
  35. void FD_ZERO(fd_set *set);
  36. \end{code}
  37. \subsection{Asynchronous IO}
  38. Les asynchronous IO, ou AIO, sont un mécanisme de notification de complétion des
  39. requêtes de lecture et écriture. L'objectif final de l'implémentation des AIO
  40. est d'arriver à faire du zéro-copie sur l'API réseau de Linux. En effet, dans un
  41. modèle par complétion, on peut indiquer directement au noyau où stocker les
  42. données reçues. Il faut néanmoins accepter la déception et voir que c'est
  43. parfois aussi coûteux que de faire la copie, particulièrement sur les
  44. transmissions de petite taille.
  45. L'API est implémentée en userland dans glibc, est relativement coûteuse et ne
  46. permet d'attendre la première opération qui aurait terminé comme le permettait
  47. les primitives précédentes. Ell est également difficile à utiliser car les
  48. notifications sont reçues sous forme de signal, de nouveau thread ou ne sont
  49. pas envoyées. Elle est donc inutilisable pour notre application mais vaut le
  50. coup d'être mentionnée pour la réalisation d'un \inltype{vlc_msg_poller_t}
  51. zéro-copie dans le futur.
  52. \begin{code}{c}{Extrait de l'API de AIO depuis man 2}
  53. struct aiocb {
  54. /* The order of these fields is implementation-dependent */
  55. int aio_fildes; /* File descriptor */
  56. off_t aio_offset; /* File offset */
  57. volatile void *aio_buf; /* Location of buffer */
  58. size_t aio_nbytes; /* Length of transfer */
  59. int aio_reqprio; /* Request priority */
  60. struct sigevent aio_sigevent; /* Notification method */
  61. int aio_lio_opcode; /* Operation to be performed;
  62. lio_listio() only */
  63. /* Various implementation-internal fields not shown */
  64. };
  65. int aio_read(struct aiocb *aiocbp);
  66. int aio_write(struct aiocb *aiocbp);
  67. \end{code}
  68. \subsection{epoll}
  69. % TODO: https://lkml.org/lkml/2015/2/24/667
  70. % TODO: https://lore.kernel.org/patchwork/patch/543311/
  71. Linux fournit depuis assez récemment un mécanisme bien plus performant et avec
  72. une interface bien plus proche des autres API standards. Ce mécanisme, epoll, a
  73. longtemps été critiqué car bien qu'étant le dernier des mécanismes permettant ce
  74. genre de programmation sur événement, il reproduisait des problèmes d'interface
  75. qui avaient été résolus par les solutions précédentes sur les autres systèmes
  76. depuis longtemps. C'est notamment des développeurs chez Nginx qui ont remonté
  77. des problèmes dans leur utilisation qui a abouti à l'ajout de
  78. \inltype{EPOLLEXCLUSIVE} dans le noyau Linux 4.5.
  79. Dans notre cas, il nous fournit une interface fonctionnant sur la disponibilité
  80. des descripteurs de fichiers, utilisable facilement pour construire notre
  81. implémentation du modèle par complétion.
  82. La complexité est en $O(1)$ et elle n'a pas de limitation sur le nombre de
  83. descripteurs autre que la consommation mémoire. C'est aussi la méthode de
  84. remplacement privilégiée pour \inltype{poll} et en concurrence directe avec les
  85. AIO de la sous-section précédente.
  86. \begin{code}{c}{API de epoll décrite dans man 2 epoll}
  87. /* retourne un descripteur de fichier vers un objet epoll */
  88. int epoll_create1(int flags);
  89. /* attend l'arrivée d'un événement sur l'instance d'epoll donnée */
  90. int epoll_wait(int epfd, struct epoll_event *events,
  91. int maxevents, int timeout);
  92. /* permet d'ajouter, modifier ou enlever des associations d'événement à des
  93. * descripteurs de fichiers dans l'instance d'epoll */
  94. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  95. \end{code}
  96. \section{Implémentation de \texttt{vlc\_msg\_poller\_t}}
  97. Je peux désormais présenter l'implémentation Linux de \inltype{vlc_msg_poller_t}
  98. que j'ai réalisée dans le prototype, en utilisant epoll.
  99. \inltype{vlc_msg_poller_t} est construite uniquement avec un descripteur de
  100. fichier.
  101. \begin{code}{c}{Structure vlc\_msg\_poller\_t dans le prototype Linux}
  102. struct vlc_msg_poller_t {
  103. int fd;
  104. }
  105. \end{code}
  106. La création se fait en appelant \inltype{epoll_create1} qui remplace la fonction
  107. dépréciée \inltype{epoll_create}.
  108. L'ajout d'une nouvelle IPC dans le poller utilise ensuite le pointeur
  109. utilisateur de \inltype{epoll_event} pour garder une trace de l'IPC.
  110. \begin{code}{c}{Implémentation de msg\_poller\_AddChannel dans le
  111. prototype Linux}
  112. VLC_API void
  113. msg_poller_AddChannel( vlc_msg_poller_t *p_poller, vlc_process_ipc_t *p_ipc )
  114. {
  115. struct epoll_event event =
  116. {
  117. /* se déclenche lorsque des données peuvent être lues */
  118. .events = EPOLLIN,
  119. .data = {
  120. /* on peut aussi choisir de stocker seulement le file descriptor */
  121. //.fd = p_ipc->fd,
  122. .ptr = p_ipc
  123. }
  124. };
  125. epoll_ctl( p_poller->fd, EPOLL_CTL_ADD, p_ipc->fd, &event);
  126. }
  127. \end{code}
  128. De même, la suppression appelle seulement la fonction \inltype{epoll_ctl} avec
  129. l'argument \inltype{EPOLL_CTL_DEL}.
  130. Enfin, la fonction d'attente utilise \inltype{epoll_wait} pour attendre et
  131. récupérer le pointeur vers l'IPC étant disponible pour l'opération de lecture.
  132. Une fois l'IPC récupérée, le \inltype{vlc_msg_poller_t} peut lire les données et
  133. extraire le message en suivant notre modèle par complétion.
  134. \begin{code}{c}{Implémentation de msg\_poller\_WaitMsg dans le prototype Linux}
  135. VLC_API int
  136. msg_poller_WaitMsg( vlc_msg_poller_t *p_poller, vlc_process_msg_t **p_msg,
  137. vlc_process_ipc_t **p_ipc )
  138. {
  139. struct epoll_event event;
  140. int ret = epoll_wait( p_poller->fd, &event, 1, -1 );
  141. if( ret == -1 )
  142. return VLC_EGENERIC;
  143. *p_ipc = event.data.ptr;
  144. /* extraction du message depuis l'IPC */
  145. return vlc_ipc_RecvMsg( *p_ipc, p_msg );
  146. }
  147. \end{code}