linux_eventloop.tex 6.4 KB

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