Ein bislang unbekanntes Betriebssystem macht Furore

Ohne Mach scheint heute die Zukunft von Unix undenkbar

23.02.1990

Nach dem US-Verteidigungsministerium und dem Apple-Gründer Steve Jobs setzt nun auch die Open Software Foundation (OSF) auf Mach als Basis ihres OSF/1. Was ist dran, an diesem "Unix für die Zukunft", und wie lösten die Software-Spezialisten an der Carnegie Mellon University (CMU) die Probleme, die der weiteren Verbreitung von Unix im Wege stehen?

Unix wurde vor rund zwanzig Jahren als Entwicklungssystem für Monoprozessoren konzipiert und unterlag seitdem einer ständigen Weiterentwicklung. Damit sind die Hauptursachen für die heutigen Schwierigkeiten bereits angedeutet: Am Anfang zeichnete sich Unix durch eine Reihe von Vorzügen aus, die seine Verbreitung förderten: Es gab nur zwei Typen von Betriebssystem-Objekten (Dateien und Prozesse), die durch wenige, einfache Operationen manipuliert wurden. Zudem war der Betriebssystem-Kern, der Kernel, mit ungefähr 100 Kilobyte relativ klein und einfach zu portieren.

Als Entwicklungssystem besitzt Unix jedoch ein Schutzkonzept, das dazu zwingt, alle Erweiterungen wie Streamstacks oder Semsphoren im Kernel zu realisieren. So wuchs dieser mit der Zeit auf eine Größe von rund 2 Megabyte an und wurde dabei immer unübersichtlicher Das erschwert die Portierung und den Nachweis von Sicherheitseigenschaften.

Benutzer-Prozesse statt Kernel-Code

Bei Mach dagegen wurden alle Funktionen aus dem Kernel entfernt, die nicht unmittelbar für den Betrieb externer Geräte, die Vergabe des Arbeitsspeichers, die Inter-Prozeß-Kommunikation oder die Prozeß-Verwaltung erforderlich sind. So wird das üblicherweise im Kernel implementierte Dateisystem-Management durch Benutzer-Prozesse realisiert. Der Mach-Kernel wird dadurch ähnIich klein, wie es der Unix-Kernel ursprünglich war. (An dieser Stelle verwende ich den bei Unix üblichen Begriff "Prozeß", obwohl diese Aufgabe bei Mach durch - noch zu erklärende - "Tasks" und "Threads" erledigt wird).

Die bei weitem größten Schwierigkeiten von Unix resultieren aus der Berücksichtigung spezieller Eigenschaften von Monoprozessoren: Echt parallel ablaufende Prozesse sind im Unix-Kernel nicht vorgesehen. Mehrprozessorsysteme können unter Unix nur mit erheblichem Änderungsaufwand effizient genutzt werden. Das in Unix realisierte Konzept "Prozeß" wurde für Monoprozessoren entwickelt. Dort führt die Verwendung mehrerer kooperierender Prozesse für eine Aufgabe zu zusätzlichen Prozeßwechseln, bei denen die Ausführungsumgebungen umgeladen werden müssen.

Viele Probleme lassen sich jedoch besser durch kooperierende, parallele Prozesse (zum Beispiel Server) modellieren. Um dabei zeitaufwendige Prozeßwechsel zu vermeiden, verwendet man oft Koroutinen, die in einer gemeinsamen Ausführungsumgebung ablaufen.

Dem Betriebssystem kann die interne Struktur eines Prozesses nicht bekannt sein (vergleiche Abbildung 1a). Eine von Koroutinen verursachte Unterbrechung führt daher zu einem Prozeßwechsel, obwohl andere möglicherweise hätten fortgesetzt werden können. Aus demselben Grund ist es mit Koroutinen nicht möglich, bei Multiprozessoren eine echt parallele Ausführung zu erreichen.

Mach unterstützt deshalb ein Koroutinen-Konzept auf Betriebssystem-Ebene. Dabei wird zwischen der Ausführungsumgebung ("Task") und den Aktivitätsträgern ("Threads"), die darin ablaufen, unterschieden (vergleiche Abbildung 1b).

