Jeglicher Informationsaustausch in einem L3 System erfolgt durch Versand einer Botschaft von einer Task an eine andere. Eine Task ist der Sender, die andere der Empfänger einer Botschaft. Prinzipiell kann jede Task irgendwelche Botschaften an irgendeine andere senden. Eine Botschaft wird jedoch vom avisierten Empfänger nur angenommen, wenn der Empfänger seine Empfangsbereitschaft hergestellt hat. Die Kommunikation ist synchron. Eine Task kann empfangsbereit für Sendungen einer bestimmten Task sein (geschlossenes Warten) oder auch empfangsbereit für Sendungen jeder beliebigen Task (offenes Warten). Der Sende-/Empfangsvorgang ist atomar. Es gibt nur die Zeit vor der Übermittlung einer Botschaft und die Zeit nach der Übermittlung. Der Fall, daß eine Botschaft »teilweise empfangen« wurde, ist nicht möglich. Die eigentliche Übermittlung einer Botschaft ist logisch zeitlos.
Tatsächlich kann die Übermittlung sehr wohl einige Zeit benötigen, z.B. wenn mehrere lange Bytestrings übertragen werden. Es ist sogar der Fall denkbar, daß der Rechner ordnungsgemäß abgeschaltet wird und eine Botschaftübermittlung faktisch vor dem Abschalten und dem damit verbundenen Fixpunkt begann und nach dem Wiedereinschalten beendet wird. Trotzdem garantiert der Systemkern für die beiden beteiligten Tasks die Atomizität des Botschaftstransfers.
Die Objekte der Kommunikation, die Botschaften, sind strukturierte Datenobjekte. Die Struktur und der Inhalt einer Botschaft sind im Botschaftsvektor der Task beschrieben. Der Botschaftsvektor, der ein Teil des Prozeßkontrollblocks einer jeden Task ist, ist ein String von 32 Doppelworten. In diesen String werden in beliebiger Mischung und Anordnung Einträge für Botschaftsobjekte der Typen INT, Bytesequenz (z.B. TEXT) oder DATASPACE gemacht. Ein Eintrag für INT und DATASPACE ist ein Doppelwort, ein Bytesequenz-Eintrag zwei Doppelworte lang. Eine Botschaft kann einen bis sechzehn Integer, einen bis sechzehn Datenräume, einen bis sieben Texte oder beliebige Mischungen daraus bis zur maximalen Eintragslänge von sechzehn Doppelworten aufnehmen.
Der Aufbau einer Botschaft wird vom Absender durch Initialisierung des Botschaftsvektors mit der Prozedur new msg eingeleitet. put msg (param) »füllt« den Botschaftsvektor mit Objekten des definierten Typs. Die fertige Botschaft wird dann durch send oder call an den Empfänger geschickt.
Der Empfänger initialisiert ebenfalls seinen Botschaftsvektor (new msg) und spezifiziert durch invite msg (param) den Aufbau der erwarteten Botschaft. Anschließend geht die empfangswillige Task in geschlossenes Warten (receive) oder offenes Warten (wait) über. Nach Empfang der Botschaft wird der Botschaftsvektor durch get msg (param) ausgewertet.
(* TASK sender *) (*1*) INT VAR result, integer := 1234 ; (*2*) TEXT VAR string := "Hallo Empfänger!" ; (*3*) DATASPACE VAR space := dataspace (std); (*4*) new msg; (*5*) put msg (integer); (*6*) put msg (string); (*7*) put msg (space); (*8*) send (/"empfänger",never,result); (*9*) IF result = communication ok THEN putline ("O.K.") ELSE putline ("K.O.") FI; (* TASK empfänger *) (*10*) INT VAR my int, maxlength := 40, result; TEXT VAR textvar := ""; DATASPACE VAR rec ds; (*11*) new msg; (*12*) invite msg (int msg); (*13*) invite msg (string msg, textvar, maxlength); (*14*) invite msg (int msg); (*15*) invite msg (ds msg ); (*16*) receive (/"sender",never,result); (*17*) get msg (my int); (*18*) get msg (textvar); (*19*) get msg (rec ds); (*20*) FILE VAR f := sequential file (output, logfile); putline (f, (text (my int))); putline (f, (textvar)); putline (f, (text (index (rec ds)))); (* So kann der Inhalt des empfangenen Datenraums *) (* in eine Textdatei kopiert werden: *) copy (rec ds, "neue datei um " + time) ;
Dieses Beispiel skizziert den Ablauf einer einmaligen Botschaftsübermittlung zwischen einem Sender und einem Empfänger. In der sendenden Task wird zunächst der Inhalt der Botschaft festgelegt. Es sollen die Zahl '1234', der Text 'Hallo Empfänger!' und ein Datenraum, der die zuletzt benutzte Datei enthält (wird durch die PROC std geliefert) an die Task 'empfänger' geschickt werden.
Diese in Zeile 1 bis 3 deklarierten Objekte werden nach Initialisierung des Botschaftsvektors (Zeile 4) durch put msg in den Botschaftsvektor eingetragen (Zeile 5 bis 7). Die eigentliche Übertragung (Zeile 8) erfolgt mit der Option never (ggf. beliebig lange auf Empfangsbereitschaft des Empfängers warten) für das Sende- Timeout.
Anschließend wird das vom Systemkern in 'result' gelieferte Ergebnis der Operation ausgewertet.
Der Empfänger legt ebenfalls Objekte für den Empfang der erwarteten Botschaft fest (10), initialisiert seinen Botschaftsvektor (11) und bestimmt die Interpretation der erwarteten Objekte (invite msg (..) ).
Hinweis: da Texte als Bytestrings verschickt werden (Zeile 14), muß die empfangende Task zwei Abschnitte des Botschaftsvektors freihalten. Siehe dazu den folgenden Abschnitt über den Aufbau des Botschaftsvektors.
Anschließend geht die empfangende Task in den Zustand »geschlossenes Warten« (Botschaften werden nur von 'sender' akzeptiert (Zeile 16)). Nach Empfang einer Botschaft wird der Inhalt des Botschaftsvektors in die entsprechenden Variablen übernommen (Zeile 17 bis 19) und in einer Datei protokolliert (Zeile 20).
Die wesentliche Erkenntnis an dieser Stelle soll sein, daß zwischen Sender und Empfänger eine Absprache über den Aufbau der gesendeten und erwarteten Botschaft existieren muß, da durch nachträgliche Inspektion der Inhalt einer empfangenen Botschaft nicht sicher entschlüsselt werden kann.
Solche Absprachen sind für wichtige Bereiche der Intertask Kommunikation bereits getroffen. Ihre Beschreibung ist Inhalt von Kapitel 13 ff.: Protokolle.
Achtung: Über die Intertask Kommunikation wird nicht nur die von Ihnen programmierte Kommunikation abgewickelt, sondern zum Beispiel auch die normale Ein/Ausgabe.
JEDE EINGABE/AUSGABE OPERATION BENUTZT DEN BOTSCHAFTSVEKTOR.
Der Botschaftsvektor wird also von Testausgaben gegebenenfalls zerstört !
Würde also im vorangegangenen Beispiel zwischen Zeile 16 und Zeile 17 das Kommando putline ("Die Nachricht ist angekommen") eingefügt, wäre der Botschaftsvektor der empfangenden Task zerstört und sie könnte die für sie bestimmte Nachricht nicht mehr korrekt auswerten.
Das Programm
new msg; put msg (int msg); put msg (ds msg) put ("HALLO"); ...
ist folgendermaßen zu lesen:
initialisiere den Botschaftsvektor; erster Abschnitt wird Integer; zweiter Abschnitt wird Datenraum; initialisiere den Botschaftsvektor; (* !!!!! *) erster Abschnitt wird Integer ; (*'Gib-aus Code'*) zweiter Abschnitt wird Text ; (* 'HALLO' *) call (/"my display", never, never, result); ....
Der Botschaftsvektor ist ein 128 Byte langer Abschnitt im Prozeßkontrollblock einer Task.
MDV RDV B1 B2 .... B15 B16 E1 E2 .... E13 E14
Die Doppelworte 'MDV' und 'RDV' beschreiben die Typen der folgenden Einträge, B1 bis B16 sind Botschaftsabschnitte, E1 bis E14 Eigenadressen.
Da der Empfänger im Falle der string msg eine Fremdadresse erhält, muß noch die Angabe eines geeigneten Platzes im eigenen Adreßraum folgen. Nur zu diesem Zweck werden die Eigenadressen E1 bis E14 benötigt.
Dieser Abschnitt des Botschaftsvektors wird also nur benutzt, wenn Bytestrings empfangen werden sollen.
Die ELAN Schnittstelle zur Intertask-Kommunikation umfaßt neben den Prozeduren zum Aufbau von Botschaften und den Sende-/Empfangsoperationen auch noch eine Reihe vom Konstanten zur Auswertung von Botschaften und Antworten.
Botschaftsabschnitte und Eigenadressen werden nicht gelöscht. Bei Analyse des Botschaftsvektors können also durchaus noch alte Inhalte in diesen Abschnitten enthalten sein.
PROC put msg (DATASPACE VAR ds)
PROC put msg (TEXT CONST string)
PROC put msg (INT CONST stringaddr, stringlength)
PROC invite msg (INT CONST typ)
PROC invite msg (INT CONST typ, TEXT VAR string, INT CONST max length)
PROC invite msg (INT CONST typ, stringaddr, max length)
PROC get msg (DATASPACE VAR ds)
PROC get msg (TEXT VAR string)
TEXT VAR buffer ; new msg; invite msg (buffer, 32000); wait(...); get msg (buffer);
Falls der empfangene String in einer anderen Variablen benötigt wird, muß anschließend kopiert werden.
Folgendes Vorgehen bietet nämlich eine wunderliche Fehlerquelle:
TEXT VAR buffer, tvar ; new msg; invite msg (buffer , 32000); wait (...); get msg (tvar) (* sehr fehleranfällig !! *)
Es kann bei diesem Vorgehen vorkommen, daß der Empfang in der Textvariablen 'tvar' eine Müllabfuhr auf dem Textheap des Standarddatenraums auslöst, die die Quelle 'buffer' verschiebt. In diesem Falle wird der String von einer falschen Anfangsadresse an kopiert.
Eine andere Möglichkeit, diese Fehlerquelle auszuschließen, besteht darin, als Empfangspuffer eine ROW 32000 BYTE zu definieren.
PROC get msg (INT VAR stringaddr, stringlength)
(************************************************) (* Dieses Programm in Task "empfänger" starten *) (************************************************) TASK VAR sender ; LET RBUFFER = ROW 100 BYTE ; RBUFFER VAR bufa, bufb, bufc; INT VAR res, intvar1, intvar2 , in addr := ADDR bufa(1) , in length := 100 ; (* Hier kann man sich selbst umzingeln *) (* Falls der Wert > Puffer ist, wird *) (* hinter dem Puffer überschrieben *) new msg; invite msg ( string msg , in addr , in length); (* bytestring , Adr im Stdds , soviel darfs sein *) wait (50000 , sender , res); get msg (intvar1, intvar2) ; (***********************************************) (* Anschließend dieses in der sendenden Task *) (***********************************************) TASK VAR partner := task ("empfänger") ; INT VAR res , i ; LET SBUFFER = ROW 100 BYTE ; SBUFFER VAR sbuf1, sbuf2, sbuf3; FOR i FROM 1 UPTO 100 REP sbuf1 [i] := byte (i ) PER; new msg; put msg (ADDR sbuf1 [1] , 300) ; send (partner, 1000 , res); IF res = communication ok THEN put ("OK") FI ;
Um bei Definition und Auswertung der erwarteten Botschaftsabschnitte mit sprechenden Namen arbeiten zu können, sind folgende Konstanten vordefiniert.
Um initialisierte Botschaftsvektoren vor Überschreiben zu schützen, können sie in einen Stack gebracht werden, der maximal 100 Vektoren aufnehmen kann. Dies kann insbesondere nötig werden, wenn vor dem Absenden oder Auswerten des Botschaftsvektors irgendwelche Ausgaben gemacht werden sollen.
Der Botschaftstransfer, die Übermittlung des Inhalts des Botschaftsvektors der sendenden Task zum Botschaftsvektor der empfangenden Task kann stattfinden, wenn der Empfänger seine Empfangsbereitschaft hergestellt hat. Der Empfänger stellt durch wait oder receive seine Empfangsbereitschaft her und eine vom Sender durch send oder call abgeschickte Botschaft wird empfangen. Da die Kommunikation Unwägbarkeiten unterworfen ist (z.B. kann eine Task bereits von einem anderen Kommunikationspartner vereinnahmt sein), enthält jede der Prozeduren eine Variable, in der vom System das erzielte Ergebnis geliefert wird.
Alle Operationen können durch ein Timeout limitiert werden. Wenn nach Ablauf dieser in Millisekunden angegebenen Zeitspanne der avisierte Partner nicht entsprechend reagiert hat, so wird die Operation mit einer entsprechenden Meldung beendet.
Soll kein Abbruch durch Zeitüberschreitung möglich sein, muß never als Timeout gewählt werden.
Falls bei einer send oder call Operation, in der ein oder mehrere Datenräume verschickt werden sollten, ein send timeout auftrat, sollten die Datenraumbezeichner mit get msg aus dem Botschaftsvektor geholt und ggf. gelöscht werden. Andernfalls bliebe eine Datenraumleiche in der Task zurück.
PROC call (TASK CONST partner, INT CONST send timeout, receive timeout, INT VAR result)
communication ok: eine Botschaft wurde geschickt und von 'partner' empfangen. Die Antwort von 'partner' wurde empfangen.
task not existing: die Task 'partner' gibt es nicht. Es wurde keine Botschaft geschickt und keine Antwort empfangen.
send timeout: bis zum Ablauf von send timeout war die Task 'partner' nicht empfangsbereit. Es wurde keine Botschaft geschickt und keine Antwort empfangen.
receive timeout: bis zum Ablauf von receive timeout wurde keine Antwort von 'partner' empfangen. Dieses Ergebnis wird auch geliefert, wenn 'partner' gelöscht wird, bevor sie die Antwort zurückschicken kann.
PROC send one receive one (TASK CONST partner, INT CONST code, DATASPACE VAR ds, INT VAR reply, result): disable stop; new msg; put msg (code); (* Sendung aufbauen *) put msg (ds); invite msg (int msg); (* Antwort vorbereiten *) invite msg (ds msg); call (partner, never, never, result); IF result = communication ok THEN get msg (reply); get msg (ds) FI END PROC send one receive one;
PROC receive (TASK CONST sender, INT CONST receive timeout, INT VAR result)
PROC send (TASK CONST partner, INT CONST send timeout, INT VAR result)
PROC wait (INT CONST receive timeout, TASK VAR sender, INT VAR result)