2. Un système de communication entre processus dans un système à temps partagé
Cette section décrit un ensemble d'opérations permettant la communication entre processus à l'intérieur d'un système à temps partagé. Suivant la notation de [10], j'appelle cette facilité de communication entre processus un IPC. Pour faciliter la présentation de cet IPC, un modèle de système à temps partagé est décrit ; ce modèle sert ensuite à illustrer l'emploi des opérations de communication entre processus.
Le système à temps partagé modèle comporte deux éléments : le moniteur et les processus. Le moniteur assure des fonctions telles que la commutation de contrôle d'un processus vers un autre lorsqu'un processus a utilisé « suffisamment » de temps, le traitement des interruptions matérielles, la gestion de la mémoire centrale et du support de permutation, le contrôle du passage du contrôle d'un processus à un autre (c'est-à-dire les mécanismes de protection), la création de processus, la prise en charge des processus endormis, et la fourniture aux processus d'un ensemble d'opérations d'extension de la machine (souvent appelées Supervisor ou Monitor Calls). Les processus assurent les fonctions normales de l'utilisateur (processus utilisateur) ainsi que les fonctions habituellement considérées comme des fonctions de supervision dans un système à temps partagé (processus système) mais non assurées par le moniteur dans le modèle actuel. Un processus système typique est le gestionnaire de disque ou le système de fichiers. Les processus système sont le gestionnaire de disque ou le système de fichiers. Les processus système sont probablement autorisés à s'exécuter en mode superviseur, et ils exécutent réellement des instructions d'E/S et effectuent d'autres opérations privilégiées que les processus utilisateur ne sont pas autorisés à effectuer. À tous autres égards, les processus utilisateur et système sont identiques. Pour des raisons d'efficacité, il peut être utile de considérer les processus système comme verrouillés en mémoire centrale.
Bien qu'elles nous préoccuperont plus loin dans cette étude, les considérations de protection ne sont pas mon sujet ici : je supposerai plutôt que tous les processus sont de « bons » processus qui ne commettent jamais d'erreur. Si le lecteur a besoin d'une structure de protection à garder en tête pendant sa lecture de cette note, le système de capacités développé dans [1][3][7][8] devrait le satisfaire.
Parmi les opérations qu'un processus peut demander au moniteur d'exécuter, six présentent un intérêt particulier pour offrir une capacité de communication entre processus.
RECEIVE. Cette opération permet à un processus spécifié d'envoyer un message au processus qui exécute le RECEIVE. L'opération a quatre paramètres : le port (défini ci-dessous) en attente du message -- le port RECEIVE ; le port dont un message sera accepté -- le port SEND ; une spécification du tampon disponible pour recevoir le message ; et un emplacement vers lequel transférer lorsque la transmission est terminée -- l'emplacement de reprise.
SEND. Cette opération envoie un message du processus qui exécute le SEND vers un processus spécifié. Elle a quatre paramètres : un port auquel envoyer le message -- le port RECEIVE ; le port depuis lequel le message est envoyé -- le port SEND ; une spécification du tampon contenant le message à envoyer ; et l'emplacement de reprise.
RECEIVE ANY. Cette opération permet à tout processus d'envoyer un message au processus qui exécute le RECEIVE ANY. L'opération a quatre paramètres : le port en attente du message -- le port RECEIVE ; une spécification du tampon disponible pour recevoir le message ; un emplacement de reprise ; et un emplacement où noter le port qui a envoyé le message.
SEND FROM ANY. Cette opération permet à un processus d'envoyer un message à un processus capable de recevoir un message de n'importe quel processus. Elle a les quatre mêmes paramètres que SEND. (La nécessité de cette opération sera expliquée bien plus loin).
SLEEP. Cette opération permet au processus en cours d'exécution de s'endormir en attendant l'achèvement d'un événement. L'opération a un paramètre facultatif, l'événement à attendre. Un exemple d'événement est l'arrivée d'une interruption matérielle. Le moniteur n'endort jamais unilatéralement un processus du fait que celui-ci a exécuté l'une des quatre opérations ci-dessus ; toutefois, si un processus est endormi lorsqu'une des quatre opérations ci-dessus est satisfaite, ce processus est réveillé.
UNIQUE. Cette opération obtient un numéro unique auprès du moniteur.
Un port est un chemin de données particulier vers un processus (un port RECEIVE) ou depuis un processus (un port SEND), et tous les ports ont un numéro de port unique associé qui sert à identifier le port. Les ports servent à transmettre des messages d'un processus à un autre de la manière suivante. Considérons deux processus, A et B, qui souhaitent communiquer. Le processus A exécute un RECEIVE sur le port N depuis le port M. Le processus B exécute un SEND sur le port N depuis le port M. Le moniteur apparie les numéros de port et transfère le message du processus B au processus A. Dès que le tampon a été entièrement transmis hors du processus B, le processus B est repris à l'emplacement spécifié dans l'opération SEND. Dès que le message est entièrement reçu par le processus A, le processus A est repris à l'emplacement spécifié dans l'opération RECEIVE. La manière dont les processus se procurent les bons numéros de port pour communiquer avec d'autres processus ne concerne pas le moniteur -- ce problème est laissé aux processus.
Lorsqu'un SEND est exécuté, rien ne se produit tant qu'un RECEIVE correspondant n'est pas exécuté. Quelque part dans le moniteur doit se trouver une table des numéros de port associés aux processus et aux emplacements de reprise. Les entrées de la table sont effacées après chaque appariement SEND/RECEIVE. Si un RECEIVE adéquat n'est pas exécuté pendant un certain temps, le SEND finit par expirer et le processus émetteur en est averti. Si un RECEIVE est exécuté mais que le SEND correspondant ne se produit pas pendant longtemps, le RECEIVE expire et le processus récepteur en est averti.
Le mécanisme d'expiration des entrées « inutilisées » de la table a peu d'importance fondamentale ; il fournit simplement une méthode commode de ramassage des miettes de la table. Il n'y a pas de problème si une entrée expire prématurément, car le processus peut toujours réexécuter l'opération. Cependant, l'intervalle d'expiration devrait être assez long pour que la réexécution continuelle d'une opération n'entraîne qu'une faible surcharge.
RECEIVE ANY n'expire jamais, mais peut être retiré au moyen d'un appel au superviseur. Un message résultant d'un SEND FROM ANY est toujours envoyé immédiatement et sera écarté s'il n'existe pas de destinataire adéquat. Aucun message d'erreur n'est renvoyé et l'accusé de réception, s'il y en a un, incombe aux processus. Si la table où le SEND et le RECEIVE sont appariés venait à déborder, un processus émettant un nouveau SEND ou RECEIVE en est averti exactement comme si le SEND ou le RECEIVE avait expiré.
L'emplacement de reprise est une entrée d'interruption associée à une pseudo-interruption locale au processus qui exécute l'opération spécifiant l'emplacement de reprise. Si le processus est en cours d'exécution lorsque survient l'événement provoquant la pseudo-interruption (par exemple, l'arrivée d'un message satisfaisant un RECEIVE en attente), l'effet est exactement celui qu'aurait une interruption matérielle du processus transférant le contrôle à l'emplacement de reprise. Les informations suffisantes sont sauvegardées pour que le processus puisse reprendre son exécution au point où il a été interrompu une fois l'interruption traitée. Si le processus est endormi, il est mis en état de marche et la pseudo-interruption est conservée jusqu'à ce que le processus s'exécute de nouveau, et l'interruption est alors autorisée. Tout port de message RECEIVE ou RECEIVE ANY peut donc servir à fournir des interruptions de processus, des canaux d'événements, de la synchronisation de processus, des transferts de messages, etc. L'utilisateur programme ce qu'il veut.
C'est au lecteur qu'il appartient, à titre d'exercice, de se convaincre que le moniteur dont il est affligé peut être amené à fournir les six opérations décrites ci-dessus -- la plupart des moniteurs le peuvent, puisque ce ne sont que des appels au superviseur supplémentaires.
Un exemple. Supposons que notre système à temps partagé modèle soit initialisé avec plusieurs processus toujours en cours d'exécution. En outre, ces processus permanents possèdent quelques ports<2> universellement connus et assignés à demeure. Supposons que deux des processus en cours d'exécution permanente soient le processus journal (logger-process) et le processus explorateur de téléscripteur (teletype-scanner-process). Lorsque le processus explorateur de téléscripteur commence à s'exécuter, il s'endort en attendant une interruption du matériel explorateur de téléscripteur. Le processus journal s'endort initialement en attendant un message du processus explorateur de téléscripteur via des ports SEND et RECEIVE permanents bien connus. Le processus explorateur de téléscripteur tient une table indexée par numéro de téléscripteur, contenant dans chaque entrée une paire de numéros de port à utiliser pour envoyer les caractères de ce téléscripteur à un processus, et une paire de numéros de port à utiliser pour recevoir les caractères destinés à ce téléscripteur depuis un processus. Si un caractère arrive (réveillant le processus explorateur de téléscripteur) et que le processus n'a aucune entrée pour ce téléscripteur, il obtient une paire de numéros uniques auprès du moniteur (par UNIQUE) et envoie un message contenant cette paire de numéros au processus journal en utilisant les ports pour lesquels il sait que le processus journal a un RECEIVE en attente. Le processus explorateur inscrit également la paire de numéros dans la table des téléscripteurs, et envoie le caractère ainsi que tous les caractères ultérieurs de ce téléscripteur au port portant le premier numéro depuis le port portant le second numéro. Le processus explorateur doit aussi transmettre une seconde paire de numéros uniques au processus journal pour qu'il l'utilise en sortie de téléscripteur, et exécuter un RECEIVE avec ces numéros de port. Lorsque le processus journal reçoit le message du processus explorateur, il démarre une copie de ce que les utilisateurs du SDS 940 TSS [6] appellent l'executive<3>, et transmet les numéros de port à cette copie de l'executive, afin que ce processus executive puisse lui aussi effectuer ses entrées et sorties vers le téléscripteur au moyen de ces ports. Si le processus journal veut obtenir un numéro de tâche et un mot de passe auprès de l'utilisateur, il peut utiliser temporairement les numéros de port pour communiquer avec l'utilisateur avant de les transmettre à l'executive. Le processus explorateur pourrait toujours utiliser les mêmes numéros de port pour un téléscripteur donné, tant que les numéros ne sont transmis qu'à une seule copie de l'executive à la fois.
Il importe de distinguer l'acte de transmettre un port d'un processus à un autre et l'acte de transmettre un numéro de port d'un processus à un autre. Dans l'exemple précédent, où les caractères d'un téléscripteur donné sont envoyés soit au processus journal, soit à un processus executive, par le processus explorateur de téléscripteur, le port SEND reste toujours dans le processus explorateur de téléscripteur tandis que le port RECEIVE passe du processus journal au processus executive. En revanche, le numéro de port SEND est transmis entre le processus journal et le processus executive pour permettre au processus récepteur d'exécuter un RECEIVE depuis le bon port SEND. Il est crucial que, dès qu'un processus transfère un port à un autre processus, le premier processus n'utilise plus ce port. Nous pourrions ajouter un mécanisme qui impose cela. Le système d'objets protégés de [9] est un tel mécanisme. Avec ce mécanisme, un processus exécutant un SEND aurait besoin d'une capacité pour le port SEND, et une seule capacité pour ce port SEND existerait dans le système à un instant donné. Un processus exécutant un RECEIVE serait tenu de posséder une capacité pour le port RECEIVE, et une seule capacité pour ce port RECEIVE existerait à un instant donné. Sans un tel mécanisme de protection, un port passe implicitement d'un processus à un autre du simple fait que les processus utilisent le port à des moments disjoints, même si le numéro du port n'est jamais transmis explicitement.
Bien sûr, si le système d'objets protégés nous est accessible, il n'est vraiment pas nécessaire de spécifier deux numéros de port avant qu'une transmission puisse avoir lieu. Le fait qu'un processus connaisse un numéro de port RECEIVE existant pourrait être considéré comme une preuve de prime abord (prima facie) du droit de ce processus à envoyer vers ce port. La différence entre les ports RECEIVE et RECEIVE ANY ne dépend alors que du nombre de copies d'un numéro de port donné qui ont été distribuées. Un système fondé sur cette approche serait de toute évidence préférable à celui décrit ici s'il était possible de supposer que tous les systèmes à temps partagé autonomes d'un réseau adopteraient ce mécanisme de protection. Si cette hypothèse ne peut être faite, il semble plus pratique d'exiger les deux numéros de port.
Notons que, dans le système de communication entre processus (IPC) décrit ici, lorsque deux processus souhaitent communiquer, ils établissent eux-mêmes la connexion et sont libres de le faire d'une manière mutuellement commode. Par exemple, ils peuvent échanger des numéros de port, ou bien un processus peut choisir tous les numéros de port et indiquer à l'autre lesquels utiliser. Cependant, dans une mise en œuvre particulière d'un système à temps partagé, les constructeurs du système pourraient choisir de restreindre l'exécution des SEND et des RECEIVE par les processus et d'interdire la circulation arbitraire des ports et des numéros de port, en exigeant au contraire que le moniteur (ou quelque autre programme spécial) soit appelé pour assurer ces fonctions.
Le contrôle de flux est assuré dans cet IPC par la méthode simple consistant à ne jamais commencer la transmission de données résultant d'un SEND d'un processus avant qu'un RECEIVE ne soit exécuté par le destinataire. Bien entendu, des messages entre processus peuvent aussi être échangés pour suggérer qu'un processus cesse d'émettre ou qu'un espace soit alloué.
En général, les ports bien connus et assignés à demeure sont utilisés via RECEIVE ANY et SEND FROM ANY. Les ports permanents serviront le plus souvent à démarrer des processus et, par conséquent, peu de données passeront par eux. Si un processus est en cours d'exécution (peut-être endormi) et a un RECEIVE ANY en attente, alors tout processus connaissant le numéro du port de réception peut parler à ce processus sans passer par les processus journaux. Cela est évidemment essentiel au sein d'un système à temps partagé local et semble très utile dans un réseau plus général si l'idéal du partage de ressources doit être atteint. Par exemple, dans un réseau de partage de ressources, les programmes des bibliothèques de sous-programmes de tous les sites pourraient avoir en permanence des RECEIVE ANY en attente sur des ports assignés à demeure et dont les numéros sont bien connus. Ainsi, pour utiliser une ressource réseau particulière telle qu'un matériel de manipulation de matrices, un processus s'exécutant n'importe où dans le réseau peut envoyer au sous-programme d'inversion de matrice un message contenant la matrice à inverser et les numéros de port à utiliser pour renvoyer les résultats.
Un exemple supplémentaire illustre l'emploi du compilateur FORTRAN. Nous avons déjà expliqué comment un utilisateur s'installe à son téléscripteur et se fait connecter à un executive. Nous poursuivons à partir de là. L'utilisateur dialogue en entrée et en sortie avec l'executive, qui effectue des SEND et des RECEIVE. Finalement, l'utilisateur tape RUN FORTRAN, et l'executive demande au moniteur de démarrer une copie du compilateur FORTRAN et transmet à FORTRAN, comme paramètres de démarrage, les numéros de port que l'executive utilisait pour parler au téléscripteur. (Du moins conceptuellement, FORTRAN reçoit un port auquel RECEIVE les caractères venant du téléscripteur et un port depuis lequel SEND les caractères vers le téléscripteur.) FORTRAN attend bien sûr ces paramètres et effectue des SEND et des RECEIVE via les ports indiqués pour découvrir auprès de l'utilisateur quels fichiers d'entrée et de sortie il veut utiliser. FORTRAN tape INPUT FILE? à l'utilisateur, qui répond F001. FORTRAN envoie alors un message au processus du système de fichiers, qui est endormi en attendant quelque chose à faire. Le message est envoyé via des ports bien connus et demande au système de fichiers d'ouvrir F001 en entrée. Le message contient aussi une paire de numéros de port que le processus du système de fichiers peut utiliser pour envoyer sa réponse. Le système de fichiers cherche F001, l'ouvre en entrée, inscrit quelques entrées dans ses tables de fichiers ouverts, et renvoie à FORTRAN un message contenant les numéros de port que FORTRAN peut utiliser pour lire le fichier. La même procédure est suivie pour le fichier de sortie. Lorsque la compilation est terminée, FORTRAN renvoie les numéros de port du téléscripteur (et les ports) à l'executive qui était endormi en attendant un message de FORTRAN, puis FORTRAN s'arrête lui-même. Le processus du système de fichiers se rendort lorsqu'il n'a rien d'autre à faire<4>.
Là encore, le processus du système de fichiers peut conserver une petite collection de numéros de port qu'il réutilise sans cesse s'il peut amener les utilisateurs du système de fichiers à lui rendre les numéros de port lorsqu'ils ont fini de s'en servir. Bien entendu, lorsque cette collection de numéros de port s'est finalement épuisée, le système de fichiers peut obtenir de nouveaux numéros uniques auprès du moniteur.