Hintergrund

In meinem Kurs Betriebssysteme verwende ich ab und zu Code-Abschnitte, die die Implementation verschiedener Konzepte des Betriebssystementwurfs demonstrieren. Diese Code-Abschnitte sind bisher sehr uneinheitlich, und setzen sich aus verschiedenen Quellen zusammen:

  • Freier Code aus „echten“ Betriebssystemen wie z.B. Linux;
  • „Unechter“ Code (Code, zwar prinzipiell funktioniert, aber so nie einem Betriebsystem eingesetzt wurde), vornehmlich in C und Modula-2;
  • Pseudocode.

Um die Didaktik des Kurses zu verbessern, möchte ich ein Betriebssystem extra für diese Veranstaltung schreiben, so dass möglichst viele Konzepte direkt im Code wiederzufinden sind. Außerdem soll aihPOS — so der Name des Projekts — später als Codebasis bei verschiedenen Forschungsprojekten in unser Gruppe nützlich sein.

„aihPOS“ steht für another incompatible and hackneyed Pi Operating System. Obwohl dieses Akronym einiges über das Betriebssystem aussagt, habe ich es doch in erster Linie gewählt, weil es rückwärts geschrieben den Namen meiner Tochter ergibt: Sophia. Daher werde ich den Betriebssystemnamen ab sofort so schreiben: ɒiʜꟼOS. 🙂

Ziele

Bevor man ein Projekt der Größe eines Betriebssystems beginnt, und sei es auch nur ein so kleines wie ɒiʜꟼOS, muss man sich über die Designziele im Klaren sein. Ich nenne zunächst einmal einige Ziele, die ich ausdrücklich nicht habe:

  • Geschwindigkeit: immer, wenn es einen Widerspruch zwischen Performance und einem klaren Design gibt, werde ich das klare Design bevorzugen. Schließlich soll ɒiʜꟼOS niemals produktiv eingesetzt werden, sondern zur Demonstrations- und Testzwecken dienen.
  • Kompatibilität zu POSIX, Windows o.ä.
  • Einsatz auf komplexer Hardware wie z.B. x86-PCs.

Vielmehr habe ich folgende Designziele:

  1. Wesentliche Konzepte von Betriebssystemen sollen entsprechende Abstraktionen im Code wiederfinden.
  2. Das Betriebssystem soll eine klare Struktur haben und dem Mikrokernel-Ansatz folgen.
  3. Als Zielhardware wird der Raspberry Pi benutzt. Er ist günstig zu haben, so dass Studierende leicht mit Modifikationen des Codes herumspielen können, ohne ihren teuren Laptop oder Desktop zu riskieren. Außerdem besitzt unser Labor bereits einen Klassensatz Raspberry Pi 1B+ (mit einer Gertboard-Erweiterung).

Sprache

Traditionell werden Betriebssysteme in Assembler oder C geschrieben. Theoretisch kann für das Schreiben von Betriebssysteme nahezu jede Sprache genutzt werden (und z.T. werden sie auch tatsächlich genutzt). Häufig muss zwar „im Notfall“ etwas Assembler-Code eingebunden werden (völlig ohne geht es in der Regel sowieso nicht, da viele Feinheiten der unterliegenden Maschine sich nicht in höheren Programmiersprachen abbilden), aber es gibt Betriebssysteme in Java oder Lisp. Jedoch eignen sich die meisten Sprachen nicht sonderlich gut für eine Bare-Metal-Entwicklung, wie ein Betriebssystem sie erfordert: Die Sprachen bringen ihr eigenes Laufzeitsystem mit, das wiederum Systemrufe des Betriebssystem nutzt. Diese stehen aber bei der Entwicklung eines Betriebssystems ja gar nicht zur Verfügung. Entsprechend können einige Sprachkonzept nicht genutzt werden. Noch mehr Systemrufe sind meist in den Bibliotheken zu finden, so dass viele Bibliotheksfunktionen ebenfalls nicht genutzt werden können und ggf. nachimplementiert werden müssen.

Trotzdem werde ich für ɒiʜꟼOS nicht C benutzen, und das im wesentlichen aus drei Gründen:

  • Entsprechend der obigen Anforderungen sollen sich Betriebssystemkonzept gut in Code-Abstraktionen wiederfinden. Dies ist bei C fast so wenig der Fall, wie in Assembler.1
  • C ist eine alte Sprache, die viele modernen Konzepte der Typen- und Speicher- und Nebenläufigkeitssicherheit nicht kennt. Daher ist das „Rumspielen“ mit dem Code (vergleiche Designziel 3) relativ gefährlich.
  • So ein Projekt ist immer eine gute Gelegenheit, eine neue Sprache zu erlernen (oder wenigstens zu vertiefen)

