2. Ein System zur Interprozesskommunikation in einem Time-Sharing-System
Dieser Abschnitt beschreibt eine Menge von Operationen, die Interprozesskommunikation innerhalb eines Time-Sharing-Systems ermöglichen. Der Schreibweise von [10] folgend nenne ich diese Einrichtung zur Interprozesskommunikation ein IPC. Zur Unterstützung der Darstellung dieses IPC wird ein Modell eines Time-Sharing-Systems beschrieben; dieses Modell wird dann benutzt, um die Verwendung der Operationen zur Interprozesskommunikation zu veranschaulichen.
Das Modell-Time-Sharing-System hat zwei Teile: den Monitor und die Prozesse. Der Monitor nimmt Aufgaben wahr wie das Umschalten der Steuerung von einem Prozess auf einen anderen, wenn ein Prozess "genügend" Zeit verbraucht hat, die Behandlung von Hardware-Unterbrechungen, die Verwaltung von Kernspeicher und Auslagerungsmedium, die Steuerung der Übergabe der Steuerung von einem Prozess auf einen anderen (d. h. Schutzmechanismen), das Erzeugen von Prozessen, die Betreuung schlafender Prozesse und die Bereitstellung einer Menge maschinenerweiternder Operationen für die Prozesse (oft Supervisor- oder Monitor Calls genannt). Die Prozesse nehmen die normalen Benutzerfunktionen wahr (Benutzerprozesse) sowie die Funktionen, die in einem Time-Sharing-System üblicherweise als Supervisorfunktionen gelten (Systemprozesse), im gegenwärtigen Modell aber nicht vom Monitor ausgeführt werden. Ein typischer Systemprozess ist der Plattenhandler oder das Dateisystem. Systemprozesse sind der Plattenhandler oder das Dateisystem. Systemprozesse dürfen sich vermutlich im Supervisor-Modus ausführen, und sie führen tatsächlich E/A-Befehle aus und nehmen andere privilegierte Operationen vor, die Benutzerprozesse nicht ausführen dürfen. In jeder anderen Hinsicht sind Benutzer- und Systemprozesse identisch. Aus Effizienzgründen kann es nützlich sein, Systemprozesse als im Kernspeicher festgehalten zu betrachten.
Obwohl sie später in dieser Untersuchung von Bedeutung sein werden, sind Schutzüberlegungen hier nicht mein Anliegen: stattdessen nehme ich an, dass alle Prozesse "gute" Prozesse sind, die niemals einen Fehler machen. Braucht der Leser eine Schutzstruktur, die er beim Lesen dieser Notiz im Auge behalten kann, so dürfte das in [1][3][7][8] entwickelte Capability-System zufriedenstellend sein.
Von den Operationen, die ein Prozess beim Monitor zur Ausführung anfordern kann, sind sechs für die Bereitstellung einer Möglichkeit zur Interprozesskommunikation von besonderem Interesse.
RECEIVE. Diese Operation erlaubt es einem angegebenen Prozess, eine Nachricht an den Prozess zu senden, der das RECEIVE ausführt. Die Operation hat vier Parameter: den auf die Nachricht wartenden Port (unten definiert) -- den RECEIVE-Port; den Port, von dem eine Nachricht angenommen wird -- den SEND-Port; eine Angabe des zum Empfang der Nachricht verfügbaren Puffers; und eine Stelle, an die bei Abschluss der Übertragung übergegangen wird -- die Wiederaufnahmestelle.
SEND. Diese Operation sendet eine Nachricht von dem Prozess, der das SEND ausführt, an einen angegebenen Prozess. Sie hat vier Parameter: einen Port, an den die Nachricht gesendet wird -- den RECEIVE-Port; den Port, von dem die Nachricht gesendet wird -- den SEND-Port; eine Angabe des die zu sendende Nachricht enthaltenden Puffers; und die Wiederaufnahmestelle.
RECEIVE ANY. Diese Operation erlaubt es jedem Prozess, eine Nachricht an den Prozess zu senden, der das RECEIVE ANY ausführt. Die Operation hat vier Parameter: den auf die Nachricht wartenden Port -- den RECEIVE-Port; eine Angabe des zum Empfang der Nachricht verfügbaren Puffers; eine Wiederaufnahmestelle; und eine Stelle, an der der Port vermerkt werden kann, der die Nachricht gesendet hat.
SEND FROM ANY. Diese Operation erlaubt es einem Prozess, eine Nachricht an einen Prozess zu senden, der eine Nachricht von jedem beliebigen Prozess empfangen kann. Sie hat dieselben vier Parameter wie SEND. (Die Notwendigkeit dieser Operation wird viel später erläutert.)
SLEEP. Diese Operation erlaubt es dem gerade laufenden Prozess, sich schlafen zu legen, bis ein Ereignis abgeschlossen ist. Die Operation hat einen optionalen Parameter, das Ereignis, auf das gewartet wird. Ein Beispiel für ein Ereignis ist das Eintreffen einer Hardware-Unterbrechung. Der Monitor legt einen Prozess niemals einseitig schlafen, weil dieser eine der vier oben genannten Operationen ausgeführt hat; ist jedoch ein Prozess schlafend, wenn eine der vier oben genannten Operationen erfüllt wird, so wird der Prozess geweckt.
UNIQUE. Diese Operation holt eine eindeutige Nummer vom Monitor.
Ein Port ist ein bestimmter Datenweg zu einem Prozess (ein RECEIVE-Port) oder von einem Prozess (ein SEND-Port), und alle Ports haben eine zugehörige eindeutige Portnummer, mit der der Port identifiziert wird. Ports werden bei der Übertragung von Nachrichten von einem Prozess zu einem anderen auf folgende Weise benutzt. Man betrachte zwei Prozesse A und B, die kommunizieren wollen. Prozess A führt ein RECEIVE auf Port N von Port M aus. Prozess B führt ein SEND auf Port N von Port M aus. Der Monitor bringt die Portnummern zur Deckung und überträgt die Nachricht von Prozess B zu Prozess A. Sobald der Puffer vollständig aus Prozess B heraus übertragen ist, wird Prozess B an der im SEND angegebenen Stelle wiederaufgenommen. Sobald die Nachricht bei Prozess A vollständig empfangen ist, wird Prozess A an der im RECEIVE angegebenen Stelle wiederaufgenommen. Wie die Prozesse in den Besitz der richtigen Portnummern gelangen, mit denen sie mit anderen Prozessen kommunizieren, ist nicht Sache des Monitors -- dieses Problem bleibt den Prozessen überlassen.
Wird ein SEND ausgeführt, so geschieht nichts, bis ein passendes RECEIVE ausgeführt wird. Irgendwo im Monitor muss es eine Tabelle von Portnummern geben, die Prozessen und Wiederaufnahmestellen zugeordnet sind. Die Tabelleneinträge werden nach jeder hergestellten SEND/RECEIVE-Zuordnung gelöscht. Wird nicht innerhalb einiger Zeit ein passendes RECEIVE ausgeführt, so läuft das SEND nach einer Weile ab und der sendende Prozess wird benachrichtigt. Wird ein RECEIVE ausgeführt, aber das passende SEND erfolgt lange nicht, so läuft das RECEIVE ab und der empfangende Prozess wird benachrichtigt.
Der Mechanismus des Ablaufenlassens "unbenutzter" Tabelleneinträge ist von geringer grundsätzlicher Bedeutung und bietet lediglich ein bequemes Verfahren zum Einsammeln des Mülls in der Tabelle. Es ist kein Problem, wenn ein Eintrag vorzeitig abläuft, denn der Prozess kann die Operation jederzeit erneut ausführen. Der Zeitablauf sollte jedoch lang genug gewählt werden, damit die fortlaufende erneute Ausführung einer Operation nur geringen Aufwand verursacht.
Ein RECEIVE ANY läuft niemals ab, kann aber mit einem Supervisor-Aufruf zurückgenommen werden. Eine aus einem SEND FROM ANY hervorgegangene Nachricht wird stets sofort gesendet und verworfen, wenn kein passender Empfänger existiert. Eine Fehlermeldung wird nicht zurückgesendet, und eine Bestätigung, falls es eine gibt, liegt bei den Prozessen. Läuft die Tabelle, in der SEND und RECEIVE zur Deckung gebracht werden, einmal über, so wird ein Prozess, der ein weiteres SEND und RECEIVE auslöst, benachrichtigt, gerade als wäre das SEND oder RECEIVE abgelaufen.
Die Wiederaufnahmestelle ist ein Unterbrechungseingang, der zu einer pseudounterbrechung gehört, die für den Prozess lokal ist, der die die Wiederaufnahmestelle angebende Operation ausführt. Läuft der Prozess gerade, wenn das die Pseudo-Unterbrechung auslösende Ereignis eintritt (zum Beispiel das Eintreffen einer Nachricht, die ein anhängiges RECEIVE erfüllt), so ist die Wirkung genau so, als hätte die Hardware den Prozess unterbrochen und die Steuerung an die Wiederaufnahmestelle übergeben. Es werden genügend Informationen gesichert, damit der Prozess nach Abwicklung der Unterbrechung an der Stelle fortfahren kann, an der er unterbrochen wurde. Schläft der Prozess, so wird er betriebsbereit gemacht und die Pseudo-Unterbrechung wird gespeichert, bis der Prozess wieder läuft; dann wird die Unterbrechung zugelassen. Jeder RECEIVE- oder RECEIVE ANY-Nachrichtenport kann somit dazu dienen, Prozessunterbrechungen, Ereigniskanäle, Prozesssynchronisation, Nachrichtenübertragungen usw. bereitzustellen. Der Benutzer programmiert, was er will.
Es bleibt dem Leser als Übung überlassen, sich davon zu überzeugen, dass der Monitor, mit dem er sich herumschlagen muss, dazu gebracht werden kann, die sechs oben beschriebenen Operationen bereitzustellen -- die meisten Monitore können das, denn es handelt sich nur um zusätzliche Supervisor-Aufrufe.
Ein Beispiel. Angenommen, unser Modell-Time-Sharing-System wird so initialisiert, dass mehrere Prozesse ständig laufen. Außerdem haben diese dauerhaften Prozesse einige allgemein bekannte und dauerhaft zugewiesene Ports<2>. Angenommen, zwei der dauerhaft laufenden Prozesse sind der Protokollprozess (logger-process) und der Fernschreiber-Abtastprozess (teletype-scanner-process). Wenn der Fernschreiber-Abtastprozess zum ersten Mal zu laufen beginnt, legt er sich schlafen und wartet auf eine Unterbrechung von der Hardware des Fernschreiber-Abtasters. Der Protokollprozess legt sich anfangs schlafen und wartet auf eine Nachricht vom Fernschreiber-Abtastprozess über wohlbekannte dauerhafte SEND- und RECEIVE-Ports. Der Fernschreiber-Abtastprozess führt eine nach Fernschreibernummer indexierte Tabelle, die in jedem Eintrag ein Paar von Portnummern zum Senden von Zeichen von diesem Fernschreiber an einen Prozess und ein Paar von Portnummern zum Empfangen von Zeichen für diesen Fernschreiber von einem Prozess enthält. Trifft ein Zeichen ein (wodurch der Fernschreiber-Abtastprozess geweckt wird) und hat der Prozess keinen Eintrag für diesen Fernschreiber, so holt er sich ein Paar eindeutiger Nummern vom Monitor (mittels UNIQUE) und sendet eine Nachricht mit diesem Nummernpaar an den Protokollprozess, und zwar über die Ports, für die der Protokollprozess bekanntermaßen ein RECEIVE anhängig hat. Der Abtastprozess trägt das Nummernpaar außerdem in die Fernschreibertabelle ein und sendet das Zeichen und alle künftigen Zeichen dieses Fernschreibers an den Port mit der ersten Nummer von dem Port mit der zweiten Nummer. Der Abtastprozess muss außerdem ein zweites Paar eindeutiger Nummern an den Protokollprozess weitergeben, damit dieser sie für die Fernschreiberausgabe verwendet, und mit diesen Portnummern ein RECEIVE ausführen. Wenn der Protokollprozess die Nachricht vom Abtastprozess empfängt, startet er eine Kopie dessen, was die Benutzer des SDS 940 TSS [6] Executive<3> nennen, und übergibt die Portnummern an diese Kopie des Executive, damit auch dieser Executive-Prozess seine Ein- und Ausgaben zum Fernschreiber über diese Ports abwickeln kann. Will der Protokollprozess eine Auftragsnummer und ein Kennwort vom Benutzer erhalten, so kann er vorübergehend die Portnummern benutzen, um mit dem Benutzer zu kommunizieren, bevor er sie an den Executive weitergibt. Der Abtastprozess könnte für einen bestimmten Fernschreiber stets dieselben Portnummern verwenden, solange die Nummern jeweils nur an eine Kopie des Executive weitergegeben werden.
Es ist wichtig, zwischen dem Vorgang des Weitergebens eines Ports von einem Prozess an einen anderen und dem Vorgang des Weitergebens einer Portnummer von einem Prozess an einen anderen zu unterscheiden. Im vorherigen Beispiel, in dem Zeichen von einem bestimmten Fernschreiber durch den Fernschreiber-Abtastprozess entweder an den Protokollprozess oder an einen Executive-Prozess gesendet werden, bleibt der SEND-Port stets im Fernschreiber-Abtastprozess, während der RECEIVE-Port vom Protokollprozess an den Executive-Prozess wandert. Andererseits wird die SEND-Portnummer zwischen dem Protokollprozess und dem Executive-Prozess weitergegeben, damit der empfangende Prozess ein RECEIVE von dem richtigen SEND-Port ausführen kann. Entscheidend ist, dass der erste Prozess den Port nicht mehr benutzt, sobald er ihn an einen anderen Prozess weitergegeben hat. Wir könnten einen Mechanismus hinzufügen, der dies erzwingt. Das System geschützter Objekte aus [9] ist ein solcher Mechanismus. Mit diesem Mechanismus bräuchte ein ein SEND ausführender Prozess eine Berechtigung (capability) für den SEND-Port, und zu jedem gegebenen Zeitpunkt dürfte im System nur eine Berechtigung für diesen SEND-Port existieren. Ein ein RECEIVE ausführender Prozess müsste eine Berechtigung für den RECEIVE-Port besitzen, und zu einem gegebenen Zeitpunkt dürfte nur eine Berechtigung für diesen RECEIVE-Port existieren. Ohne einen solchen Schutzmechanismus wandert ein Port implizit von einem Prozess zu einem anderen, indem die Prozesse den Port einfach zu überschneidungsfreien Zeiten benutzen, auch wenn die Portnummer nie ausdrücklich weitergegeben wird.
Wenn uns das System geschützter Objekte natürlich zur Verfügung steht, besteht eigentlich keine Notwendigkeit, zwei Portnummern anzugeben, bevor eine Übertragung stattfinden kann. Die Tatsache, dass ein Prozess eine bestehende RECEIVE-Portnummer kennt, könnte als prima facie gegebenes Anzeichen für das Recht dieses Prozesses gelten, an diesen Port zu senden. Der Unterschied zwischen RECEIVE- und RECEIVE ANY-Ports hängt dann allein von der Anzahl der ausgegebenen Kopien einer bestimmten Portnummer ab. Ein auf diesem Ansatz beruhendes System wäre dem hier beschriebenen offensichtlich vorzuziehen, wenn man annehmen könnte, dass alle autonomen Time-Sharing-Systeme in einem Netz diesen Schutzmechanismus übernehmen würden. Lässt sich diese Annahme nicht treffen, so erscheint es praktischer, beide Portnummern zu verlangen.
Man beachte, dass in dem hier beschriebenen System zur Interprozesskommunikation (IPC) zwei Prozesse, die kommunizieren wollen, die Verbindung selbst aufbauen und dies frei auf eine für beide Seiten bequeme Weise tun können. Sie können zum Beispiel Portnummern austauschen, oder ein Prozess kann alle Portnummern auswählen und dem anderen Prozess mitteilen, welche er verwenden soll. In einer bestimmten Implementierung eines Time-Sharing-Systems könnten die Erbauer des Systems jedoch beschließen, die Ausführung von SENDs und RECEIVEs durch die Prozesse einzuschränken und das beliebige Weitergeben von Ports und Portnummern zu verbieten und stattdessen zu verlangen, dass der Monitor (oder ein anderes spezielles Programm) aufgerufen wird, um diese Funktionen auszuführen.
Die Flusssteuerung wird in diesem IPC durch das einfache Verfahren bereitgestellt, dass die aus einem SEND eines Prozesses resultierende Datenübertragung niemals beginnt, bevor nicht der Empfänger ein RECEIVE ausgeführt hat. Natürlich können auch Nachrichten zwischen Prozessen hin- und hergesendet werden, die nahelegen, dass ein Prozess das Senden einstellt oder dass Speicher zugeteilt wird.
Im Allgemeinen werden wohlbekannte dauerhaft zugewiesene Ports über RECEIVE ANY und SEND FROM ANY benutzt. Die dauerhaften Ports werden am häufigsten zum Starten von Prozessen verwendet, und folglich werden über sie nur wenige Daten gesendet. Läuft ein Prozess (vielleicht schlafend) und hat ein RECEIVE ANY anhängig, so kann jeder Prozess, der die Empfangsportnummer kennt, mit diesem Prozess sprechen, ohne den Weg über Protokollprozesse zu nehmen. Das ist offensichtlich innerhalb eines lokalen Time-Sharing-Systems unerlässlich und erscheint in einem allgemeineren Netz sehr nützlich, wenn das Ideal der Ressourcenteilung erreicht werden soll. In einem Netz mit Ressourcenteilung könnten beispielsweise die Programme in den Unterprogrammbibliotheken aller Standorte stets anhängige RECEIVE ANYs auf dauerhaft zugewiesenen Ports mit wohlbekannten Portnummern haben. Um eine bestimmte Netzressource wie etwa eine Hardware zur Matrizenmanipulation zu nutzen, kann ein irgendwo im Netz laufender Prozess somit eine Nachricht an das Unterprogramm zur Matrixinversion senden, die die zu invertierende Matrix und die zum Zurücksenden der Ergebnisse zu verwendenden Portnummern enthält.
Ein weiteres Beispiel zeigt die Verwendung des FORTRAN-Compilers. Wir haben bereits erläutert, wie sich ein Benutzer an seinen Fernschreiber setzt und mit einem Executive verbunden wird. Von dort gehen wir weiter. Der Benutzer gibt in den Executive ein und erhält Ausgaben von ihm, während dieser SENDs und RECEIVEs ausführt. Schließlich tippt der Benutzer RUN FORTRAN, und der Executive bittet den Monitor, eine Kopie des FORTRAN-Compilers zu starten, und übergibt an FORTRAN als Startparameter die Portnummern, die der Executive zum Sprechen mit dem Fernschreiber verwendet hat. (Zumindest begrifflich erhält FORTRAN einen Port, an dem es Zeichen vom Fernschreiber empfängt, und einen Port, von dem es Zeichen an den Fernschreiber sendet.) FORTRAN erwartet diese Parameter natürlich und führt SENDs und RECEIVEs über die angegebenen Ports aus, um vom Benutzer zu erfahren, welche Eingabe- und Ausgabedateien er verwenden will. FORTRAN schreibt INPUT FILE? an den Benutzer, der mit F001 antwortet. Daraufhin sendet FORTRAN eine Nachricht an den Dateisystemprozess, der schlafend auf etwas zu tun wartet. Die Nachricht wird über wohlbekannte Ports gesendet und bittet das Dateisystem, F001 zur Eingabe zu öffnen. Die Nachricht enthält außerdem ein Paar von Portnummern, die der Dateisystemprozess zum Senden seiner Antwort verwenden kann. Das Dateisystem schlägt F001 nach, öffnet es zur Eingabe, macht einige Einträge in seinen Tabellen offener Dateien und sendet an FORTRAN eine Nachricht zurück, die die Portnummern enthält, mit denen FORTRAN die Datei lesen kann. Für die Ausgabedatei wird dasselbe Verfahren befolgt. Wenn die Übersetzung abgeschlossen ist, gibt FORTRAN die Fernschreiber-Portnummern (und die Ports) an den Executive zurück, der schlafend auf eine Nachricht von FORTRAN gewartet hat, und danach hält FORTRAN sich selbst an. Der Dateisystemprozess kehrt in den Schlaf zurück, wenn er nichts anderes zu tun hat<4>.
Auch hier kann der Dateisystemprozess eine kleine Sammlung von Portnummern halten, die er immer wieder verwendet, wenn er die Benutzer des Dateisystems dazu bringen kann, die Portnummern zurückzugeben, wenn sie mit ihnen fertig sind. Wenn diese Sammlung von Portnummern schließlich versickert ist, kann das Dateisystem natürlich einige neue eindeutige Nummern vom Monitor holen.