123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- \section{Mécanisme de gestion asynchrone}
- Comme sous Linux, il faut implémenter une boucle événementielle qui va récupérer
- les messages venant de tous les autres modules. Dans ce chapitre je vais
- détailler les solutions disponibles sous Windows et expliquer le choix des IOCP,
- ou IO completion ports, en proposant une implémentation de
- \inltype{vlc_msg_poller_t} les utilisant.
- \subsection{poll, select}
- Comme dans la partie précédente, on dispose d'une fonction \inltype{select}
- \footnote{\url{https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-select}}
- capable d'indiquer l'état de chacun des sockets décrits dans un tableau.
- \begin{code}{c}{Prototype de la fonction select}
- int WSAAPI select(
- int nfds,
- fd_set *readfds,
- fd_set *writefds,
- fd_set *exceptfds,
- const timeval *timeout
- );
- \end{code}
- Malheureusement celle-ci ne peut être utilisée que sur des sockets venant de
- l'API winsock. Il nous sera donc impossible de l'utiliser pour construire la
- boucle événementielle.
- De même, \inltype{poll}
- \footnote{\url{https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-wsapoll}}
- n'existe pas comme sous Linux et il s'agit d'une
- fonction winsock.
- \begin{code}{c}{Prototype de la fonction WSAPoll}
- int WSAAPI WSAPoll(
- LPWSAPOLLFD fdArray,
- ULONG fds,
- INT timeout
- );
- \end{code}
- Par conséquent, ces deux choix sont définitivement éliminés pour la construction
- avec les canaux nommés.
- \subsection{WaitForMultipleObject}
- Pour obtenir le même effet que les fonctions sous Linux, sur des
- \inltype{HANDLE} de façon générique, Windows propose une famille de fonctions de
- la forme \inltype{WaitFor*}.
- D'abord, \inltype{WaitForSingleObject}
- \footnote{\url{https://docs.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-waitforsingleobject}}
- attend que le \inltype{HANDLE} soit
- signalé, éventuellement en prenant en compte un ultimatum.
- \begin{code}{c}{Prototype de WaitForSingleObject sur MSDN}
- DWORD WaitForSingleObject(
- HANDLE hHandle,
- DWORD dwMilliseconds
- );
- \end{code}
- Cependant, cela correspond peu à notre cas d'usage et sera plutôt utilisé pour
- les manipulations de processus. On peut donc s'intéresser à la variante nous
- concernant, qui sans surprise s'appelle \inltype{WaitForMultipleObjects}
- \footnote{\url{https://docs.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-waitformultipleobjects}}.
- \begin{code}{c}{Prototype de la fonction WaitForMultipleObjects sur MSDN}
- DWORD WaitForMultipleObjects(
- DWORD nCount,
- CONST HANDLE *lpHandles,
- BOOL bWaitAll,
- DWORD dwMilliseconds
- );
- \end{code}
- Cette fonction va retourner soit un code d'erreur, soit une constante
- correspondant à l'objet signalé, allant de \inltype{WAIT_OBJECT_0} à
- \inltype{WAIT_OBJECT_0 + nCount-1}.
- Elle ne peut surveiller plus de \inltype{MAXIMUM_WAIT_OBJECTS} en même temps, ce
- qui en pratique se limite à 64 objets. Loin d'être bloquant pour notre projet,
- on va plutôt s'intéresser aux performances. \inltype{WaitForMultipleObjects}
- fonctionne exactement comme \inltype{select} et fournit donc une complexité
- linéaire en le nombre de \inltype{HANDLE} surveillés. Néanmoins, la valeur de
- retour est capable d'indiquer uniquement un seul \inltype{HANDLE} notifié et il
- faut donc parcourir à nouveau le tableau pour trouver les autres
- \inltype{HANDLE} notififés avec la même méthode.
- \subsection{Overlapped IO}
- Windows fournit ensuite un mécanisme bas niveau pour mettre les opérations d'IO
- en \og{}tâche de fond\fg{}
- \footnote{\url{https://docs.microsoft.com/en-us/windows/desktop/sync/synchronization-and-overlapped-input-and-output}}.
- Ci-dessous, on voit l'argument \inltype{lpOverlapped} dans les prototypes des
- fonctions de lecture
- \footnote{\url{https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-readfile}}
- et écriture
- \footnote{\url{https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-writefile}}
- qui va contenir l'état de l'opération lorsqu'elle est réalisée de façon
- asynchrone.
- \begin{code}{c}{Prototype de WriteFile sur MSDN}
- BOOL WriteFile(
- HANDLE hFile,
- LPCVOID lpBuffer,
- DWORD nNumberOfBytesToWrite,
- LPDWORD lpNumberOfBytesWritten,
- LPOVERLAPPED lpOverlapped
- );
- \end{code}
- \begin{code}{c}{Prototype de ReadFile sur MSDN}
- BOOL ReadFile(
- HANDLE hFile,
- __out_data_source(FILE)LPVOID lpBuffer,
- DWORD nNumberOfBytesToRead,
- LPDWORD lpNumberOfBytesRead,
- LPOVERLAPPED lpOverlapped
- );
- \end{code}
- Dans ces deux fonctions, si \inltype{hFile} a été ouvert avec l'option
- \inltype{FILE_FLAG_OVERLAPPED}, le paramètre \inltype{lpOverlapped} doit être
- précisé et pointer vers une structure initialisée de type \inltype{OVERLAPPED}.
- %TODO: mieux expliquer
- La structure \inltype{OVERLAPPED} peut ensuite être utilisée soit avec
- \inltype{WaitForSingleObject}, soit avec \inltype{WaitForMultipleObjects} et
- fournit ainsi un moyen de passer d'un modèle par disponibilité à un modèle par
- complétion.
- Il est possible d'étendre encore le modèle asynchrone en utilisant des
- \og{}completion routines\fg{}. Il faut alors utiliser la version \inltype{ReadFileEx}
- \footnote{\url{https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-readfileex}}
- pour la lecture.
- \begin{code}{c}{Prototype de la fonction ReadFileEx sur MSDN}
- BOOL ReadFileEx(
- HANDLE hFile,
- __out_data_source(FILE)LPVOID lpBuffer,
- DWORD nNumberOfBytesToRead,
- LPOVERLAPPED lpOverlapped,
- LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
- );
- \end{code}
- Le paramètre \inltype{lpCompletionRoutine}
- \footnote{\url{https://msdn.microsoft.com/en-us/574eccda-03eb-4e8a-9d74-cfaecc7312ce}}
- est un pointeur de fonction du type
- suivant:
- \begin{code}{c}{Définition de LPOVERLAPPED\_COMPLETION\_ROUTINE sur MSDN}
- typedef VOID (WINAPI *LPOVERLAPPED_COMPLETION_ROUTINE)(
- _In_ DWORD dwErrorCode,
- _In_ DWORD dwNumberOfBytesTransfered,
- _Inout_ LPOVERLAPPED lpOverlapped
- );
- \end{code}
- La routine de complétion sera alors appelée dès que l'opération sera terminée et
- que le thread sera dans un état alertable, c'est-à-dire se signalera disponible.
- \subsection{IO completion ports}
- Les IO completion ports, ou IOCP pour la suite, permettent d'implémenter un
- système similaire à epoll, dans un modèle par complétion, en étendant les
- \inltype{OVERLAPPED} IO vues précédemment. L'API n'est cependant pas très claire
- sur beaucoup de points donc elle sera plus longuement décrite ici pour apporter
- des explications dans ce rapport.
- Un IOCP peut être créé avec la fonction \inltype{CreateIoCompletionPort}
- \footnote{\url{https://docs.microsoft.com/en-us/windows/desktop/fileio/createiocompletionport}}
- en passant \inltype{INVALID_HANDLE_VALUE} comme \inltype{FileHandle} et
- \inltype{NULL} comme \inltype{ExistingCompletionPort}.
- \begin{code}{c}{Prototype de CreateIoCompletionPort sur MSDN}
- HANDLE WINAPI CreateIoCompletionPort(
- _In_ HANDLE FileHandle,
- _In_opt_ HANDLE ExistingCompletionPort,
- _In_ ULONG_PTR CompletionKey,
- _In_ DWORD NumberOfConcurrentThreads
- );
- \end{code}
- Le paramètre \inltype{CompletionKey} est ignoré dans ce cas, et le paramètre
- \inltype{NumberOfConcurrentThreads} permet d'indiquer à l'IOCP combien de thread
- vont pouvoir simultanément recevoir les notifications de nouveaux messages. Ce
- paramètre ne démarrre pas de nouveaux threads mais sert seulement à
- l'ordonnancement dans le cas où plusieurs threads demanderaient des
- notifications au port. Dans ce dernier cas, chacun des threads recevra des
- notifications pour des opérations différentes, mais cela ne nous concerne pas
- ici.
- Nous pouvons ensuite ajouter des opérations en utilisant la même fonction, mais
- en renseignant \inltype{FileHandle} comme étant l'objet sur lequel l'opération a
- été effectuée, \inltype{ExistingCompletionPort} comme étant le \inltype{HANDLE}
- créé dans l'appel précédent et \inltype{CompletionKey} comme étant une valeur
- arbitraire qui nous servira à retrouver l'opération plus tard.
- Il est possible de créer un port tout en associant un \inltype{HANDLE} à la
- construction, mais le cas ne nous intéresse pas pour l'implémentation de
- \inltype{vlc_msg_poller_t}.
- Une fois les opérations associées au port, on peut utiliser
- \inltype{GetQueuedCompletionStatus}
- \footnote{\url{https://msdn.microsoft.com/en-us/library/Aa364986(v=VS.85).aspx}}
- pour récupérer un paquet de complétion.
- \begin{code}{c}{Prototype de GetQueuedCompletionStatus sur MSDN}
- BOOL WINAPI GetQueuedCompletionStatus(
- _In_ HANDLE CompletionPort,
- _Out_ LPDWORD lpNumberOfBytes,
- _Out_ PULONG_PTR lpCompletionKey,
- _Out_ LPOVERLAPPED *lpOverlapped,
- _In_ DWORD dwMilliseconds
- );
- \end{code}
- Cette fonction attendra la fin d'une opération en cours, et plus exactement
- l'arrivée d'un paquet de complétion, pour retourner, mais permettra de récupérer
- la structure \inltype{OVERLAPPED} contenant les données et informations liées à
- l'opération effectuée.
- Enfin, il sera possible de poster une notification sans effectuer d'opération en
- appelant \inltype{PostQueuedCompletionStatus}
- \footnote{\url{https://docs.microsoft.com/en-us/windows/desktop/FileIO/postqueuedcompletionstatus}},
- afin de débloquer la boucle événementielle par exemple.
- \begin{code}{c}{Prototype de PostQueuedCompletionStatus}
- BOOL WINAPI PostQueuedCompletionStatus(
- _In_ HANDLE CompletionPort,
- _In_ DWORD dwNumberOfBytesTransferred,
- _In_ ULONG_PTR dwCompletionKey,
- _In_opt_ LPOVERLAPPED lpOverlapped
- );
- \end{code}
- \section{Implémentation et conclusions pour la boucle événementielle}
- Ayant déjà décrit \inltype{vlc_msg_poller_t} pour la partie Linux et expliqué en
- détails le fonctionnement de l'API d'IOCP, il reste principalement à décrire
- l'implémentation du modèle par complétion dans la situation d'attente de
- message.
- Contrairement à Linux, les IOCP ne vont pas signaler quand un des
- \inltype{HANDLE} sera prêt à être lu mais plutôt quand les données auront
- effectivement été lues. Il faut donc allouer un tampon par requête de lecture.
- La méthodologie sera alors de dédier un tampon par \inltype{HANDLE} puis
- d'effectuer toutes les opérations de lecture. Lorsqu'une des opérations est
- considérée comme terminée, on pourra traiter le message puis relancer
- l'opération de lecture.
- % TODO: snippet de code
- En résumé, la gestion des entrées sorties et des erreurs proposées par Windows
- ne respecte absolument pas le principe de moindre surprise, mais les différentes
- alternatives proposées par le système permettent de construire le modèle par
- complétion dont nous avons besoin de façon performante.
|