123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- \section{Communication entre processus}
- Dans un premier temps, il faut choisir un mécanisme de communication entre les
- différents processus. Nous allons donc lister rapidement les méthodes
- utilisables.
- \begin{itemize}
- \item Les tubes unix ou pipes unix permettent de réaliser une communication
- multi-processus unidirectionnel. D'un côté, un producteur écrit dans le
- tube, ou bloque jusqu'à ce qu'il puisse avoir la place d'écrire. De
- l'autre côté, un consommateur lit dans le tube ou bloque si aucune
- données n'est disponible. Les tubes unix ont l'avantage d'être très
- facile à manipuler et être globalement disponible sur toutes les
- plateformes importantes sans exception. Néanmoins, il ne s'agit que du
- transfert de données et il faut donc un mécanisme d'accès pour les
- ressources. Les tubes Unix peuvent être créés de façon anonymes avec la
- fonction \inltype{pipe}, soit être nommés via la fonction
- \inltype{mknode} ou \inltype{mkfifo}.
- \item Les files de message POSIX, ou message queues sont assez proches des
- tubes unix, mais sont forcément nommées et permettent d'ajouter des
- priorités tout en segmentant les messages. Ici la propriété de
- segmentation est très intéressante pour pouvoir déboguer et parser les
- messages envoyés d'une partie de l'application à l'autre. Néanmoins,
- aucun mécanisme ne permet d'envoyer des ressources sur une file de
- message. Il faut donc également réaliser une passerelle et un contrôle
- d'identité pour pouvoir l'utiliser dans notre cas.
- \item Les mémoires partagés POSIX fournissent un moyen de communication
- extrêmement performants pour dialoguer. Elles ont besoin d'un mécanisme
- de synchronisation mais n'ont absolument pas besoin de passer par des
- appels systèmes hormi pour leur création. C'est donc un candidat de
- choix pour une communication entre deux processus. En revanche, elle
- demande l'introduction de beaucoup de contrôle autour pour éviter les
- bogues classiques en C, les dépassements, les écrasements et les
- ressources ne peuvent être transmises qu'à travers un autre mécanisme ou
- une redirection d'IO vers la mémoire partagée.
- %TODO: expliquer redirection au dessus en citant la partie général: qqun
- % fait les IO et redirige le résultat sur la shared memory
- \item Les alternatives Sys V des méthodes citées ci-dessus. Malheureusement,
- les primitives de Sys V fonctionnent avec leur propre API et ne se
- rapproche pas de la philosophie unix du tout. Ces API fournissent un
- objet Sys V au lieu d'un descripteur de fichier et bien qu'étant
- compatible plus facilement avec d'autres systèmes de part son
- ancienneté, nous allons voir que les alternatives apportent bien plus
- d'avantages.
- \item Les sockets unix permettent d'envoyer des messages, soit de façon
- segmentée avec les mécanismes de «end of record», soit par l'envoi de
- datagrammes. Les performances en tant qu'IPC sont les mêmes que les deux
- premières méthodes, mais les sockets Unix disposent d'une
- caractéristique unique qui correspondent très exactement à notre
- problème. Ils disposent d'un moyen de transmettre des descripteurs de
- fichiers d'un processus à l'autre, en y joignant le message qui permet
- d'interpréter la ressource. Cette interface est néanmoins difficile à
- manipuler comme on va le voir dans la suite.
- \end{itemize}
- La solution qui s'applique le plus simplement à notre solution tout en
- conservant des performances correctes apparait clairement comme étant les
- sockets unix. Ils fournissent unes permettant d'apporter la gestion des accès.
- Ils fournissent également un moyen de transférer des descripteurs de fichiers
- entre processus.
- \section{Partage de ressources entre processus}
- Ensuite, nous allons implémenter le transfert de descripteur de fichier
- entre processus. L'objectif derrière cette fonctionnalité est de pouvoir
- utiliser les descripteurs de fichier comme des jetons inforgeables qui apportent
- des accès aux ressources, soit par communication, soit par accès direct.
- L'implémentation finale de ce système est fortement inspirée des techniques
- utilisées initialement par Nginx. Nginx est un serveur web et proxy inverse
- programmé avec un paradigme asynchrone. Comme pour la sandbox, il fonctionne
- avec un processus orchestrateur qui contrôle les processus serveurs, ou workers.
- Chaque worker fonctionne en tant qu'utilisateur non privilégié.
- Cette solution doit être compatible avec le bus de communication que l'on veut
- mettre en place.
- Les sockets unix sont une solution de communication inter-processus
- correspondant parfaitement au problème. Ils sont gérés efficacement, peuvent
- être anonyme et donc parfaitement contrôlés et sont représentés dans le
- programme par des descripteurs de fichiers. Nous allons également voir dans la
- suite qu'ils permettent également de transférer les descripteurs de fichier
- grâce à un mécanisme de données auxiliaires.
- Le mécanisme de données auxiliaires mentionné plus haut est générique pour
- toutes les classes de sockets. En fait, on peut s'intéresser aux structures
- utilisées lors de l'envoi d'un message.
- \begin{code}{c}{Structure \inltype{msghdr} utilisée dans
- \inltype{sendmsg}/\inltype{recvmsg}}
- /* from man 2 send */
- struct msghdr {
- void *msg_name; /* optional address */
- socklen_t msg_namelen; /* size of address */
- struct iovec *msg_iov; /* scatter/gather array */
- size_t msg_iovlen; /* # elements in msg_iov */
- void *msg_control; /* ancillary data, see below */
- size_t msg_controllen; /* ancillary data buffer len */
- int msg_flags; /* flags (unused) */
- };
- \end{code}
- Il y a en réalité deux types de buffers envoyés via cette structure. Le premier
- type concerne \inltype{msg_iov} qui est en réalité un tableau de
- \inltype{iovec}. Chaque \inltype{iovec} contient un pointeur vers un buffer et
- une taille. Ces \inltype{iovec} représentent les données qui seront envoyés à
- travers le socket. Le fait que \inltype{msg_iov} soit un tableau de ces buffers
- vient du fait que le système va devoir faire des copies à travers les noyaux et
- donc qu'il est plus efficace de ne le faire qu'une fois si les données que l'on
- veut envoyer sont séparés de base.
- L'autre buffer est en réalité dédié à la pile logicielle manipulant le socket
- dans le noyau. Il s'agit d'un buffer de configuration, ou pour reprendre le
- vocabulaire utilisé dans la structure, un buffer de contrôle de l'interface bas
- niveau. Il peut être utilisé pour donner des paramètres dépendant de chaque type
- de socket.
- \begin{code}{c}{Structure \inltype{cmsghdr} représentant un élément auxiliaire}
- /* from man 3 cmsg */
- struct cmsghdr {
- size_t cmsg_len; /* Data byte count, including header
- (type is socklen_t in POSIX) */
- int cmsg_level; /* Originating protocol */
- int cmsg_type; /* Protocol-specific type */
- /* followed by
- unsigned char cmsg_data[]; */
- };
- \end{code}
- La manipulation de ce buffer est un peu particulière. Il s'agit en fait
- d'allouer des structures de données au seins du buffer même, tout en respectant
- les contraintes demandées par le système. En pratique, l'API POSIX fournit les
- macros suivantes pour manipuler ce buffer.
- \begin{itemize}
- \item \inltype{CMSG_FIRSTHDR} retourne un pointeur vers le premier
- \inltype{cmsghdr} dans le tampon de données auxiliaires associé au
- \inltype{msghdr} passé en paramètre.
- \item \inltype{CMSG_NXTHDR} retourne le prochain \inltype{cmsghdr} après
- celui passé en paramètre, ou \inltype{NULL} si le tampon est plein.
- \item \inltype{CMSG_SPACE} retourne la taille occupée par un élément
- auxiliaire.
- \item \inltype{CMSG_DATA} retourne un pointeur vers la partie donnée d'un
- \inltype{cmsghdr}.
- \item \inltype{CMSG_LEN} retourne la valeur à stocker dans le champ
- \inltype{cmsg_len} en comptant l'alignement, à partir de la taille des
- données.
- \end{itemize}
- Deux problématiques ont due être prise en compte lors de l'utilisation de cette
- API. D'un côté, il faut faire attention aux accès non alignés. De l'autre, il
- faut respecter les règles de strict aliasing.
- % TODO développer problématiques
|