In diesem Abschnitt werden die Mechanismen vorgestellt, mit denen Emerald viele der angesprochenen Probleme umgeht oder meistert. Einige schwerwiegende Probleme, die sich aus der Mobilität ergeben, werden erst bei dieser Diskussion aufgedeckt.
Das Emerald Betriebssystem ist ein einzelner Unix-Prozeß, der parallel zu anderen Unix-Anwendungen auf einem Rechner laufen kann. Auf jedem Rechner des Netzwerkes muß ein solches Betriebssystem von Hand gestartet werden. Das Betriebssystem verwaltet lokal alle Emerald-Objekte, führt Kontextwechsel zwischen Emerald-Prozessen aus und kümmert sich beim Aufruf entfernter Objekte um die explizite Lokation und den Datenaustausch. Das Aufsetzen auf dem Unix-System bringt natürlich einige Vorteile für die Implementation des Betriebssystems, das hardwarenahe Aufgaben wie I/O nicht selbst zu definierten braucht.
Innerhalb eines Rechners teilen sich Emerald-Objekte einen Adreßraum, der vom Betriebssystem dynamisch verwaltet wird. Theoretisch sind Sicherheitsprobleme zu befürchten, doch garantiert die Sprache mit dem objektorientierten Konzept und Abfragen zur Laufzeit, daß keine ungültigen Zugriffe auftreten können. Dieses Teilen des Adreßraumes trägt auch erheblich zur Performancesteigerung bei, da Objekte innerhalb eines Rechners direkte Lesezugriffe und Prozeduraufrufe in andere Objekte tätigen können. Objekte in eigenen Adreßräumen wären besser geschützt, doch Datenaustausch müßte aufwendig über shared Memory und messages [12] erfolgen.
Generell sind der Emerald-Compiler und das Betriebssystem eng gekoppelt. In vielen Fällen wird der Compiler Code erzeugen, der direkt auf Strukturen des Betriebssystems zugreift, was wiederum ("it's not a bug, it's a feature") aufgrund der Konzeption der Sprache keine Sicherheitslücke ist, sondern vielmehr hilft, die Performance zu verbessern. Weiterhin erzeugt der Compiler ausführliche Tabellen, die das Betriebssystem zur Vereinfachung und Ermöglichung von Mobilität benötigt.
Der Emerald-Compiler erzeugt direkt ausführbare Maschinenbefehle, die vom Betriebssystem in den Speicher geladen, und dort direkt von der Hardware des Rechners ausgeführt wird.
Alle Objekte in Emerald werden mit dem gleichen Mechanismus eines Objektkonstruktors erzeugt, allerdings sind die erzeugten Maschinenbefehle nicht immer gleich. Obwohl Emerald netzwerkunabhängige Zugriffsmechanismen anbietet, wird diese Eigenschaft nur von einer relativ kleinen Zahl von Objekten benötigt. Innerhalb des Emerald-Betriebssystems gibt es vier Modelle verschiedenen Aufwandes und Komplexität, und zur Übersetzungszeit wählt der Compiler aus, welches Modell im jeweiligen Fall ausreicht.
Grafik 3 zeigt die interne Repräsentation von Objekten. Objekt X ist global; der Wert in X ist ein Zeiger auf einen internen Object Descriptor. Auf einem Rechner existiert eine Tabelle mit allen Deskriptoren aller globalen Objekte, die dem Rechner bekannt sind. An den Flags am Anfang des Deskriptors erkennt das System, daß es sich um den Deskriptor eines globalen Objektes handelt. In den Flags ist u.a. vermerkt, ob sich das Objekt auf dem lokalen Rechner oder auf einem entfernten Rechner befindet. Im ersten Falle, wie er in der Grafik dargestellt ist, enthält der data pointer einen Zeiger auf das Objekt im lokalen Adreßraum, der Zugriff kann also lokal abgewickelt werden. Ist das Objekt nicht lokal, so wendet sich das System mit der eindeutigen OID an das Betriebssystem des in location angegebenen Rechner, um einen entfernten Prozedurauf auszuführen.
Y ist ein lokales Objekt, die Adresse in Y zeigt direkt auf das Objekt selber. Anhand der Flags erkennt das System die Lokalität und führt inline-Aufrufe aus. Objekt Z ist ein direktes Objekt wie z.B. eine einfache Ganzzahl; Z enthält keine Adresse auf die Daten sondern die Daten selber.
Insgesamt muß erst bei dem Aufruf eines globalen Objektes das Emerald-Betriebssystem um Hilfe gebeten werden. Der Compiler erzeugt einen Test, der in den Flags des Objektheaders abfragt, ob das Objekt lokal ist. Ist der Test erfolgreich, so wird der Aufruf unter Umgehung des Systems ausgeführt. In vielen Fällen, wie z.B. bei unveränderlichen oder lokalen Objekten kann der Compiler auch entscheiden, daß das Zielobjekt immer lokal ist, und kann daher den Test wegfallen lassen.
Beim Zugriff auf unveränderliche Objekte fügt der Compiler weiterhin einen Test ein, ob bereits eine lokale Kopie existiert. Schlägt der Test fehl, so wird das Betriebssystem angewiesen, eine Kopie des Objektes zu finden und eine neue lokale Kopie zu erzeugen.
Mobilität schafft eine neue Dimension von Problemen. Vormals lokale Zugriffe müssen auf einmal über Rechnergrenzen hinweg ausgeführt werden, sowohl in das Objekt hinein als auch aus dem Objekt heraus. Ein schwerwiegendes Problem sind Prozesse. Falls das sich bewegende Objekt einen Prozeß besitzt, so muß dieser eingefroren und auf dem neuen Rechner an der gleichen Stelle fortgesetzt werden. Gleiches gilt für externe Prozeduraufrufe, die in dem Objekt momentan aktiv sind. Dadurch wird es möglich, daß ein ehemals lokaler Prozeduraufruf plötzlich eine entfernte Rückkehr aus der Prozedur erfordert. Auf dem Stack und in den Prozessorregestern befinden sich Referenzen, sprich explizite Hardwareadressen, auf ehemals lokale Objekte, die auf einmal nicht mehr lokal sind. Weiterhin, da sich Emerald- Objekte einen Adreßraum teilen, wird das Objekt auf dem Zielrechner eine andere Hardwareadresse erhalten wie zuvor. Das Objekt muß daher reloziert werden, d.h. alle Sprungadressen, auch die innerhalb eines Objektes, müssen angepaßt werden.
Insgesamt kann die Bewegung eines Objektes sehr aufwendig sein. Die sich ergebenden Probleme werden von Emerald hingenommen. Compilergenerierte Tabellen, die das Layout eines Objektes beschreiben, helfen dem Betriebssystem, ein Objekt zu relozieren. Der Stack und Prozessorregister werden nach Objektreferenzen abgesucht; und Referenzen auf ehemals lokale Objekte werden durch Referenzen auf die entsprechenden Objektdeskriptoren des neuen Systems ersetzt. Analog können natürlich auch ehemals entfernte und nun lokale Referenzen durch direkte Adressen ersetzt werden.
Bei der Bewegung von Objekten werden in den Tabellen des Quellrechners und des Zielrechners automatisch und sofort die entsprechenden neuen Zustände eingetragen, d.h. Flags und Pointer werden gemäß Grafik 3 umgehängt. Drittobjekte, die an der Bewegung nicht beteiligt waren, können sich auf diese Weise sofort auf die neue Situation umstellen.
Obwohl für die Erzeugung eines Objektes ein individueller Objektkonstruktor ausgeführt wird, teilen sich dennoch Objekte vom gleichen Typ den gleichen Maschinencode. Da sich der Code nicht zur Laufzeit ändern kann, wird er vom System wie ein unveränderliches Objekt behandelt. Falls ein Objekt bewegt wird, so werden zunächst nur die Daten des Objektes an den Zielrechner übertragen. Der Zielrechner stellt fest, ob bereits eine lokale Kopie des Programmcodes existiert; falls nicht, so wird eine Kopie vom Quellrechner angefordert. Prinzipiell könnte der Programmcode auch sofort mit den Daten mitgeschickt werden, allerdings kann angenommen werden, daß der Code öfter bereits auf dem Zielsystem vorhanden ist als nicht. Dieses Prinzip der lazy evaluation spart im Durchschnittsfall also Zeit.
Der Aufenthaltsort von Objekten ist in Emerald nicht immer bekannt. Globale Objekte können zu jedem Zeitpunkt auf einen anderen Rechner migrieren. Da das Objekt selber keine Kenntnis davon hat, wie viele und welche anderen Objekte es referenzieren, ist es nicht möglich, diese Referenzen stets auf dem neuesten Stand zu halten. Auch wenn diese Information bekannt wäre, würde der Prozeß des Erneuerns größtenteils redundant sein; das Objekt kann sich schließlich mehrfach bewegen, ohne daß ständig alle Referenzen benötigt werden.
Emerald befürwortet ein System mit weitergeleiteten Adressen (forwarding addresses). Beim Aufruf eines globalen, nicht residenten Objektes wird die location Information im Objektdeskriptor (siehe Grafik 3) ausgewertet. Bei diesem Rechner wird angefragt, ob das Objekt mit der bekannten OID dort vorhanden ist. Im Erfolgsfall ist damit alles erledigt. Falls sich das Objekt jedoch weiterbewegt hat, so befragt das Zielsystem seine eigene Datentabelle, und schickt die Anfrage an den dort notierten Rechner weiter. Irgendwann wird das Objekt gefunden. Parallel zum eigentlichen Prozeduraufruf wird eine Meldung an den aufrufenden Rechner zurückgeschickt, die die aktuelle Lokation des Objektes enthält. Diese Meldung läuft auf dem gleichen Pfad wie der ausgesandte Aufruf, so daß alle Rechner in der Kette ihre Information auffrischen können. Zukünftige Aufrufe können nun direkt, ohne den Umweg über andere Rechner, ausgeführt werden.
Im Gegensatz zum traditionellen Verständnis eines Compilers, der eine strikte Umsetzung von Programmtext in Maschinencode vornimmt, ist der Compiler des Emerald-Systems völlig anders konstruiert. Der Programmtext wird global auf seine Zusammenhänge hin analysiert, und auf der Basis dieser Informationen werden jeweils optimale Zugriffsstrategien erzeugt. Das Betriebssystem verläßt sich völlig auf den Compiler. Das Betriebssystem erhält vom Compiler Daten, die zur Behandlung von Objekten nötig sind und gestattet dem erzeugten Maschinencode zwecks besserer Performance die Manipulation von systeminternen Strukturen. Weiterhin ist der Compiler verpflichtet, an geeigneten Stellen Sprünge ins Betriebssystem vorzunehmen, um z.B. einen Kontextwechsel auszulösen.
An vielen Stellen des Designs fließen performance-orientierte Überlegungen ein, wie z.B. die Entscheidung, kleine Parameter vorausschauend, meist auch ohne explizite Anweisung des call-by-move vom Programmierer, huckepack mit dem Prozeduraufruf mitzuschicken. Ein gutes Beispiel für die Philosophie bei der Entwicklung von Emerald, den Regelfall auch dann zu optimieren, wenn der Spezialfall aufwendiger wird, ist die Entscheidung, daß sich alle Objekte auf einem Rechner den gleichen Adreßraum teilen. Zwar wird das Bewegen von Objekten erheblich kompliziert (bei eigenen Adreßräumen wäre eine Relokation, d.h. das Umrechnen von Sprungadressen, nicht notwendig), doch bei lokalen Aufrufen wird viel Zeit gespart. Daß lokale Prozeduraufrufe um Größenordnungen häufiger sind als Objektbewegungen rechtfertigt den zusätzlichen Aufwand.
Die Entwickler von Emerald können die Effizienz der Sprache auch eindrucksvoll bestätigen. In lokalen Umgebungen wird fast die gleiche Performance wie von einer prozeduralen Sprache erreicht, und eine deutlich höhere Performance als vergleichbare objektorientierte Systeme wie Smalltalk. In verteilten Umgebungen existieren noch nicht allzuviele Vergleichsmöglichkeiten. Die Migrationszeiten und entfernte Aufrufzeiten von EPL werden allerdings deutlich unterboten. Timing-Analysen zeigen, daß bei einem entfernten Prozeduraufruf nur 12% der Zeit auf durch Verteilung entstehenden Overhead zurückzuführen sind, 88% der Zeit wird allein für den Austausch von Datenpaketen benötigt [2].
[Inhaltsverzeichnis] [Voriges Kapitel] [Nächstes Kapitel]
Frank Pilhofer <fp -AT- fpx.de> Back to the Homepage Last modified: Fri Mar 31 18:41:12 1995