Eine Task besteht aus einem vier Gigabyte großen virtuellen Adreßraum mit Seiten-Adressierung und einer Liste von Zugriffsrechten. Bei der Generierung neuer Tasks können virtuelle Speicherbereiche auf unterschiedliche Art "vererbt" werden: Entweder die generierte Task erhält gemeinsamen Zugriff (Read-write-sharing) oder sie erhält eine - logische - Kopie (Copy-on-write).

Koroutinen gehören zum Betriebssystem-Konzept

Tasks führen keine Aktionen aus. Vielmehr können innerhalb einer Task beliebig viele Threads ablaufen. Sie nutzen dabei gemeinsam die Ressourcen (zum Beispiel Speicherbereiche und Zugriffsrechte), die dieser zugeteilt sind. Da die Zustände der Threads durch die Registerinhalte sowie die Befehlszähler beschrieben werden, müssen nur diese bei einem Thread-Wechsel umgeladen werden.

Der Aufwand ist dabei um etwa den Faktor zehn kleiner als für einen Prozeßwechsel bei Unix. Ein ähnlicher Ansatz wurde bereits im V-Kernel realisiert. Sowohl auf der Ebene der Tasks als auch auf der der Threads findet ein Scheduling statt. Threads sind nur dann ausführbar, wenn ihre Task ausführbar (und geladen) ist.

Das Blockieren eines Threads geschieht in zwei Stufen (vergleiche Abbildung 2). Zunächst wird der Thread vom Zustand "running" in den Zustand "will suspend" versetzt. Dann ist er weiterhin ausführbar und kann durch Thread-resume wieder in den Zustand "running" gebracht werden. Erst nach einem zusätzlichen "thread wait" ist er im Zustand "suspended" blockiert. Dieses Kommando ist jedoch im Zustand "running" wirkungslos.

Das Scheduling von Tasks erfolgt entsprechend. Dadurch wird eine effiziente Koordinierung zwischen echt parallel laufenden Tasks möglich: Benötigt beispielsweise ein Thread einer Task A ein Zwischenergebnis einer Task B, so versetzt er die Task A frühzeitig in den Zustand "will suspend". Erst unmittelbar vor der ersten Operation mit dem Zwischenergebnis führt er ein Wait aus. In der Zwischenzeit kann jedoch die Task B, nach Bereitstellung der benötigten Information, ein Resume auf die Task A ausgeführt haben, so daß das Wait nicht zu einer Blockierung führt: Überflüssige Task-Wechsel werden vermieden.

Bei einem Betriebssystem, das - wie Unix - von autonom arbeitenden Prozessen ausgeht, ist eine Synchronisation zwischen diesen nur bei Zugriffen auf externe Geräte, insbesondere auf das Dateisystem, erforderlich und diese finden im Kernel statt. Daraus erklärt sich, daß in Unix auf effiziente Mechanismen zur Synchronisation von Benutzerprozessen verzichtet wurde.

In einem solchen System findet eine Zusammenarbeit von Prozessen vor allem dann statt, wenn die Ergebnisse des einen Prozesses an einen anderen weitergegeben werden sollen. Zu diesem Zweck enthält Unix das Konzept der Pipes (virtuelle Zwischendateien). Werden sie nicht zur Übertragung größerer Datenmengen genutzt, sondern zum Beispiel für einmaligen Nachrichten-Austausch, führt der Aufwand für die Einrichtung jedoch zu erheblichen Kosten pro übertragenem Zeichen. Bei einer Kommunikation über physische Zwischendateien tritt - wie auch bei Pipes zwischen nicht verwandten Prozessen - das Problem der Namensgebung auf. Zusätzlich sind Maßnahmen zur Flußkontrolle erforderlich. Wegen der internen Pufferung des Unix-Dateisystems können solche Dateien auch nicht für Synchronisationsnachrichten verwendet werden.

