Next: Hierarchien
Up: Organisation und Aufbau von
Previous: Einführung
Spätestens mit der Publizierung von Dijkstra über
``The Structure of `THE'-Multiprogramming System'' im Jahre 1968
wurde eine Strukturierung eines Systems in Schichten
entsprechend der Abhängigkeiten populär
(siehe Abbildung 3.1).
Er zerlegte sein Betriebssystem in fünf Schichten, bei
der jede Ebene auf dem Weg von unten nach oben
bestimmte Hardware-Ressourcen in abstrakter und
virtueller Form zur Verfügung stellte.
Bei dieser Strukturierung war sichergestellt,
daß jede Ebene nur Aufrufe an sich selbst und die
untergeordneten Ebenen absetzt.
Damit eröffnet sich nicht nur eine leichtere Verstehbarkeit
eines solchen Systems - die einzelnen Ebenen lassen sich
auch leichter testen oder austauschen.
Die gleiche Motivation führte auch zu den Schichten aus
dem ISO-Referenz-Modell für Netzwerk-Software
(siehe Abbildung 3.2).
Abbildung 3.2:
ISO-Referenz-Modell
Abbildung 3.1:
THE-System von Dijkstra
|
Genauso wie bei dem THE-System von Dijkstra
sind mit den einzelnen
Schichten Schnittstellen verbunden, und jede Schicht implementiert
ein Protokoll mit seiner eigenen Ebene auf der Seite des
Kommunikationspartners, das den anderen Schichten verborgen bleibt.
Auf diese Weise ähneln Kommunikationspakete russischen Puppen,
bei denen jede Schicht nach und nach ihre privaten Informationen
generiert (bzw. auf der anderen Seite liest), um dann das Paket
der übergeordneten Ebene einzubauen (bzw. herauszunehmen und
weiterzugeben).
Genauso wie bei Software-Systemen liegt es auch nahe, bei
Bibliotheken Schichten einzuführen.
Die Abbildung 3.3
zeigt einen Ausschnitt der Schichten der Bibliotheken in C.
Abbildung:
Auswahl der Schichten gängiger C-Bibliotheken
|
Typisch für die Bibliotheksstruktur klassischer Programmiersprachen
ist der Aufbau, der mit systemnahen Modulen unten beginnt und
nach oben mit eher system-unabhängigen Schichten fortgesetzt wird.
Zwar läßt sich beispielsweise eine der Schichten ersetzen,
dennoch sind die Abhängigkeiten zu UNIX von curses und
X11 unübersehbar.
Selbst bei der Beschränkung auf obere Schichten ohne direkten
Import der Systemschnittstelle besteht immer das Risiko,
daß Systemabhängigkeiten indirekt eingeführt werden.
So verwendet und exportiert beispielsweise die C-Standard-Bibliothek eine
Reihe von Datentypen, die in der Ebene der Systemaufrufe definiert werden.
Durch die Verwendung objekt-orientierter Techniken lassen sich jedoch
die Schnittstellendefinitionen von den zugehörigen Implementierungen
trennen, und damit ist es möglich, dieses Beziehungsgeflecht
vollkommen umzustülpen.
Abbildung:
Trennung zwischen systemabhängigen
und systemunabhängigen Schichten
|
Wie Abbildung 3.4 zeigt, ist es nun möglich,
alle Abstraktionen mitsamt den systemunabhängigen Implementierungen
in die unterste Schicht zu verlagern.
Es läßt sich sogar erreichen, daß eine Applikation nur aus dieser
Schicht importiert und damit eine garantierte Systemunabhängigkeit
besitzt.
Weiter darüber folgt eine systemabhängige Ebene, die
eine möglichst sprachnahe Schnittstelle zum System offeriert.
Dabei können verschiedene Module aus der systemunabhängigen
Schicht importiert werden wie z.B. Mechanismen zur
Ausnahmenbehandlung.
In der obersten Ebene werden dann Implementierungen der
systemunabhängigen Abstraktionen hinzugefügt, die von
der Systemschnittstelle aus der mittleren Ebene abhängen.
Natürlich beschränkt sich eine objekt-orientierte Bibliothek
sich nicht in jedem Fall auf drei Schichten.
Sehr häufig entstehen Schichten, indem bestimmte Funktionalitäten
zu Paketen geschnürt werden (z.B. eine Schicht für Container,
eine für Persistenz und die nächste für verteilte Systeme).
Oder es existieren mannigfaltige externe Abhängigkeiten
(z.B. zu einem bestimmten Betriebssystem, einer bestimmten
Netzwerkarchitektur, einer Datenbank usw.), die dann entsprechende
Schichtenpaare hinterlassen (je eine als direkte Schnittstelle und
eine mit darauf basierenden Implementierungen, wobei dies
manchmal auch gleich zusammengefaßt wird).
Darüber hinaus ist es denkbar, verschiedene Schicht-Systeme
parallel zu unterhalten, die die gleiche Menge an Modulen
unterschiedlich unterteilen, z.B. auf der einen Seite
funktional orientiert und auf der anderen Seite nach
Abhängigkeiten sortiert.
Allerdings wird dies normalerweise nicht praktiziert und
allenfalls im Rahmen der Dokumentation virtuell erzeugt.
Die Strukturierung in Schichten wird üblicherweise nicht
direkt von den objekt-orientierten Programmiersprachen unterstützt.
Allerdings gibt es bei einzelnen Programmiersprachen
(insbesondere Eiffel (cluster) und BETA (fragments))
vorgegebene Meta-Sprachen,
während bei anderen Programmiersprachen auf die Dienste
des Betriebssystems oder anderer spezieller Pakete zurückgegriffen wird.
Im einfachsten Fall werden einzelne Schichten in entsprechenden
Verzeichnissen untergebracht - so auch bei Oberon.
Die Ulmer Oberon-Bibliothek ist in folgenden acht Schichten
organisiert:
Abbildung 3.5:
Importbeziehungen zwischen den Schichten
|
- base
- Allgemeine Abstraktionen und Mechanismen mitsamt
den systemunabhängigen Implementierungen.
- fp
- Konstantensammlung, die Informationen über die
Implementierung der Gleitkommazahlen auf dem aktuellen Prozessor gibt.
Weiter sind Schnittstellen zu einigen
Gleitkommaprozessoren vorhanden,
die den Zugang zu speziellen Operationen ermöglichen, die
beispielsweise für mathematische Funktionen interessant sind.
Jede dieser Schnittstellen exportiert eine BOOLEAN-Konstante
available.
Damit kann mit konstanten BOOLEAN-Ausdrücken entschieden
werden, ob eine dieser Schnittstellen zur Verfügung steht, oder
ob man einen ``handkodierten'' Algorithmus benötigt.
Da konstante BOOLEAN-Ausdrücke bereits zur Übersetzzeit
bearbeitet werden können, wird zumindest bei einem optimierenden
Übersetzer nur der benötigte Teil generiert.
- fpbase
- Hier sind systemunabhängige Implementierungen, die
die Konstanten aus fp benötigen und ggf. auch auf
die Schnittstellen der Gleitkommazahlenprozessoren zugreifen.
- if
- Hier sind alle Module enthalten, die dem Übersetzer bekannt sind.
Darunter fallen die Behandlung von Laufzeitfehlern, die
Datenstrukturen mit allen Modulen und Kommandos,
die Schnittstellen zur Speicherverwaltung und
einige globale Variablen im Zusammenhang mit Koroutinen.
- tasks
- Dieser Bereich enthält alle allgemeinen Abstraktionen und
systemunabhängigen Implementierungen, die vom Ulmer
Koroutinenschema abhängen.
Da das Modul aus if benötigt wird, das einige globale
Variablen zu Koroutinen unterhält, gibt es eine Abhängigkeit
von tasks zu if.
- conf
- Hier sind zwei Module enthalten, die die Parameter der aktuellen
Installation repräsentieren.
Sie werden während der Installation generiert.
- sys
- In diesem Bereich sind alle Module enthalten, die
die Schnittstellen zu dem System offerieren.
Ferner befinden sich hier auch die Module für die
Speicherverwaltung.
- unix
- Hier sind alle Module, die Abstraktionen aus den unteren Ebenen
auf Basis der UNIX-Schnittstelle implementieren.
Next: Hierarchien
Up: Organisation und Aufbau von
Previous: Einführung
Andreas Borchert
2/2/1998