123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- \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}
|