"Feinkörnig" zusammenarbeitende, parallele Prozesse benötigen flexible und schnelle Kommunikationsmöglichkeiten. Mach basiert daher auf dem Prinzip des "Message Passing". Messages bestehen aus einem Header fester Länge gefolgt von einer geordneten Menge typisierter Daten. Eine Message kann bis zu vier Gigabyte - also einen gesamten Adreßraum - auf einmal transportieren und sowohl synchron als auch asynchron gesendet beziehungsweise empfangen werden. Aus Effizienzgründen wird zwischen

einfachen Messages und solchen unterschieden, die Zeiger oder Zugriffsrechte auf Ports enthalten (dabei wird jeweils der Typ der Daten mitübertragen).

Ein Port ist eine - vom Kernel verwaltete - Warteschhlange, in die alle Tasks, die das Senderecht besitzen, Messages einreihen können. Genau eine Task hat dann das zum Entnehmen von Messages erforderliche Empfangsrecht.

Das Senderecht kann als "Capability" betrachtet werden: Nur wer es besitzt, kann Aufträge an den Port senden, der den betreffenden Dienstbringer verbirgt. Innerhalb der Task, die das Empfangsrecht besitzt, können jedoch mehrere Threads gleichzeitig versuchen, Nachrichten aus dem Port zu empfangen.

Bei der Generierung von Tasks wird ein spezieller Port eingerichtet (vergleiche Abbildung 3), für den die Task das Senderecht und der Kernel das Empfangsrecht erhalten. Operationen werden durch das Senden einer Message an diesen Port veranlaßt. Zusätzlich besitzt jede Task noch Empfangsrechte für einen Data-Port, über den sie der Kernel mit Aufruf-Parametern versorgt, und einen Notify-Port, über den ihr besondere Ereignisse mitgeteilt werden.

Durch das Port-Konzept werden folgende Probleme vermieden: Eine geeignete Vergabe der Senderechte ermöglicht es unkontrolliertes Senden zu verhindern. Terminiert eine Task sind die gegenseitigen Kommunikations-Abhängigkeiten bekannt, so daß der Kernel alle betroffenen Tasks informieren kann.

Muß ein Dienst, der von einer Task erbracht wird, auf eine andere Task verlagert werden, so geschieht dies - für die Auftraggeber transparent - durch Weitergabe der Empfangsrechte für den Port, der Aufträge für diesen Dienst entgegennimmt.

Wie fast alle neueren Betriebssysteme für Universalrechner verwaltet auch Mach den physischen Arbeitsspeicher als Cache für virtuelle Adreßräume. Diese sind oft nur dünn besetzt. Um dennoch den Aufwand möglichst gering zu halten, werden sie durch geordnete Listen variabler Länge, durch Address-maps realisiert, die Verweise auf sogenannte Speicherobjekte enthalten.

Ein Speicherobjekt ist eine Byte-weise ansprechbare Sammlung von Daten. Es kann ganz oder teilweise in die Adreßräume von Tasks abgebildet sein. Beispielsweise lassen sich Memory-mapped-files so realisieren. Jedem Speicherobjekt ist ein Pager für die Behandlung eventuell auftretender Seitenfehler zugeordnet. Dies kann eine vom Benutzer geschriebene Task oder der im Betriebssystem vorhandene Default-pager sein. In jedem Fall läuft er jedoch im Benutzermodus. Durch die Implementierung von Pagern mit speziellen Strategien können bestimmte Anwendungen beschleunigt werden.

Die virtuelle Speicherverwaltung ermöglicht es ebenfalls, beim Senden einer Message auf das physische Kopieren zu verzichten und diese stattdessen nur in den Adreßraum einer anderen Task abzubilden. Erst wenn eine der beteiligten Tasks schreibend auf das Speicherobjekt zugreift, wird eine physische Kopie erstellt (Copy-onwrite).

Ein wichtiges Entwurfsziel war, die Speicherverwaltung so weit wie möglich unabhängig von denen der Zielmaschinen zu machen. Um dies zu erreichen, besteht sie aus zwei Schichten, von denen nur die untere hardwareabhängig ist.

