linux_file_descriptor.tex 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. \section{Communication entre processus}
  2. Dans un premier temps, il faut choisir un mécanisme de communication entre les
  3. différents processus. Nous allons donc lister rapidement les méthodes
  4. utilisables.
  5. \begin{itemize}
  6. \item Les tubes unix ou pipes unix permettent de réaliser une communication
  7. multi-processus unidirectionnel. D'un côté, un producteur écrit dans le
  8. tube, ou bloque jusqu'à ce qu'il puisse avoir la place d'écrire. De
  9. l'autre côté, un consommateur lit dans le tube ou bloque si aucune
  10. données n'est disponible. Les tubes unix ont l'avantage d'être très
  11. facile à manipuler et être globalement disponible sur toutes les
  12. plateformes importantes sans exception. Néanmoins, il ne s'agit que du
  13. transfert de données et il faut donc un mécanisme d'accès pour les
  14. ressources. Les tubes Unix peuvent être créés de façon anonymes avec la
  15. fonction \inltype{pipe}, soit être nommés via la fonction
  16. \inltype{mknode} ou \inltype{mkfifo}.
  17. \item Les files de message POSIX, ou message queues sont assez proches des
  18. tubes unix, mais sont forcément nommées et permettent d'ajouter des
  19. priorités tout en segmentant les messages. Ici la propriété de
  20. segmentation est très intéressante pour pouvoir déboguer et parser les
  21. messages envoyés d'une partie de l'application à l'autre. Néanmoins,
  22. aucun mécanisme ne permet d'envoyer des ressources sur une file de
  23. message. Il faut donc également réaliser une passerelle et un contrôle
  24. d'identité pour pouvoir l'utiliser dans notre cas.
  25. \item Les mémoires partagés POSIX fournissent un moyen de communication
  26. extrêmement performants pour dialoguer. Elles ont besoin d'un mécanisme
  27. de synchronisation mais n'ont absolument pas besoin de passer par des
  28. appels systèmes hormi pour leur création. C'est donc un candidat de
  29. choix pour une communication entre deux processus. En revanche, elle
  30. demande l'introduction de beaucoup de contrôle autour pour éviter les
  31. bogues classiques en C, les dépassements, les écrasements et les
  32. ressources ne peuvent être transmises qu'à travers un autre mécanisme ou
  33. une redirection d'IO vers la mémoire partagée.
  34. %TODO: expliquer redirection au dessus en citant la partie général: qqun
  35. % fait les IO et redirige le résultat sur la shared memory
  36. \item Les alternatives Sys V des méthodes citées ci-dessus. Malheureusement,
  37. les primitives de Sys V fonctionnent avec leur propre API et ne se
  38. rapproche pas de la philosophie unix du tout. Ces API fournissent un
  39. objet Sys V au lieu d'un descripteur de fichier et bien qu'étant
  40. compatible plus facilement avec d'autres systèmes de part son
  41. ancienneté, nous allons voir que les alternatives apportent bien plus
  42. d'avantages.
  43. \item Les sockets unix permettent d'envoyer des messages, soit de façon
  44. segmentée avec les mécanismes de «end of record», soit par l'envoi de
  45. datagrammes. Les performances en tant qu'IPC sont les mêmes que les deux
  46. premières méthodes, mais les sockets Unix disposent d'une
  47. caractéristique unique qui correspondent très exactement à notre
  48. problème. Ils disposent d'un moyen de transmettre des descripteurs de
  49. fichiers d'un processus à l'autre, en y joignant le message qui permet
  50. d'interpréter la ressource. Cette interface est néanmoins difficile à
  51. manipuler comme on va le voir dans la suite.
  52. \end{itemize}
  53. La solution qui s'applique le plus simplement à notre solution tout en
  54. conservant des performances correctes apparait clairement comme étant les
  55. sockets unix. Ils fournissent unes permettant d'apporter la gestion des accès.
  56. Ils fournissent également un moyen de transférer des descripteurs de fichiers
  57. entre processus.
  58. \section{Partage de ressources entre processus}
  59. Ensuite, nous allons implémenter le transfert de descripteur de fichier
  60. entre processus. L'objectif derrière cette fonctionnalité est de pouvoir
  61. utiliser les descripteurs de fichier comme des jetons inforgeables qui apportent
  62. des accès aux ressources, soit par communication, soit par accès direct.
  63. L'implémentation finale de ce système est fortement inspirée des techniques
  64. utilisées initialement par Nginx. Nginx est un serveur web et proxy inverse
  65. programmé avec un paradigme asynchrone. Comme pour la sandbox, il fonctionne
  66. avec un processus orchestrateur qui contrôle les processus serveurs, ou workers.
  67. Chaque worker fonctionne en tant qu'utilisateur non privilégié.
  68. Cette solution doit être compatible avec le bus de communication que l'on veut
  69. mettre en place.
  70. Les sockets unix sont une solution de communication inter-processus
  71. correspondant parfaitement au problème. Ils sont gérés efficacement, peuvent
  72. être anonyme et donc parfaitement contrôlés et sont représentés dans le
  73. programme par des descripteurs de fichiers. Nous allons également voir dans la
  74. suite qu'ils permettent également de transférer les descripteurs de fichier
  75. grâce à un mécanisme de données auxiliaires.
  76. Le mécanisme de données auxiliaires mentionné plus haut est générique pour
  77. toutes les classes de sockets. En fait, on peut s'intéresser aux structures
  78. utilisées lors de l'envoi d'un message.
  79. \begin{code}{c}{Structure \inltype{msghdr} utilisée dans
  80. \inltype{sendmsg}/\inltype{recvmsg}}
  81. /* from man 2 send */
  82. struct msghdr {
  83. void *msg_name; /* optional address */
  84. socklen_t msg_namelen; /* size of address */
  85. struct iovec *msg_iov; /* scatter/gather array */
  86. size_t msg_iovlen; /* # elements in msg_iov */
  87. void *msg_control; /* ancillary data, see below */
  88. size_t msg_controllen; /* ancillary data buffer len */
  89. int msg_flags; /* flags (unused) */
  90. };
  91. \end{code}
  92. Il y a en réalité deux types de buffers envoyés via cette structure. Le premier
  93. type concerne \inltype{msg_iov} qui est en réalité un tableau de
  94. \inltype{iovec}. Chaque \inltype{iovec} contient un pointeur vers un buffer et
  95. une taille. Ces \inltype{iovec} représentent les données qui seront envoyés à
  96. travers le socket. Le fait que \inltype{msg_iov} soit un tableau de ces buffers
  97. vient du fait que le système va devoir faire des copies à travers les noyaux et
  98. donc qu'il est plus efficace de ne le faire qu'une fois si les données que l'on
  99. veut envoyer sont séparés de base.
  100. L'autre buffer est en réalité dédié à la pile logicielle manipulant le socket
  101. dans le noyau. Il s'agit d'un buffer de configuration, ou pour reprendre le
  102. vocabulaire utilisé dans la structure, un buffer de contrôle de l'interface bas
  103. niveau. Il peut être utilisé pour donner des paramètres dépendant de chaque type
  104. de socket.
  105. \begin{code}{c}{Structure \inltype{cmsghdr} représentant un élément auxiliaire}
  106. /* from man 3 cmsg */
  107. struct cmsghdr {
  108. size_t cmsg_len; /* Data byte count, including header
  109. (type is socklen_t in POSIX) */
  110. int cmsg_level; /* Originating protocol */
  111. int cmsg_type; /* Protocol-specific type */
  112. /* followed by
  113. unsigned char cmsg_data[]; */
  114. };
  115. \end{code}
  116. La manipulation de ce buffer est un peu particulière. Il s'agit en fait
  117. d'allouer des structures de données au seins du buffer même, tout en respectant
  118. les contraintes demandées par le système. En pratique, l'API POSIX fournit les
  119. macros suivantes pour manipuler ce buffer.
  120. \begin{itemize}
  121. \item \inltype{CMSG_FIRSTHDR} retourne un pointeur vers le premier
  122. \inltype{cmsghdr} dans le tampon de données auxiliaires associé au
  123. \inltype{msghdr} passé en paramètre.
  124. \item \inltype{CMSG_NXTHDR} retourne le prochain \inltype{cmsghdr} après
  125. celui passé en paramètre, ou \inltype{NULL} si le tampon est plein.
  126. \item \inltype{CMSG_SPACE} retourne la taille occupée par un élément
  127. auxiliaire.
  128. \item \inltype{CMSG_DATA} retourne un pointeur vers la partie donnée d'un
  129. \inltype{cmsghdr}.
  130. \item \inltype{CMSG_LEN} retourne la valeur à stocker dans le champ
  131. \inltype{cmsg_len} en comptant l'alignement, à partir de la taille des
  132. données.
  133. \end{itemize}
  134. Deux problématiques ont due être prise en compte lors de l'utilisation de cette
  135. API. D'un côté, il faut faire attention aux accès non alignés. De l'autre, il
  136. faut respecter les règles de strict aliasing.
  137. % TODO développer problématiques