Un modèle de système à temps partagé
Cette section décrit un modèle de système à temps partagé que je juge particulièrement propre à assurer la communication inter-processus. La structure fondamentale de ce modèle de système à temps partagé n'est pas originale [5][9].
Le modèle de système à temps partagé comporte deux éléments : le moniteur et les processus. Le moniteur assure plusieurs fonctions, notamment transférer la commande d'un processus à un autre selon les besoins (par exemple lorsqu'un processus a consommé un temps « suffisant » ou lorsqu'une interruption se produit), gérer la mémoire centrale et le support de va-et-vient, contrôler le passage de la commande d'un processus à un autre (c'est-à-dire les mécanismes de protection), créer des processus, prendre soin des processus endormis, etc.
Les processus assurent la plupart des fonctions habituellement considérées comme des fonctions de superviseur dans un système à temps partagé (processus système) ainsi que les fonctions normales de l'utilisateur (processus utilisateur). Un processus système typique est le gestionnaire de disque ou le système de fichiers. Pour des raisons d'efficacité, il peut être utile de considérer les processus système comme verrouillés en mémoire centrale.
Un processus peut demander au moniteur d'assurer plusieurs fonctions : démarrer un autre processus égal et autonome (c'est-à-dire charger un programme, ou trouver quelque part une copie d'un programme pouvant être partagée, le démarrer et lui passer quelques paramètres initiaux) ; arrêter le processus en cours ; endormir le processus courant en attendant un événement déterminé ; envoyer un message à un processus déterminé ; se rendre disponible pour recevoir un message d'un processus déterminé ; se rendre disponible pour recevoir un message de n'importe quel processus ; envoyer un message à un processus capable de recevoir de n'importe quel processus ; et demander un numéro unique. Il devrait sans aucun doute exister d'autres fonctions du moniteur. On laisse au lecteur le soin de se convaincre, à titre d'exercice, que le moniteur dont il est affligé peut fournir ces fonctions - la plupart le peuvent.
Je ne m'occuperai pas ici des considérations de protection, mais supposerai au contraire 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 capability décrit dans [5][6][7][8] devrait le satisfaire.
Examinons maintenant d'un peu plus près les huit opérations énumérées ci-dessus qu'un processus peut demander au moniteur d'exécuter.
START. Cette opération démarre un autre processus. Elle a deux paramètres : une sorte d'identification du programme à charger, et une liste de paramètres pour ce programme. Une fois le programme chargé, il est démarré à son point d'entrée, et sa liste de paramètres lui est transmise d'une manière bien connue. Le processus continuera d'exister jusqu'à ce qu'il s'arrête lui-même.
HALT. Cette opération endort le processus en cours d'exécution en attendant l'achèvement d'un événement. L'opération a un paramètre : l'événement à attendre. Des exemples d'événements sont l'arrivée d'une interruption matérielle, l'arrivée d'un message d'un autre processus, etc. Le processus est repris à l'instruction qui suit la commande SLEEP. Le moniteur ne force jamais unilatéralement un processus à s'endormir, sauf lorsque le processus épuise son quantum de temps.
RECEIVE. Cette opération permet à un autre processus d'envoyer un message à ce processus. L'opération a quatre paramètres : le port (défini ci-dessous) qui attend le message, le port dont un message sera accepté, une spécification de la mémoire tampon disponible pour recevoir le message, et une adresse de transfert lorsque la transmission est terminée. [Autrement dit, une adresse d'interruption. N'importe quel port de message peut servir à autoriser les interruptions, les canaux d'événements, etc. L'utilisateur programme ce qu'il veut.]
SEND. Cette opération envoie un message à un autre processus. [Je suppose qu'un processus pourrait aussi s'envoyer un message à lui-même.] Elle a quatre paramètres : un port auquel envoyer le message, le port d'où le message est envoyé, le message, et une adresse de transfert lorsque la transmission est terminée.
RECEIVE ANY. Cette opération permet à n'importe quel processus d'envoyer un message à ce processus. L'opération a quatre paramètres : le port qui attend le message, la mémoire tampon disponible pour recevoir le message, une adresse de transfert lorsque le message est reçu, et une adresse où le port qui a envoyé le message peut être noté.
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 mêmes quatre paramètres que SEND. La nécessité de cette opération sera discutée plus bas.
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 ou en provenance d'un processus. Tous les ports ont un numéro 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, désireux de communiquer. Le processus A exécute un RECEIVE au port N depuis le port M.
Le processus B exécute un SEND au 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 la mémoire tampon a été entièrement transmise 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 au processus A, celui-ci 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.
Un exemple. Supposons que notre modèle de système à temps partagé soit initialisé avec plusieurs processus toujours en cours d'exécution. En outre, ces processus permanents ont des ports universellement connus et attribués de façon permanente. [Ou peut-être n'existe-t-il qu'un seul port connu de façon permanente, appartenant à un processus-annuaire qui tient une table des associations entre processus permanents et ports bien connus.] Supposons que deux des processus en cours d'exécution permanente soient le processus d'enregistrement et le processus de balayage du téléscripteur. Lorsque le processus de balayage du téléscripteur commence à s'exécuter, il s'endort en attendant une interruption du balayeur matériel du téléscripteur. Le processus d'enregistrement s'endort initialement en attendant un message du processus de balayage du téléscripteur, via des ports SEND et RECEIVE bien connus et permanents. Le processus de balayage du téléscripteur tient une table indexée par numéro de téléscripteur, où chaque entrée contient un port vers lequel envoyer les caractères de ce téléscripteur, et un port auquel recevoir les caractères destinés à ce téléscripteur. Si un caractère arrive (réveillant le processus de balayage du téléscripteur) et que ce processus n'a aucune entrée pour ce téléscripteur, il obtient une paire de numéros uniques auprès du moniteur (via UNIQUE) et envoie un message contenant cette paire de numéros au processus d'enregistrement, en utilisant les ports dont il sait que le processus d'enregistrement y a un RECEIVE en attente. [En fait, le processus de balayage pourrait toujours utiliser la même paire de numéros de port pour un téléscripteur donné, tant qu'ils ne sont transmis qu'à une seule copie de l'executive à la fois.] Le processus de balayage inscrit aussi la paire de numéros dans la table des téléscripteurs, et envoie les caractères, 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 de balayage transmet probablement aussi une seconde paire de numéros uniques au processus d'enregistrement pour qu'il l'utilise en sortie de téléscripteur, et exécute un RECEIVE à l'aide de ces numéros. Le processus d'enregistrement, lorsqu'il reçoit le message du processus de balayage, démarre une copie de ce que les utilisateurs du SDS 940 TSS [12] appellent l'executive (ce programme qui imprime les répertoires de fichiers, indique qui se trouve sur les autres téléscripteurs, fait tourner des sous-systèmes, etc.) et passe à cette copie de l'executive les numéros de port afin que ce processus executive puisse lui aussi faire ses entrées et sorties vers le téléscripteur au moyen de ces ports. Si le processus d'enregistrement veut obtenir un numéro de tâche et un mot de passe auprès de l'utilisateur, il peut utiliser temporairement ces numéros de port pour communiquer avec l'utilisateur avant de les transmettre à l'executive.
Les numéros de port sont souvent échangés entre processus. Plus rarement, un port est transféré à un autre processus. Il est crucial qu'une fois qu'un processus a transféré un port à un autre processus, le premier n'utilise plus ce port. Nous pourrions ajouter un mécanisme qui impose cette règle. Le système d'objets protégés de [8] est un tel mécanisme. [Bien sûr, si le système d'objets protégés nous est accessible, il n'est vraiment plus 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 constitue une preuve prima facie de son droit d'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 particulier qui ont été distribuées. Un système fondé sur cette approche serait clairement 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 paraît plus pratique d'exiger les deux numéros de port.]
Notons qu'il doit exister quelque part dans le moniteur une table des numéros de port associés aux processus et aux adresses de reprise. Les entrées de cette table sont effacées après chaque appariement SEND/RECEIVE. Notons aussi que si un processus s'exécute (peut-être endormi) et a un RECEIVE ANY en attente, alors n'importe quel processus connaissant le numéro du port de réception peut lui parler sans passer par des processus d'enregistrement ni rien de tel. 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.
Lorsqu'un SEND est exécuté, rien ne se produit tant qu'un RECEIVE correspondant n'est pas exécuté. Si un RECEIVE approprié n'est pas exécuté pendant un certain temps, le SEND expire au bout d'un moment et le processus émetteur du SEND 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 qui l'a exécuté est averti.
Un RECEIVE ANY n'expire jamais, mais peut être retiré. Un message SEND FROM ANY est toujours envoyé immédiatement et sera écarté si aucun récepteur approprié n'existe. 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ù les SEND et les 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é.
En général, les ports bien connus et attribués de façon permanente 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.
Encore un exemple, cette fois une démonstration de l'utilisation du compilateur FORTRAN. Nous avons déjà expliqué comment un utilisateur s'assied devant son téléscripteur et se fait connecter à un executive. Nous reprenons à partir de là. L'utilisateur effectue des entrées et des sorties vers l'executive, qui exécute 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 passe à FORTRAN, comme paramètres de démarrage, les deux ports que l'executive utilisait pour parler au téléscripteur. FORTRAN, bien sûr, attend ces paramètres et exécute des SEND et des RECEIVE vers ces ports pour découvrir quels fichiers d'entrée et de sortie l'utilisateur veut utiliser. FORTRAN imprime INPUT FILE? à l'utilisateur, qui répond F001. FORTRAN envoie alors un message au processus du système de fichiers, lequel dort 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 ports 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 ports 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 rend les numéros de port du téléscripteur à l'executive, qui dormait 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 plus rien d'autre à faire.
[Le lecteur aura sans doute remarqué à présent que je n'aime pas concevoir qu'un nouveau processus (constitué d'une nouvelle copie conceptuelle d'un programme) soit démarré chaque fois qu'un autre utilisateur souhaite utiliser ce programme. Je préfère concevoir le programme comme un processus unique qui sait qu'il est utilisé simultanément par beaucoup d'autres processus et qui multiplexe consciemment entre les utilisateurs, ou diffère le service des utilisateurs jusqu'à ce qu'il puisse s'en occuper.]
De plus, 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 parvient à obtenir des utilisateurs du système de fichiers qu'ils rendent les numéros de port lorsqu'ils n'en ont plus besoin. Bien sûr, lorsque cette collection de numéros de port se sera finalement épuisée, le système de fichiers pourra obtenir de nouveaux numéros uniques auprès du moniteur.
Notons que lorsque deux processus souhaitent communiquer, ils établissent la connexion eux-mêmes 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. Bien sûr, dans une mise en œuvre particulière d'un système à temps partagé, les concepteurs du système peuvent choisir de restreindre l'exécution des SEND et des RECEIVE par les processus et d'interdire l'échange arbitraire de numéros de port, en exigeant plutôt que le moniteur (ou quelque autre programme spécial) soit appelé pour assurer ces fonctions.
Le contrôle de flux est assuré dans ce système par la méthode simple consistant à ne jamais démarrer un SEND depuis un processus avant qu'un RECEIVE ne soit exécuté par le récepteur. Bien sûr, des messages inter-processus peuvent être échangés pour suggérer à un processus d'arrêter d'envoyer, ou pour demander qu'un espace soit alloué, etc.