Als Basis der vorliegenden Arbeit dient das an der Gesellschaft für
Mathematik und Datenverarbeitung (GMD) entwickelte Betriebssystem
L3. Da die vorliegende Arbeit auf den verschiedensten Konzepten des
Systems aufbaut, sollen hier die wichtigsten geschildert werden. Dazu
gehören insbesondere das Taskkonzept, die Behandlung von
Datenräumen und die Interprozeßkommunikation. 4.1 Überblick über die Struktur von L3
L3 ist ein mikrokernbasiertes Betriebssystem. Es besteht aus einem inneren Kern, der elementare Operationen bereitstellt und einer Menge von Servern, die auf dem Kern aufbauend die fehlenden betriebssystemspezifischen Dienste zur Verfügung stellen.
In Abbildung [hier] ist die Struktur eines L3-Systems dargestellt. Zu sehen ist der Kern mit einer Menge auf ihn aufsetzender Server. Eine besondere Rolle spielt dabei der Task-Server, der sogenannte Supervisor. Er stellt einen privilegierten, für den Kern vertrauenswürdigen Prozeß dar. Er ist mit der Verwaltung des Tasksystems betraut und wird auch als äußerer Kern bezeichnet.
Auf diesen Servern setzen dann die Anwendungen auf, die dem Nutzer
unter anderem eine Oberfläche bieten, mit deren Hilfe er mit dem
System arbeiten kann. Diese Anwendungen sind für das Verständnis der
weiteren Ausführungen nicht notwendig und werden deshalb nicht näher
betrachtet. 4.1.1 Der Kern
Der Kern wurde nach dem Minimalitätsprinzip entworfen. Dabei stellt sich die Frage, was ist minimal? Welche Funktionen gehören in den Kern, welche können außerhalb implementiert werden? In [16] werden folgende Kriterien für den Entwurf definiert:
Resultat war ein Kern, der als abstrakte Maschine den Datentyp Task implementiert [17]. Eine Task besteht aus:
Der vom Kern bereitgestellte virtuelle Adreßraum reicht von Adresse 0 bis 3,5 Gigabyte. Die übrigen 0,5 Gigabyte des 32-bit Adreßraums werden vom Kern verwendet. Der Adreßraum wird durch das Mappen von Datenräumen mit Inhalt gefüllt.
Datenräume sind Objekte, die in den virtuellen Adreßraum gemappt werden können. Sie sind bis zu einem Gigabyte groß und unterliegen dem Paging.
Leichtgewichtige Prozesse, die sich einen gemeinsamen Adreßraum teilen, werden als Threads bezeichnet. Threads sind Kernobjekte, die eine zeiteindeutige 64 Bit breite Id besitzen. Sie sind die aktiven Elemente des Systems und bis auf residente Threads auch persistent. Resident bedeutet hier, daß ein Thread im Gegensatz zu einem persistenten Thread das Ausschalten des Rechners nicht überlebt und nicht dem Paging unterliegt. In diese Kategorie fallen Gerätetreiber und die Kernelthreads, die in der Regel Gerätetreiber sind. Residente Threads werden beim Hochfahren des Systems vom Kern (die Kernelthreads) oder von einer Nutzer-Task etabliert.
Die aktuelle Implementation stellt pro Station maximal 16382 Threads zur Verfügung.
Tasks werden mit Hilfe einer 64 Bit breiten Id identifiziert und stellen die protection domain in L3 dar. Auf welchen Grundlagen dieser Schutz basiert, wird im folgenden Kapitel beschrieben.
Aufbauend auf dem Konzept der Task stellt der Kern Clans zur Verfügung, auf
die in Kapitel [hier] eingegangen wird. Sicherheit in L3
Die im ersten Punkt der Kriterien geforderte Unterstützung einer Sicherheitsarchitektur erfolgt durch folgende Eigenschaften:
Die einzige Möglichkeit, Einfluß auf das Verhalten einer Task zu nehmen, stellt die Interprozeßkommunikation dar. Hier erklärt sich die Task bzw. ein Thread der Task bereit, etwas zu empfangen, entscheidet dabei aber darüber, was er zu empfangen bereit ist und wie er bei Erhalt der Nachricht reagiert.
Eine Kommunikation verletzt also nicht die Autonomie der Task, da ihr auch dadurch nichts aufgezwungen werden kann. Die Task entscheidet immer selbst, was sie tut.
Auf der Grundlage dieser beiden Prinzipien ist man in der Lage, eine
Sicherheitsarchitektur zu entwerfen, die den Anforderungen der
Anwendung genügt.
Das Resourcenmanagement in L3
Der L3-Kern realisiert eine elementare Resourcenverwaltung. Er verwaltet lediglich:
Alle anderen Resourcen, wie Drucker, Terminals oder andere
Ein-/Ausgabegeräte werden von Tasks verwaltet.
Error recovery und Fehlerbehandlungsmechanismen
Error Recovery auf Kernniveau wird in Form des Fixpunktmechanismus angeboten. Ein Fixpunkt ist eine konsistente Kopie aller Task- und Threadkontrollblöcke, sowie aller Datenräume der Tasks zu einem bestimmten Zeitpunkt. Stürzt das System aufgrund eines Hard- oder Softwarefehlers ab, ist es möglich, den Systemzustand zum Zeitpunkt des letzten Fixpunktes wiederherzustellen. Das geschieht automatisch beim ,,Hochfahren'' des Systems.
Programmfehler, wie Zugriffe auf nicht gemappte Bereiche u.a.m.,
werden der Task über einen Standardmechanismus zugestellt, der in
einem späteren Kapitel noch beschrieben wird.
Operationen des Kerns
Für den Datentyp Task und die zugehörigen Objekte werden
Operationen bereitgestellt, die sich nach [18] wie
in Abbildung [hier] darstellen lassen. In dieser
Abbildung wird auch versucht, eine Einteilung in Funktionskomplexe
vorzunehmen. Das ist aufgrund der Überschneidungen nicht eindeutig
möglich, bietet aber einen Überblick über die bereitgestellte
Funktionalität. Für eine Beschreibung der Funktionen und ihrer
Anbindung an die Programmiersprache C sei die folgenden Abschnitte und
auf [19] verwiesen.
4.1.2 Der Supervisor und die Server
Aufbauend auf den Funktionen des Kerns stellen der Supervisor und die Server die fehlende betriebssystemspezifische Funktionalität bereit. In Form von Tasks werden Funktionen zur Behandlung der Systemkonfiguration, zur Arbeit mit Geräten, der Manipulation von Tasks u.a.m. realisiert. Einige besondere Server seien in den folgenden Ausführungen kurz beschrieben.
Der Supervisor ist die erste Usertask. Er wird bei der Installation erzeugt und lebt dann ewig, d.h. bis zu einer Neuinstallation oder einem Austausch gegen einen anderen Supervisor.
Er dient weiterhin als Nameserver im System. Threads und Tasks haben in L3 neben ihrer orts- und zeiteindeutigen Id auch einen Namen. Will ein Thread mit einem anderen kommunizieren, wendet er sich mit dem Namen an den Supervisor, um die Id seines Partners herauszufinden.
Die Anordnung dieser privilegierten Tasks in der Taskhierarchie ist kein vom Kern vorgegebenes Dogma, sondern ein Prinzip des Supervisors, der privilegierte und residente Tasks nur unterhalb von SYS erzeugt. Wird versucht, eine solche Task an einer anderen Stelle zu erzeugen, wird der Dienst vom Supervisor abgelehnt.
Will eine Task das erstemal eine Ausgabe auf ein Terminal machen, wickelt sie mit SYSIO das Linkprotokoll ab und erhält nach erfolgreichem Abschluß Zugriff auf die Task, die das Terminal behandelt.
Für eine genauere Beschreibung der Taskhierarchie und der durch sie
bereitgestellten Funktionalität sei auf [20]
verwiesen.
4.2 Interprozeßkommunikation
Tasks nehmen in der Regel zur Erfüllung einer Aufgabe Dienste anderer Tasks in Anspruch. Sie benötigen dazu eine Möglichkeit zur Kommunikation.
IPC in L3 ist synchron, direkt und mit Timeouts behaftet. Aktive Elemente, Threads, kommunizieren direkt mit anderen Threads. Es gibt keine Kanäle oder Ports, nur die globalen, zeit- und ortseindeutigen Thread-Id's.
Da es keine zwischengeschalteten Objekte gibt, werden Nachrichten auch nicht gepuffert. Sendet ein Thread eine Nachricht an einen anderen, wird er solange blockiert, bis der Empfänger bereit ist und die Nachricht in Empfang nimmt.
Nachrichten sind strukturiert. Sie werden durch sogenannte message dopes beschrieben und können aus bis zu vier Komponenten bestehen. Diese Komponenten sind:
Direct strings bestehen aus Bytes, die direkt in der Nachrichtenstruktur stehen. Sie müssen mindestens 8 Byte lang sein und könne aus bis zu 1032 Bytes bestehen.
Indirect strings beschreiben zwei Speicherbereiche jeweils durch Adresse und Länge. Der erste enthält die zu sendenden bzw. die empfangenen Informationen, der andere beschreibt einen Puffer für zu empfangende Daten.
Flex pages gestatten das Senden von Seiten des eigenen
Adreßraums. Im Adreßraum des Empfängers wird ein temporäres Mapping
etabliert, das dem Empfänger den Zugriff auf die Seiten
gestattet. Dieses Mapping kann jederzeit vom Kern oder vom Sender
ungültig gemacht werden. Die Seiten werden während der Gültigkeit des
Mappings zwischen Sender und Empfänger geshared. Flex pages sind
Datenräume werden faul kopiert, d. h., es wird lediglich ein Eintrag in der Verwaltungsstruktur geändert. Sie gehören nach einem erfolgreichen Abschluß der Kommunikation der Empfängertask.
Abbildung [hier] stellt den Aufbau einer Nachricht dar.
Ein großer Teil der Kommunikation zwischen Klient und Server besteht aus kurzen Nachrichten, die nur eine Statusinformation oder einen Ergebniswert enthalten. Deshalb wurde in L3 eine spezielle Optimierung für kurze Nachrichten implementiert. Nachrichten, die bis zu 8 Byte lang sind, werden in Registern übertragen, was zu einem beträchtlichen Geschwindigkeitszuwachs führt. Aber auch komplexere Nachrichten werden in L3 schneller übertragen als in anderen System wie z.B. Mach [7].
L3-IPC stellt folgende Primitive zur Verfügung:
Senden einer Nachricht Message an einen Thread Destination Thread, Abbruch nach Timeout Millisekunden
Empfangen einer Nachricht von einem Thread Source Thread, Abbruch nach Timeout Millisekunden. Die empfangene Nachricht befindet sich bei erfolgreichem Empfang im Message Buffer
Entspricht Receive, aber es werden Nachrichten von einem beliebigen Thread in Empfang genommen. Die Id des Senders steht nach erfolgreichem Aufruf in Source Thread.
Dieser Ruf realisiert (bei Angabe eines Timeouts Never) den klassischen RPC. Es wird ein Auftrag (Message) an einen Server Thread gesendet und auf eine Antwort gewartet. Nach erfolgreicher Kommunikation befindet sich die Antwort im Message Buffer. Nach Send Timeout wird das Senden, nach Receive Timeout wird das Empfangen abgebrochen.
Dieser Ruf implementiert den klassischen Ablauf bei der Kommunikation eines Servers. Der Server schickt die Antwort auf einen Auftrag an den Client Thread und wartet dann auf einen Auftrag von irgend einem neuen Klienten. Nach erfolgreicher Kommunikation befinden sich die Informationen über den Klienten und seinen Auftrag in New Client Thread und Message Buffer.
Datenräume sind Objekte, die in den virtuellen Adreßraum gemappt werden können. Sie sind bis zu einem GByte groß und unterliegen dem Paging.
In einem Datenraum können sich Daten beliebigen Typs befinden. Datenräume werden dynamisch in den Adreßraum gemappt und sind dann linear adressierbar. Es ist daher möglich, Programmkode, der sich in einem Datenraum befindet, direkt auszuführen.
Datenräume sind tasklokale Objekte. Sie werden innerhalb der Task erzeugt oder mittels IPC von einer anderen Task empfangen. Wird ein Datenraum gesendet, geht das Eigentumsrecht an die Empfängertask über.
Die Standarddatenraumverwaltung (der Standardpager) stellt den Mechanismus des copy on write zur Verfügung, d.h., beim Kopieren eines Datenraumes wird nur eine Referenz auf das Original erzeugt. Datenräume werden in L3 über baumartige Strukturen verwaltet, die eine doppelstufige Pagetablehierarchie darstellen. Die erste Stufe realisiert die Eigentumsrelation zwischen Task und Datenraum, die zweite ordnet den Datenräumen Blöcke auf der Platte zu. Das Kopieren von Datenräumen erfolgt durch das Erzeugen eines Eintrages für den Datenraum, der auf den gleichen Eintrag in der zweiten Stufe verweist wie das Original. In einem zweiten Schritt werden alle Seiten des Datenraums als read only und shared gekennzeichnet. Erst wenn eine Veränderung in dem alten oder in dem neuen Datenraum vorgenommen wird, wird eine physische Kopie der veränderten Seiten erzeugt.
Mappen eines Datenraums eines externen Pagers
L3 stellt wie Mach auch das Konzept der externen Pager zur Verfügung [21]. Als externer Pager kann jede L3-Task fungieren, die dann den von ihr zur Verfügung gestellten Datenräumen eine Semantik aufprägt.
Eine Task mappt einen Datenraum unter Angabe verschiedener Parameter. Diese beschreiben:
In Abbildung [hier] ist die Situation nach dem mappen eines Datenraums eines externen Pagers dargestellt.
Greift ein Thread nun auf diesen Bereich zu, entstehen wie bei einem normalen Datenraum Seitenfehler. Diese werden vom Kern analysiert (1) und mittels des Externen Pager Protokolls an den implementierenden Pager weitergeleitet. Dieser stellt die entsprechenden Informationen auf Seiten seines eigenen Adreßraums bereit und sendet eine Antwort, die diese Seiten in Form von Flexpages enthält. Die in der Antwort enthaltenen Seiten werden vom Kern beim Empfang der Nachricht gemappt. Dann wird die verursachende Anweisung neu aufgesetzt.
Ein wichtiger Aspekt bei der Implementation von Anwendungen ist die Durchsetzung einer auf die Anwendung zugeschnittenen Sicherheitspolitik. Einen ersten Ansatz hierfür bieten die L3-Konzepte Autonomie der Task und Integrität der Nachrichten.
Der L3-Kern bietet noch einen darüber hinausgehenden Mechanismus an. Angeregt vom subject restriction concept in Birlix [22] wurde das Konzept der Clans & Chiefs realisiert.
In Birlix ist es möglich, verdächtige Prozesse einzukapseln. Ihnen wird eine Liste aller von ihnen kontaktierten Prozesse zugeordnet, die in der Regel Server sind. Die Kommunikation der Prozesse wird dann anhand dieser Liste überwacht. Versucht ein Prozeß mit einem anderen zu kommunizieren, der nicht in der Liste steht, wird diese Kommunikation unterdrückt.
L3 realisiert hier ein allgemeineres Konzept. In Anlehnung an die schottischen Familienclans können Task zu Clans zusammengefaßt werden, die einen Chief besitzen.
Ein Clan ist eine Menge von Tasks, die von einem Chief kontrolliert werden. Innerhalb des Clans wird IPC zwischen Threads normal behandelt. Nachrichten, die jedoch die Grenzen des Clans überschreiten, werden über den Chief umgeleitet. Überschreiten heißt dabei sowohl Überschreiten der Grenze aus dem Clan heraus als auch in den Clan hinein.
Kommunikation über Clan-Grenzen hinweg
Eine solche Kommunikation könnte dann wie in Abbildung [hier] aussehen. Sendet eine Task R an eine Task S, die sich in einem anderen Clan befindet, kreuzt die Nachricht zwei Clan-Grenzen. Die Kommunikation läuft dann wie folgt ab:
Der Chief kann dann die Nachricht in Bezug auf den Sender, den Empfänger, aber auch den Inhalt analysieren. Entsprechend der dabei gewonnenen Kentnisse hat er die Möglichkeit:
Dabei stehen ihm mehrere Möglichkeiten zur Verfügung:
Richtungserhaltend bedeuted: Sendet der Chief eine Nachricht, so muß gelten:
Das bedeutet für den Chief:
Der Chief kann bei den gegebenen Einschränkungen jeden beliebigen Algorithmus zur Kontrolle der Kommunikation seines Clans verwenden. Damit ist es möglich, eine auf die spezielle Anwendung zugeschnittene Sicherheitspolitik auf User-Level zu implementieren. Dann fungieren Clans als Schutzdomänen auf einem höheren Niveau.
In [23] sind weiterführende Ausführungen zu diesem Thema zu finden. Dort wird unter anderem auch erläutert, wie das Verhältnis von Vertrauenswürdigkeit und Integrität des Nachrichtenaustausches auf der einen und die Möglichkeit, Nachrichten zu fälschen, auf der anderen Seite zu bewerten ist. Weiterhin werden Möglichkeiten aufgezeigt, trotz der Fälschungsmöglichkeiten Informationen über die Vertrauenswürdigkeit des Absenders zu erhalten.