\section{Gestion asynchrone} Maintenant que j'ai choisi les solutions de communication entre les différents processus, on peut s'intéresser à la manière de gérer les échanges de messages venant potentiellement de plusieurs sources. Linux dispose de plusieurs mécanismes pour effectuer des opérations asynchrones, tous n'ayant pas les mêmes caractéristiques en terme de consommation mémoire ou complexité. \subsection{poll, select} Parmi les solutions pour manipuler plusieurs descripteurs de fichier en même temps, les solutions classiques sont \inltype{poll} et \inltype{select}. Les deux fonctions marquent leur ancienneté par une mauvaise complexité algorithmique dans leur exploitation, c'est-à-dire un temps linéaire en le nombre de descripteurs lors de l'attente d'un événement. Dans un premier temps, nous allons voir \inltype{poll}, à qui nous pouvons indiquer l'ensemble des événements à surveiller et qui nous signalera lorsque l'un des événements a été notifié. Le vériable descripteur ayant reçu la notification n'est cependant pas précisé et il faut parcourir le tableau des événements pour pouvoir le trouver. \begin{code}{c}{Prototype de poll dans man 2 poll} int poll(struct pollfd *fds, nfds_t nfds, int délai); \end{code} Cette méthode est aujourd'hui relativement remplacée par \inltype{epoll} et ne nous intéressera pas. Ensuite, \inltype{select} fournit le même mécanisme en indiquant par des opérations bit à bit quels sont les descripteurs de fichier ayant reçu une notification. En plus de souffrir du problème précédent obligeant à parcourir l'ensemble des descripteurs, elle est donc également limitée en nombre de descripteur de fichier possible. \begin{code}{c}{Prototype de select et macros dans man 2 select} int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); void FD_CLR(int fd, fd_set *set); int FD_ISSET(int fd, fd_set *set); void FD_SET(int fd, fd_set *set); void FD_ZERO(fd_set *set); \end{code} \subsection{Asynchronous IO} Les asynchronous IO, ou AIO, sont un mécanisme de notification de complétion des requêtes de lecture et écriture. L'objectif final de l'implémentation des AIO est d'arriver à faire du zéro-copie sur l'API réseau de Linux. En effet, dans un modèle par complétion, on peut indiquer directement au noyau où stocker les données reçues. Il faut néanmoins accepter la déception et voir que c'est parfois aussi coûteux que de faire la copie, particulièrement sur les transmissions de petite taille. L'API est implémentée en userland dans glibc, est relativement coûteuse et ne permet d'attendre la première opération qui aurait terminé comme le permettait les primitives précédentes. Ell est également difficile à utiliser car les notifications sont reçues sous forme de signal, de nouveau thread ou ne sont pas envoyées. Elle est donc inutilisable pour notre application mais vaut le coup d'être mentionnée pour la réalisation d'un \inltype{vlc_msg_poller_t} zéro-copie dans le futur. \begin{code}{c}{Extrait de l'API de AIO depuis man 2} struct aiocb { /* The order of these fields is implementation-dependent */ int aio_fildes; /* File descriptor */ off_t aio_offset; /* File offset */ volatile void *aio_buf; /* Location of buffer */ size_t aio_nbytes; /* Length of transfer */ int aio_reqprio; /* Request priority */ struct sigevent aio_sigevent; /* Notification method */ int aio_lio_opcode; /* Operation to be performed; lio_listio() only */ /* Various implementation-internal fields not shown */ }; int aio_read(struct aiocb *aiocbp); int aio_write(struct aiocb *aiocbp); \end{code} \subsection{epoll} % TODO: https://lkml.org/lkml/2015/2/24/667 % TODO: https://lore.kernel.org/patchwork/patch/543311/ Linux fournit depuis assez récemment un mécanisme bien plus performant et avec une interface bien plus proche des autres API standards. Ce mécanisme, epoll, a longtemps été critiqué car bien qu'étant le dernier des mécanismes permettant ce genre de programmation sur événement, il reproduisait des problèmes d'interface qui avaient été résolus par les solutions précédentes sur les autres systèmes depuis longtemps. C'est notamment des développeurs chez Nginx qui ont remonté des problèmes dans leur utilisation qui a abouti à l'ajout de \inltype{EPOLLEXCLUSIVE} dans le noyau Linux 4.5. Dans notre cas, il nous fournit une interface fonctionnant sur la disponibilité des descripteurs de fichiers, utilisable facilement pour construire notre implémentation du modèle par complétion. La complexité est en $O(1)$ et elle n'a pas de limitation sur le nombre de descripteurs autre que la consommation mémoire. C'est aussi la méthode de remplacement privilégiée pour \inltype{poll} et en concurrence directe avec les AIO de la sous-section précédente. \begin{code}{c}{API de epoll décrite dans man 2 epoll} /* retourne un descripteur de fichier vers un objet epoll */ int epoll_create1(int flags); /* attend l'arrivée d'un événement sur l'instance d'epoll donnée */ int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); /* permet d'ajouter, modifier ou enlever des associations d'événement à des * descripteurs de fichiers dans l'instance d'epoll */ int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); \end{code} \section{Implémentation de \texttt{vlc\_msg\_poller\_t}} Je peux désormais présenter l'implémentation Linux de \inltype{vlc_msg_poller_t} que j'ai réalisée dans le prototype, en utilisant epoll. \inltype{vlc_msg_poller_t} est construite uniquement avec un descripteur de fichier. \begin{code}{c}{Structure vlc\_msg\_poller\_t dans le prototype Linux} struct vlc_msg_poller_t { int fd; } \end{code} La création se fait en appelant \inltype{epoll_create1} qui remplace la fonction dépréciée \inltype{epoll_create}. L'ajout d'une nouvelle IPC dans le poller utilise ensuite le pointeur utilisateur de \inltype{epoll_event} pour garder une trace de l'IPC. \begin{code}{c}{Implémentation de msg\_poller\_AddChannel dans le prototype Linux} VLC_API void msg_poller_AddChannel( vlc_msg_poller_t *p_poller, vlc_process_ipc_t *p_ipc ) { struct epoll_event event = { /* se déclenche lorsque des données peuvent être lues */ .events = EPOLLIN, .data = { /* on peut aussi choisir de stocker seulement le file descriptor */ //.fd = p_ipc->fd, .ptr = p_ipc } }; epoll_ctl( p_poller->fd, EPOLL_CTL_ADD, p_ipc->fd, &event); } \end{code} De même, la suppression appelle seulement la fonction \inltype{epoll_ctl} avec l'argument \inltype{EPOLL_CTL_DEL}. Enfin, la fonction d'attente utilise \inltype{epoll_wait} pour attendre et récupérer le pointeur vers l'IPC étant disponible pour l'opération de lecture. Une fois l'IPC récupérée, le \inltype{vlc_msg_poller_t} peut lire les données et extraire le message en suivant notre modèle par complétion. \begin{code}{c}{Implémentation de msg\_poller\_WaitMsg dans le prototype Linux} VLC_API int msg_poller_WaitMsg( vlc_msg_poller_t *p_poller, vlc_process_msg_t **p_msg, vlc_process_ipc_t **p_ipc ) { struct epoll_event event; int ret = epoll_wait( p_poller->fd, &event, 1, -1 ); if( ret == -1 ) return VLC_EGENERIC; *p_ipc = event.data.ptr; /* extraction du message depuis l'IPC */ return vlc_ipc_RecvMsg( *p_ipc, p_msg ); } \end{code}