Mach: Von vornherein für Unix konzipiert

Fast alle Konzepte von Mach wurden in ähnlicher Form schon in anderen Betriebssystemen erprobt, die jedoch wegen ihrer Inkompatibilität mit Unix nie den Status vermarktbarer Produkte erreichten. Daher wurde Mach von vornherein so konzipiert, daß Unix-Schnittstellen - etwa nach dem X/Open-Kriterien - genutzt werden können. Die Folge: Vorhandene Unix-Programme laufen auch unter Mach.

Mach bildet dabei die Basis für Unix: Ein Unix-Prozeß entspricht einer Mach-Task mit nur einem Thread. Der Mach-Kernel stellt bereits einen großen Teil der Funktionalität eines Unix-Kernels zur Verfügung (vergleiche Abbildung 4). Die übrigen Unix-Funktionen können durch eine im Benutzermodus ablaufende "Unix-Compatibility-Task" realisiert werden.

Durch die unterschiedlichen Mechanismen zum Auslösen von Kernel-Operationen ist bei dieser Vorgehensweise jedoch der Zugang zu den Quellprogrammen erforderlich. Um dennoch Unix-Kompatibilität auf der Ebene der Lademodule zu erreichen, wird bei den jetzt vorliegenden Versionen von Mach die "Compatibility-Task" im Kernel-Modus ausgeführt. Schwierigkeiten bereitete in diesem Zusammenhang die - im Kontext der Mach Konzepte - ungenügend spezifizierte Semantik einiger Unix-Operationen, zum Beispiel die Behandlung von Signalen.

Ein "Unix für die Zukunft" ist Mach also nicht: Im Gegensatz zu diesem stellt es keine Benutzerumgebungen zur Verfügung. Die OSF plant daher, unter Mach 2.5, das bisher von Encore als "Unsupported Product" vertrieben wurde, die 4.3-BSD-Umgebung sowie das 4.4-BSD-Dateisystem zu verwenden

Die offene Struktur von Mach erlaubt darüber hinaus jedoch vielfältige Erweiterungen zum Beispiel die Integration dedizierter Dateisysteme. Zudem ist mit Mach - ausgehend von vorhandener Unix-Software - der schrittweise Übergang zu echt parallelen Programmen für Multiprozessoren und verteilte Systeme möglich.

Da bei Mach die Unterstützung von Netzwerken nicht im Kernel realisiert wird, ist es - im eigentlichen Sinne - kein verteiltes Betriebssystem. Eine Netzwerk-Erweiterung kann jedoch in einfacher, transparenter Weise integriert werden.

Auf jedem Knoten im Netz läuft dazu eine Task, die exklusiven Zugang zum Netzwerk hat, der Netz-Server (siehe Abbildung 4). Soll beispielsweise der Port 41 auf dem Knoten B für Tasks auf dem Knoten A erreichbar sein, so vergibt der Server auf B einen eindeutigen Network-port-identifier. Der Netz-Server auf A richtet für diesen einen (lokalen) Port 41 ein, für den er das Empfangsrecht besitzt. Er verschlüsselt die daraus empfangenen Nachrichten und schickt sie an den Server auf B. Dieser gibt sie entschlüsselt an den Port 41 weiter.

Im Gegensatz zum Unix-Kernel ist Mach einer Überprüfung von Sicherheitsfragen wesentlich besser zugänglich: Die meisten Systemdienste sind außerhalb des Kernels realisiert und können daher separat geprüft werden. Und durch die geringe Größe wird eine Überprüfung des Kernels wesentlich vereinfacht.

Im Auftrag der "Defense Advanced Research Project Agency" wurde die Eignung von Mach als Grundlage für ein Sicherheits-Betriebssystem untersucht. Dabei wurde festgestellt daß die Kontrolle des Zugriffs zu Objekten sowie der Weitergabe von Rechten für jeden Benutzer und jedes Objekt mit relativ

geringem Aufwand gewährleistet werden kann. Zu diesem Zweck müssen alle Objekte mit zwei zur Festlegung einer Sicherheitsstrategie erforderlichen Attributen versehen werden.

