windows_eventloop.tex 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. \section{Mécanisme de gestion asynchrone}
  2. Comme sous Linux, il faut implémenter une boucle événementielle qui va récupérer
  3. les messages venant de tous les autres modules. Dans ce chapitre je vais
  4. détailler les solutions disponibles sous Windows et expliquer le choix des IOCP,
  5. ou IO completion ports, en proposant une implémentation de
  6. \inltype{vlc_msg_poller_t} les utilisant.
  7. \subsection{poll, select}
  8. Comme dans la partie précédente, on dispose d'une fonction \inltype{select}
  9. \footnote{\url{https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-select}}
  10. capable d'indiquer l'état de chacun des sockets décrits dans un tableau.
  11. \begin{code}{c}{Prototype de la fonction select}
  12. int WSAAPI select(
  13. int nfds,
  14. fd_set *readfds,
  15. fd_set *writefds,
  16. fd_set *exceptfds,
  17. const timeval *timeout
  18. );
  19. \end{code}
  20. Malheureusement celle-ci ne peut être utilisée que sur des sockets venant de
  21. l'API winsock. Il nous sera donc impossible de l'utiliser pour construire la
  22. boucle événementielle.
  23. De même, \inltype{poll}
  24. \footnote{\url{https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-wsapoll}}
  25. n'existe pas comme sous Linux et il s'agit d'une
  26. fonction winsock.
  27. \begin{code}{c}{Prototype de la fonction WSAPoll}
  28. int WSAAPI WSAPoll(
  29. LPWSAPOLLFD fdArray,
  30. ULONG fds,
  31. INT timeout
  32. );
  33. \end{code}
  34. Par conséquent, ces deux choix sont définitivement éliminés pour la construction
  35. avec les canaux nommés.
  36. \subsection{WaitForMultipleObject}
  37. Pour obtenir le même effet que les fonctions sous Linux, sur des
  38. \inltype{HANDLE} de façon générique, Windows propose une famille de fonctions de
  39. la forme \inltype{WaitFor*}.
  40. D'abord, \inltype{WaitForSingleObject}
  41. \footnote{\url{https://docs.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-waitforsingleobject}}
  42. attend que le \inltype{HANDLE} soit
  43. signalé, éventuellement en prenant en compte un ultimatum.
  44. \begin{code}{c}{Prototype de WaitForSingleObject sur MSDN}
  45. DWORD WaitForSingleObject(
  46. HANDLE hHandle,
  47. DWORD dwMilliseconds
  48. );
  49. \end{code}
  50. Cependant, cela correspond peu à notre cas d'usage et sera plutôt utilisé pour
  51. les manipulations de processus. On peut donc s'intéresser à la variante nous
  52. concernant, qui sans surprise s'appelle \inltype{WaitForMultipleObjects}
  53. \footnote{\url{https://docs.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-waitformultipleobjects}}.
  54. \begin{code}{c}{Prototype de la fonction WaitForMultipleObjects sur MSDN}
  55. DWORD WaitForMultipleObjects(
  56. DWORD nCount,
  57. CONST HANDLE *lpHandles,
  58. BOOL bWaitAll,
  59. DWORD dwMilliseconds
  60. );
  61. \end{code}
  62. Cette fonction va retourner soit un code d'erreur, soit une constante
  63. correspondant à l'objet signalé, allant de \inltype{WAIT_OBJECT_0} à
  64. \inltype{WAIT_OBJECT_0 + nCount-1}.
  65. Elle ne peut surveiller plus de \inltype{MAXIMUM_WAIT_OBJECTS} en même temps, ce
  66. qui en pratique se limite à 64 objets. Loin d'être bloquant pour notre projet,
  67. on va plutôt s'intéresser aux performances. \inltype{WaitForMultipleObjects}
  68. fonctionne exactement comme \inltype{select} et fournit donc une complexité
  69. linéaire en le nombre de \inltype{HANDLE} surveillés. Néanmoins, la valeur de
  70. retour est capable d'indiquer uniquement un seul \inltype{HANDLE} notifié et il
  71. faut donc parcourir à nouveau le tableau pour trouver les autres
  72. \inltype{HANDLE} notififés avec la même méthode.
  73. \subsection{Overlapped IO}
  74. Windows fournit ensuite un mécanisme bas niveau pour mettre les opérations d'IO
  75. en \og{}tâche de fond\fg{}
  76. \footnote{\url{https://docs.microsoft.com/en-us/windows/desktop/sync/synchronization-and-overlapped-input-and-output}}.
  77. Ci-dessous, on voit l'argument \inltype{lpOverlapped} dans les prototypes des
  78. fonctions de lecture
  79. \footnote{\url{https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-readfile}}
  80. et écriture
  81. \footnote{\url{https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-writefile}}
  82. qui va contenir l'état de l'opération lorsqu'elle est réalisée de façon
  83. asynchrone.
  84. \begin{code}{c}{Prototype de WriteFile sur MSDN}
  85. BOOL WriteFile(
  86. HANDLE hFile,
  87. LPCVOID lpBuffer,
  88. DWORD nNumberOfBytesToWrite,
  89. LPDWORD lpNumberOfBytesWritten,
  90. LPOVERLAPPED lpOverlapped
  91. );
  92. \end{code}
  93. \begin{code}{c}{Prototype de ReadFile sur MSDN}
  94. BOOL ReadFile(
  95. HANDLE hFile,
  96. __out_data_source(FILE)LPVOID lpBuffer,
  97. DWORD nNumberOfBytesToRead,
  98. LPDWORD lpNumberOfBytesRead,
  99. LPOVERLAPPED lpOverlapped
  100. );
  101. \end{code}
  102. Dans ces deux fonctions, si \inltype{hFile} a été ouvert avec l'option
  103. \inltype{FILE_FLAG_OVERLAPPED}, le paramètre \inltype{lpOverlapped} doit être
  104. précisé et pointer vers une structure initialisée de type \inltype{OVERLAPPED}.
  105. %TODO: mieux expliquer
  106. La structure \inltype{OVERLAPPED} peut ensuite être utilisée soit avec
  107. \inltype{WaitForSingleObject}, soit avec \inltype{WaitForMultipleObjects} et
  108. fournit ainsi un moyen de passer d'un modèle par disponibilité à un modèle par
  109. complétion.
  110. Il est possible d'étendre encore le modèle asynchrone en utilisant des
  111. \og{}completion routines\fg{}. Il faut alors utiliser la version \inltype{ReadFileEx}
  112. \footnote{\url{https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-readfileex}}
  113. pour la lecture.
  114. \begin{code}{c}{Prototype de la fonction ReadFileEx sur MSDN}
  115. BOOL ReadFileEx(
  116. HANDLE hFile,
  117. __out_data_source(FILE)LPVOID lpBuffer,
  118. DWORD nNumberOfBytesToRead,
  119. LPOVERLAPPED lpOverlapped,
  120. LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
  121. );
  122. \end{code}
  123. Le paramètre \inltype{lpCompletionRoutine}
  124. \footnote{\url{https://msdn.microsoft.com/en-us/574eccda-03eb-4e8a-9d74-cfaecc7312ce}}
  125. est un pointeur de fonction du type
  126. suivant:
  127. \begin{code}{c}{Définition de LPOVERLAPPED\_COMPLETION\_ROUTINE sur MSDN}
  128. typedef VOID (WINAPI *LPOVERLAPPED_COMPLETION_ROUTINE)(
  129. _In_ DWORD dwErrorCode,
  130. _In_ DWORD dwNumberOfBytesTransfered,
  131. _Inout_ LPOVERLAPPED lpOverlapped
  132. );
  133. \end{code}
  134. La routine de complétion sera alors appelée dès que l'opération sera terminée et
  135. que le thread sera dans un état alertable, c'est-à-dire se signalera disponible.
  136. \subsection{IO completion ports}
  137. Les IO completion ports, ou IOCP pour la suite, permettent d'implémenter un
  138. système similaire à epoll, dans un modèle par complétion, en étendant les
  139. \inltype{OVERLAPPED} IO vues précédemment. L'API n'est cependant pas très claire
  140. sur beaucoup de points donc elle sera plus longuement décrite ici pour apporter
  141. des explications dans ce rapport.
  142. Un IOCP peut être créé avec la fonction \inltype{CreateIoCompletionPort}
  143. \footnote{\url{https://docs.microsoft.com/en-us/windows/desktop/fileio/createiocompletionport}}
  144. en passant \inltype{INVALID_HANDLE_VALUE} comme \inltype{FileHandle} et
  145. \inltype{NULL} comme \inltype{ExistingCompletionPort}.
  146. \begin{code}{c}{Prototype de CreateIoCompletionPort sur MSDN}
  147. HANDLE WINAPI CreateIoCompletionPort(
  148. _In_ HANDLE FileHandle,
  149. _In_opt_ HANDLE ExistingCompletionPort,
  150. _In_ ULONG_PTR CompletionKey,
  151. _In_ DWORD NumberOfConcurrentThreads
  152. );
  153. \end{code}
  154. Le paramètre \inltype{CompletionKey} est ignoré dans ce cas, et le paramètre
  155. \inltype{NumberOfConcurrentThreads} permet d'indiquer à l'IOCP combien de thread
  156. vont pouvoir simultanément recevoir les notifications de nouveaux messages. Ce
  157. paramètre ne démarrre pas de nouveaux threads mais sert seulement à
  158. l'ordonnancement dans le cas où plusieurs threads demanderaient des
  159. notifications au port. Dans ce dernier cas, chacun des threads recevra des
  160. notifications pour des opérations différentes, mais cela ne nous concerne pas
  161. ici.
  162. Nous pouvons ensuite ajouter des opérations en utilisant la même fonction, mais
  163. en renseignant \inltype{FileHandle} comme étant l'objet sur lequel l'opération a
  164. été effectuée, \inltype{ExistingCompletionPort} comme étant le \inltype{HANDLE}
  165. créé dans l'appel précédent et \inltype{CompletionKey} comme étant une valeur
  166. arbitraire qui nous servira à retrouver l'opération plus tard.
  167. Il est possible de créer un port tout en associant un \inltype{HANDLE} à la
  168. construction, mais le cas ne nous intéresse pas pour l'implémentation de
  169. \inltype{vlc_msg_poller_t}.
  170. Une fois les opérations associées au port, on peut utiliser
  171. \inltype{GetQueuedCompletionStatus}
  172. \footnote{\url{https://msdn.microsoft.com/en-us/library/Aa364986(v=VS.85).aspx}}
  173. pour récupérer un paquet de complétion.
  174. \begin{code}{c}{Prototype de GetQueuedCompletionStatus sur MSDN}
  175. BOOL WINAPI GetQueuedCompletionStatus(
  176. _In_ HANDLE CompletionPort,
  177. _Out_ LPDWORD lpNumberOfBytes,
  178. _Out_ PULONG_PTR lpCompletionKey,
  179. _Out_ LPOVERLAPPED *lpOverlapped,
  180. _In_ DWORD dwMilliseconds
  181. );
  182. \end{code}
  183. Cette fonction attendra la fin d'une opération en cours, et plus exactement
  184. l'arrivée d'un paquet de complétion, pour retourner, mais permettra de récupérer
  185. la structure \inltype{OVERLAPPED} contenant les données et informations liées à
  186. l'opération effectuée.
  187. Enfin, il sera possible de poster une notification sans effectuer d'opération en
  188. appelant \inltype{PostQueuedCompletionStatus}
  189. \footnote{\url{https://docs.microsoft.com/en-us/windows/desktop/FileIO/postqueuedcompletionstatus}},
  190. afin de débloquer la boucle événementielle par exemple.
  191. \begin{code}{c}{Prototype de PostQueuedCompletionStatus}
  192. BOOL WINAPI PostQueuedCompletionStatus(
  193. _In_ HANDLE CompletionPort,
  194. _In_ DWORD dwNumberOfBytesTransferred,
  195. _In_ ULONG_PTR dwCompletionKey,
  196. _In_opt_ LPOVERLAPPED lpOverlapped
  197. );
  198. \end{code}
  199. \section{Implémentation et conclusions pour la boucle événementielle}
  200. Ayant déjà décrit \inltype{vlc_msg_poller_t} pour la partie Linux et expliqué en
  201. détails le fonctionnement de l'API d'IOCP, il reste principalement à décrire
  202. l'implémentation du modèle par complétion dans la situation d'attente de
  203. message.
  204. Contrairement à Linux, les IOCP ne vont pas signaler quand un des
  205. \inltype{HANDLE} sera prêt à être lu mais plutôt quand les données auront
  206. effectivement été lues. Il faut donc allouer un tampon par requête de lecture.
  207. La méthodologie sera alors de dédier un tampon par \inltype{HANDLE} puis
  208. d'effectuer toutes les opérations de lecture. Lorsqu'une des opérations est
  209. considérée comme terminée, on pourra traiter le message puis relancer
  210. l'opération de lecture.
  211. % TODO: snippet de code
  212. En résumé, la gestion des entrées sorties et des erreurs proposées par Windows
  213. ne respecte absolument pas le principe de moindre surprise, mais les différentes
  214. alternatives proposées par le système permettent de construire le modèle par
  215. complétion dont nous avons besoin de façon performante.