Es stellt sich die Frage, welche anderen Sprachen möglich wären. Die beiden derzeitigen Mainstream-Sprachen Java2 und Python[^2a| kommen hier nicht infrage, da sie soweit von der Maschine entfernt sind, dass die Brücke zwischen Maschinen- und Sprachabstraktionen nicht sehr intuitiv wäre. Es gibt in meiner Gruppe zwar ein Projekt, ein Python-Betriebssystem zu schreiben, aber dies ist noch nicht so weit, dass es den hier gestellten Designzielen entspricht.

Es gibt einige Sprachen, die für sich in Anspruch nehmen systemnahe zu sein. Die bekanntesten sind wohl C++, D, Forth, Go und Rust. Von diesen scheidet Forth aus, da es ein bißchen zu maschinennah ist. Außerdem werden Abstraktionen in der Regel auf Datenstrukturen abgebildet, wo Forth etwas limitiert ist. Die anderen vier Sprachen sind aus dieser Perspektive zweifellos alle geeignet.

Daher habe ich weitere Kriterien betrachtet, die sicherlich sowohl in Auswahl als auch Attribuierung z.T. etwas subjektiv sind:

C++ D Go Rust
Bare-metal Programmierung + 0 +
Speichersicherheit 4 + 05 +
Nebenläufigkeitssicherheit 0 + +
Toolstabilität + + 0 ,
Neuheit der Konzepte (subjektiv) 0 6 + +

Als Ergebnis dieser Betrachtungen werde ich ɒiʜꟼOS in Rust schreiben. Mir erscheint auch Rust am meisten eine neue Sichtweise zu vermitteln.7 Es gibt ein bekanntes Zitat von Alan J. Perlis:

A language that doesn’t affect the way you think about programming is not worth knowing.

Alan J. Perlis

Dies ist der persönliche Hintergrund dieser Entscheidung. C++ mit etwas Programmierdisziplin hätte es genauso gut — und vermutlich an manchen Stellen einfacher — gemacht, aber Rust bringt mir neue Ideen bei.

Betriebssystementwicklung

An dieser Stelle will ich nochmal auflisten, was bei der Betriebssystementwicklung anders ist, als bei der Entwicklung (vieler) anderer Software:

  • Man hat es nicht mit abstrakter, sondern konkreter Hardware zu tun. Dadurch muss man sich mit vielen technischen Details des Prozessors und der Gesamtplattform beschäftigen, die man sonst ignorieren kann.
  • Die Dienste eines Betriebssystems stehen nicht zur Verfügung. Es gibt keine z.B. Speicherverwaltung und oder Dateisysteme. Daher stehen die meisten Funktionen der Standardbibliothek nicht zur Verfügung, weil diese letztendlich Systemfunktionen rufen.
  • Durch die beiden obigen Umstände werden im Design keine Abstraktionen „aufgezwungen“. Sie müssen erst erschaffen werden.
  • Damit ein Betriebssystem auch zum Laufen kommt, muss man sich Gedanken über den Bootstrapping-Prozess und damit über Binärformate und die Toolchain machen.

In den kommenden Folgen dieser Serie werde ich beschreiben, wie dies mit Rust umgesetzt werden kann.

{% include alert text=‘Ein Wort der Warnung: wie ich dargelegt habe, lerne ich Rust während ich ɒiʜꟼOS schreibe. Es ist also nicht zu erwarten, dass der Code stilechter Rust-Code ist, eher wird vermutlich an vielen Stellen C/C++ durchscheinen.

Ich werde mich aber bemühen und gegebenenfalls auch funktionierenden Code später überarbeiten, um dem Geist von Rust näher zu kommen.
Dies und die Überarbeitungen von nightly Rust (siehe nächster Beitrag) werden dazu führen, dass der Code alles andere als stabil sein wird. Aber das ist ja das generelle Abenteuer einer Software-Entwicklung.
‚ %}

Dies sind die Abenteuer…

Diese Blog-Serie ist nicht als Tutorial zu verstehen9. Es gibt auch keine Einführung in die Rust- oder in die Assembler-Programmierung. Vielmehr werde ich einfach darlegen, was mir beim Schreiben eines Betriebssystems in einer neuen Sprache „begegnet“. Vielleicht werde ich später, wenn ɒiʜꟼOS einen gewissen Stand erreicht hat, alles noch mal systematisieren und direkt für die Lehre aufarbeiten. Derzeit ist die Serie die Beschreibung einer Fahrt ins Ungewisse, mit möglichen Sackgassen und der Gefahr eines Schiffbruchs.

Das Abenteuer hat begonnen…


  1. Diese Aussage ist natürlich etwas polemisch. Man kann in C sehr wohl Abstraktionen darstellen, nur relative wenig direkt auf Sprachebene. Da bleibt im Wesentlichen der struct-Datentyp, aber man kann schließlich auch in den meisten Assemblern so etwas wie Structs definieren. 

  2. Nein, ich habe Java nie ernsthaft in Betrachtung gezogen, was aber auch an meiner Abneigung gegenüber dieser Sprache liegen mag. 

  3. In der Tat gibt es in meiner Forschungsgruppe ein Projekt, einen Kernel weitgehend in Python zu schreiben. 

  4. Man kann in C++ sehr sicher schreiben, wenn man eine ausreichende Selbstdisziplin an den Tag legt und verschiedene Idiome nicht benutzt. Im Gegensatz dazu erzwingt z.B. Rust sicheren Code. Dort, wo man unsichere (weil mächtigere) Konstrukte braucht, muss es explizit gemacht werden. 

  5. Go bekommt in dieser Kategorie bei mir Punktabzug für den vorhandenen Null-Pointer 

  6. Eigentlich wollte ich hier erst ein Minuszeichen setzen, habe es mir dann anders überlegt: Ich habe C++ lange vor Boost und C++11 gelernt, und seitdem nie grundlegend „aufgeholt“. 

  7. Go landet da auf Platz 2, zumal ich schon mal in Occam reingeschnuppert hatte, und D erscheint mir z.T. bewußt „unoriginell“ zu entworfen zu sein (was in einem anderem Kontext einen Vorteil darstellt). 

  8. ARM ist geplant, aber bisher nur teilweise implementiert. 

  9. Falls es jedoch Nachfragen gibt, bin ich aber gern bereit, auf einzelne Aspekte näher einzugehen. 


Kommentare