Die entsprechenden Änderungen werden zur Zeit in Zusammenarbeit mit der Carnegie Mellon-Universität durchgeführt. Sie bestehen unter anderem darin, den Unix-Code, dessen Sicherheitseigenschaften nicht überprüft werden können, aus dem Kernel in eine separate Task zu verlagern. Nach Abschluß dieser Änderungen könnte Mach die Anforderungen der Sicherheitsstufe B3 (entsprechend dem "Orange Book") erfüllen. +

*Gerhard Heinzel beendet derzeit in Erlangen sein Informatikstudium. Einer seiner Schwerpunkte sind Betriebssysteme.

Literatur zum Mach-Betriebssystem:

Accetta, M. und andere: "Mach, A New Kernel Foundation for Unix Development", in: Proc. of Summer Usenix, July 1986

Rashid, R. F.: "Threads of a new system", in: Unix Review 8/86, S. 37-49 Rashid, R. F. und andere: "From RIG to Accent to Mach: The Evolution of a Network Operating System", in: Proc. of the 1986 Fall Joint Computer Conference, Nov. 1986

Rashid, R. F. und andere: "Machine-Independent Virtual Memory Management for Paged Uniprocessors and Multiprocessor Architectures", in: Proc. of the 2nd Symposium on Architectural Support for Programming Languages and Operating Systems, ACM, Oct. 1987, S. 64-75

Tevannian, A. und andere: "Mach Threads and the Unix Kernel: The Battle for Control", CMU Technical Report, Pittsburgh, 1987

Young, M. und andere: "The Duality of Memory and Communication in the Implementation of a Multiprocessor Operating System", in: Proc. 11th Symposium on Operating System Principles, ACM, Nov. 1987, S. 63 - 76

Keine Technik ohne neue Begriffe

Auch Mach zwingt zum Vokabelnpauken

Aktivitätsträger: Ein Prozeß, bei dem von der Ausführungsumgebung abstrahiert wird. Der Zustand eines A. kann durch den Befehlszähler und die Registerinhalte beschrieben werden.

Ausführungsumgebung: Umgebung, das heißt Adreßraum, Liste der geöffneten Dateien etc. eines oder mehrerer Aktivitätsträger.

Copy-on-write: Technik, bei der zwei Prozesse so lange auf einem gemeinsamen Datenbereich arbeiten, bis einer der beiden schreibend darauf zugreift. Erst dann wird eine Kopie erstellt.

Koroutinen: Innerhalb eines vom Betriebssystem verwalteten Prozesses laufen mehrere Aktivitäten quasiparallel ab. Für jeden Aktivitätsträger existiert ein eigener Stack und innerhalb des Prozesses findet ein zusätzliches Scheduling dieser Aktivitätsträger statt. Dabei wird ein passiver Aktivitätsträger durch einen gerade aktiven "geweckt", indem dieser seinen Befehlszähler-Stand sichert und ihn dann mit dem Stand des anderen Aktivitätsträgers initialisiert.

Message (Mach): "Datagramm", mit dem bis zu 4 Gigabyte auf einmal übertragen werden können. Sofern typisiert, ist das Versenden von Zeigern und Port-Zugriffsrechten möglich.

Port (Mach): Vom Mach-Kernel unterhaltene Warteschlange für Messages. Senden und Empfangen setzt den Besitz spezieller Rechte voraus.

Task (Mach): Ein 4 Gigabyte großer, virtueller Adreßraum, in dem Threads ablaufen. Ressourcen werden den Tasks zugewiesen.

Thread (Mach): Englisch "Handlungsfaden", "Ablauf". Threads führen innerhalb einer Task Aktionen aus und nutzen dabei gemeinsam deren Adreßraum und Ressourcen. Bei Multiprozessoren ist echt parallele Ausführung möglich.

Verwandt: Prozesse (beziehungsweise Tasks) werden als verwandt betrachtet, wenn einer mittelbar oder unmittelbar vom anderen erzeugt wurde.