DISKUSSION VON BETRIEBSSYSTEMEN FÜR DEN AT-PC:


Ich gehe in diesem Text von ein paar Grundfragen aus, die in diesem Zusammenhang immer beantwortet werden müssen, und die teilweise eine sehr ins Detail gehende Kenntnis der Konstruktion von Computer und CPU erfordern. Deshalb diskutiere ich Betriebssysteme im Bezug zu einem AT-PC und einer CPU vom Typ i486 oder weiter entwickelt. Ich werde solch eine CPU im folgenden eine Intel-CPU nennen, weil sie beim Hersteller Intel konstruiert wurde. Eine CPU mit gleichen Opcodes bekommt man aber auch von anderen Herstellern, evtl. mit Erweiterungen des Befehlssatzes für die FPU.

Wenn man ein Betriebssystem schreiben will, stellen sich einem zunächst zwei Fragen:
1.Frage: Wieviel Betriebssystem braucht man eigentlich?
Anwort: Fast garnichts!
Ein Betriebssystem muss ausser einigen Gerätetreibern vor allem eine Verwaltung von Speicher auf Medien und Arbeitsspeicher enthalten und den Programmstart ermöglichen. Auch einige Initialisierungen muss es vornehmen.

2.Frage: Muss man ein Betriebssystem in einer "höheren" Programmiersprache schreiben?
Antwort: Nein!
Ein Betriebssystem muss immer mit einer konkreten Maschine umgehen. Diese ist immer durch einen besonderen Opcode der CPU gekennzeichnet. Dieser wird nur mit einer Assemblersprache so abgebildet, dass wirklich keine Möglichkeit der Programmierung verschenkt wird und viele nötige Einstellungen überhaupt erst machbar sind.
"Höhere" Programmiersprachen sind dagegen immer mit der Absicht ersonnen worden, gerade nicht eine konkrete Maschine programmierbar zu machen, sondern eine verallgemeinerte Abstraktion. Diese "Maschinen-Unabhängigkeit" wird als Vorteil gepriesen, ist tatsächlich aber nicht gegeben, denn alle Betriebssysteme müssen einen Teil enthalten, der in Assembler geschrieben wurde. Dieser Teil wird gerne als "arch dependant" (="Architektur bedingt") verniedlicht.
Die Frage kann also nur sein, ob "höhere" Programmiersprachen geeignet sind, irgendetwas in einem Betriebssystem besser zu machen. Auch das kann verneint werden! Diese Verneinung kann aber nur mit Bezug zu einer konkreten Maschine diskutiert werden, hier dem AT-PC.

BESONDERHEITEN DES AT-PC:

Mal abgesehen davon, dass bei "höheren" Programmiersprachen genau die Vorteile einer konkreten Konstruktion von Computer und CPU jedenfalls dann unerreichbar verloren gehen, wenn sie einzigartig sind, sind auch viele Abläufe zwar im Zweck sehr ähnlich, in konkreten Maschinen aber sehr unterschiedlich zu kommandieren, weil Zwecke zunächst schaltungstechnisch ermöglicht werden müssen, bevor sie mit einem Programm kommandiert werden können. Und es ist eben deshalb die schaltungstechnisch gegebene Logik, die "logisch" ist und nicht die "höhere" Abstraktion, die zwar ungeheuer mathematisch aussehen kann, tatsächlich aber nicht logisch im Sinne von "vernünftig" ist, weil sie Ab- und Umwege erzwingt.
Was genau das heisst, mache ich jetzt an den Teilen des Betriebssystems klar, die ich als Antwort auf die 1. Frage gab. Dabei spielt weniger die Rolle, mit welchen Controllern Gerätetreiber umzugehen haben, weil die auf sehr verschiedenen Computern die gleichen ICs sein können. Insofern ist also Programm unabhängig von der Maschine AT-PC, aber natürlich abhängig von der Maschine, die ein Controller darstellt.
Mit einer Intel-CPU werden die Register solcher Controller jedenfalls mit besonderen Kommandos in einem besonderen I/O-Adressbereich erreicht. Auch die Interruptleitungen sind auf besondere Weise mit CPU und Arbeitsspeicher verknüpft. Mittlerweile sind manche Register zwar auch auf Adressen im Arbeitsspeicher erreichbar, wenn ein PCI-Bus vorhanden ist. Aber auch dann ist die Adressierung eigentümlich anders mit einer Intel-CPU zu machen als z.B. mit einer von Motorola. Ausserdem muss dann der cache ausgeschaltet werden, was die Zugriffe nicht unbedingt schneller als über I/O-Adressen werden lässt.
Ich werde nicht die Konstruktionen der Hersteller Intel und Motorola gegeneinander stellen, die lange Jahre nur um Unterschiede zwischen ihren Konstruktionen wetteiferten. Tatsächlich sind in beiden Lagern sehr bemerkenswerte Ideen entstanden. Diese bemerkenswerten Ideen gehen aber sofort unter, wenn man einen gemeinsamen Nenner mittels "höherer" Programmiersprache finden muss!
Der kleinste gemeinsame Nenner führt vollkommen logisch zum Rückschritt!

Die bemerkenswerteste Idee in einer Intel-CPU ist die Segmentierung des Arbeitsspeichers, wie sie nach Umschaltung in den protected mode gegeben ist. Mit der dann gegebenen Adressierungsweise kann allerdings keine "höhere" Programmiersprache umgehen. Zunächst ist aber das wichtigste Problem zu betrachten, das auf diese Weise konstruktiv gelöst wurde:
Programme müssen mit bekannter Anfangsadresse übersetzt werden, damit der Assembler Namen für Label in physische Adressen übersetzen kann. Das kann z.B.stets die Adresse =0 sein.
Sollen nun mehrere Programme mit dieser Anfangsadresse gleichzeitig im Arbeitsspeicher arbeiten können, geht das nur, wenn mehrere solcher 0-Adressen zur Verfügung stehen - was natürlich nur mit einem Trick möglich ist. In jeder CPU wird das Problem gelöst, indem bei jeder Adressierung ein zweiter Adressteil zu der im Programm gegebenen Adresse hinzu addiert wird. In der Intel-CPU sind gleich drei verschiedene Methoden eingebaut. Während im real mode ein Addend benutzt wird, der eine durch 16 geteilte absolute Adresse ist, die "Segmentadresse", ist im protected mode eine in einen "descriptor" eingebaute absolute Anfangsadresse eines "Segmentes" in Gebrauch, die allerdings aktuell mit einer ebenfalls zweigeteilten Adresse (Anfangsadresse der GDT+selector) adressiert und geladen werden muss. Diese scheinbar zu umständliche Methode macht aber gerade den grössten Vorteil dieser Tabelle GDT aus.
Während die im real mode zu verwendende Segmentadresse eine ziemlich nahe liegende Form einer Basisadresse ist, die auch in anderen CPUs gebräuchlich ist (und dann anders heisst), ist der konstruktiv bestimmte Umgang mit Selektoren und Deskriptoren bei einer Adressierung im protected mode eine ausserordentliche Ingenieurleistung - offenbar weit über das geistige Fassungsvermögen von Systemphilosophen hinaus gehend.
In einer Intel-CPU ist deshalb noch eine dritte Methode eingebaut, die "höhere" Programmiersprachen wieder möglich macht. Diese Methode ist der page mode, der erst eingestellt werden kann, wenn der protected mode bereits eingeschaltet ist. Dazu ist aber nicht nur ein einzelnes PE-Bit im Kontroll-Register cr0 zu setzen, sondern eine Unzahl von Konsequenzen in Kauf zu nehmen, die allesamt nicht gut tun.
Nur am Rande zu erwähnen ist der V86-mode, der allein aus Gründen der Abwärts-Kompatibilität in die Welt gesetzt wurde. Allerdings bedingt er einstellbare Stellenzahlen für Operanden und Adressen, die für die Rückschaltung aus dem protected mode in den real mode zu beachten sind.

Zunächst stelle ich fest, dass im page mode nichts zu haben ist, was nicht auch vor der Umschaltung schon zu haben wäre. Der protected mode musste allerdings um zahlreiche Eigenheiten erweitert werden, um den page mode überhaupt möglich zu machen. Vor allem musste die Adresskalkulation zur Erzeugung physischer Adressen, wie sie zur Ansteuerung realer Speicherchips sein müssen, so erweitert werden, dass ein weiterer Adressrechner nötig wurde, der demjenigen nachgeschaltet ist, der im protected mode mittels Selektor und Deskriptor gegebener Basisadresse und mittels vom Programm gegebener 32-stelliger Offsetadresse die physische Adresse erzeugt.
Im page mode kann die Offsetadresse nur 12 Bit breit sein. Die restlichen 20 Bit entstammen zu je 10 Bit zwei Tabellen, der "page directory table" und der "page table". Der Arbeitsspeicher ist dann regelmässig in 4 KByte (=1000h Byte) grosse Bereiche, die "page frames", gestückelt, die allesamt ihren eigenen Tabelleneintrag benötigen. Auch die auf zwei Tabellen verteilten Basis-Adressen brauchen aber weit mehr Platz als die (immer vorhandene!) Global Descriptor Table (="GDT") - einen mindestens 1 MByte grossen Bereich. Und weil die Zweiteilung der Tabellen ein weiteres Ausufern des Bedarfes vermeiden soll, ist das nur um den Preis von mindestens zwei separaten Zugriffen auf den Arbeitsspeicher und mindestens drei Adresskalkulationen möglich (der Inhalt von cr3 muss auch noch eingerechnet werden) - wohlgemerkt zusätzlich zu den durch die Segmentierung bereits nötigen Zugriffen und Kalkulationen.
Im Durchschnitt ist damit die Geschwindigkeit der Programmausführung also höchstens halb so schnell. Diese Kalkulationen sind nämlich bei jeder Adressierung im Arbeitsspeicher nötig. Sind "task switches" nötig, muss nicht nur ein neuer Wert für cr3 im Speicher gelesen werden, es muss zuvor auch der alte Wert im zugehörigen TSS (im Arbeitsspeicher) gerettet werden. Noch viel mehr Aufhebens macht das swapping.
Das ist aber noch nicht der ganze Nachteil!
Woher kommen nämlich die Definitionen für die 20 Bit der Adresse, die das Programm nicht bar enthalten kann?
Und wie müssen Programme aussehen, die über viele Seiten gehen, aber nur innerhalb einer Seite adressieren können?
Prinzipiell geht das nur durch zusätzliches Programm, das nicht nur während der Übersetzung eines Programmes wirken muss, sondern auch weiteres Programm, das zur Laufzeit nicht nur präsent sein muss, sondern auch alle Adressierungen aktuell ergänzen muss. Das kostet Zeit und Speicher. Dazu hatten Systemphilosophen Ideen, die jedenfalls zu viel Geld zu machen waren...

Neben der Adressierung im Arbeitsspeicher haben Programme aber oft auch mit der Adressierung im I/O-Bereich zu tun, die in Intel-CPUs ebenfalls ganz eigenartig (und durchaus raffiniert!) zu verwenden ist. Sie hat jedenfalls den Vorteil, dass falsche Adressierungen im Arbeitsspeicher, die auch mal durch Transferfehler oder irgendein dahergekommenes Alphateilchen verursacht sein können, nicht gleich allzu grossen Schaden anrichten können. Auch damit können "höhere" Programmiersprachen nicht ohne Umwege umgehen.
Immer eingebaute Controller für Timing, Interrupts, Tastatur usw... sind deshalb deutlich umständlicher regierbar.

Schliesslich ist die Verarbeitung von Unterbrechungen nicht so, dass in "höheren" Programmiersprachen alles kommandierbar wäre. Insbesondere ist eine Interrupt Descriptor Table (="IDT") im Spiel, die auf absoluter Basisadresse, also nur mit passendem Selektor, zu erreichen ist, und die diese Basis-Adresse nur mit einem speziellen Kommando zugewiesen bekommen kann.

Diese Vielfalt von Möglichkeiten wird nicht nur durch einen cache und sein nur über spezielle Kommandos einstellbares Verhalten noch berührt, sondern auch Spezialregister, über die der Maschinenzustand einzustellen ist (z.B.der protected mode).
Auch die Deskriptoren enthalten noch jede Menge Stellen, die den Maschinenzustand für bestimmte Segmente einstellen. Das sind neben der Charakterisierung für Daten oder Programm auch Privilegien und Adress-und Operandenbreiten. Auch ein Präsenz-Bit ist vorhanden, das für einen Auslagerungsmechanismus (swapping) verwertet werden kann.

Es stellt sich also angesichts eines AT-PC noch eine weitere Frage, bevor man ein Betriebssystem schreibt.
3. Frage: Braucht man das alles?
Antwort: Nein!
Die Ingenieure bei Intel hatten vor allem ein Ziel im Auge, nämlich möglichst viele Ziele mit einem Mädchen für alles erreichbar zu machen. Das geht nicht ohne sich teilweise gegenseitig ausschliessende Möglichkeiten.
Die letzte Anwort führt deshalb sofort zur nächsten Frage:
4. Was braucht man und was verschenkt man?
Diese Frage kann man sehr verschieden beantworten und berührt damit zwangsläufig quasi-religiöse Überzeugungen. Tatsächlich kann man eine vernünftige Antwort sehr leicht finden, indem man Kriterien dieser Optimierungsfrage findet, die unbestreibar sind:
Schnelligkeit, minimaler Bedarf an Speicher, einfache Fehlersuche und Programmerzeugung.

BESONDERHEITEN VON BETRIEBSSYSTEMEN:

Es scheiden sich allerdings die Geister, wenn ausserdem noch Geschäftsideen oder sonstiger Geltungsdrang eine Rolle spielen. Schliesslich kommen damit auch Wertvorstellungen ins Spiel, die teilweise Weltgeltung haben. Mit diesen fange ich an:
Zwei Eigenschaften eines Betriebssystems stehen vielen Programmierern als unverzichtbar vorteilhaft im Kopf - "paging" und insbesondere "multi tasking". Das paging macht nur Sinn im Zusammenhang mit "swapping", denn es ging einmal darum, viel zu grosse Programme in einem viel zu kleinen, einst sehr teuren Arbeitsspeicher auszuführen. Das Spiel konnte nur einfach genug mit vielen gleich grossen Teilbereichen, den pages, veranstaltet werden. Die Not zu kleiner Arbeitsspeicher ist aber längst auf eine noch viel einfachere Weise aus der Welt geschafft worden, nämlich durch mehr Speicherkapazität pro Chipfläche und damit weniger Kosten. Paging und Swapping sind also Steinzeitmethoden, die höchstens dann noch von Interesse sein können, wenn ganze Filme vorzuführen oder gigantische Suchmaschinen zu programmieren sind.
Eine andere Not entstand einst durch langsame und teure CPUs. Man baute deshalb "main frames" (Zentralrechner) um die man viele einfachste Maschinen gruppierte, die gerade mal Monitor und Tastatur ansteuern konnten. Dabei entstanden gleich weitere Probleme, die mit "multi using" und "user identification" gelöst werden mussten. Alles in allem war dabei ebenfalls viel Zeit und Arbeitspeicher für die Verwaltung zu opfern. Auch diese Not kam auf die noch viel einfachere Weise aus der Welt. Tatsächlich hat heute jeder, der das hier liest, mehr Geschwindigkeit und Speicherplatz vor sich als main frames im Pentagon vor 30 Jahren. Auch multi tasking ist also eine Steinzeitmethode, die allerdings in manchen Köpfen als Zauber vorkommt, der erlaubt, aus einer Maschine mehrere zu machen. Solche Phantasien waren auch nicht ohne Folgen im maschinellen Bereich. Es wurden "cubes" und "super cubes" ersonnen, in denen mehrere CPUs "cluster" bildeten. Das ist aber nur Kubik-Blödsinn, weil die allermeisten Probleme eben nicht einfach skalierbar sind, also nicht etwa erlauben, das Dach vor dem Haus zu bauen. Aber eine Intel-CPU ist jedenfalls auch für Mehrprozessorsysteme gebaut (mit Pins, die Buskontrolle erlauben). Angesichts eines AT-PC kann man davon aber vollständig absehen. Auch die Verteilung von Abläufen ist mittlerweile längst viel sinnvoller durch Entwicklung von Controllern zustande gekommen - multi tasking ohne Konsequenzen für Betriebssysteme.
Kurz gesagt ist paging und multitasking nicht nur kein Vorteil, sondern ein Nachteil, der die Geschwindigkeit herab setzt und den Speicherbedarf in letzter Konsequenz gigantisch erhöht (weiter unten mehr dazu). Multi tasking erfordert, wenn es nicht "on demand" (="Bei Bedarf") ist, eine Uhr. Das kann ein AT-PC aber nicht ohne grosse Nachteile bieten. Es gibt nur einen Timer und die CMOS-Uhr mit Weckerfunktion. Es muss also mit weitreichenden Folgen die Herrschaft über die Uhr verwaltet werden...

Bevor ich zu einer vernünftigen Anwort auf die letzte Frage komme, ist noch abzuhandeln, wieweit Geschäftssinn und Geltungsdrang den Verstand trüben können. Dabei komme ich auf die quasi religiöse Entscheidung für eine "höhere" Programmiersprache und einher gehende Verdammung von Assembler zuletzt...
Zunächst soll die Frage sein, ob ein Betriebssytem unbedingt ein "supervisor" sein muss. Darunter versteht man solche Systeme, die niemals umgangen werden können, solange irgendwas weiteres geschehen soll. In einer Intel-CPU ist eine Menge Zeug eingebaut, das jede Begierde nach Allgewaltigkeit befriedigen kann. Dafür gibt es keine logischen Gründe, aber Zwecke, die komischerweise niemals diskutiert werden. Es ist nämlich prinzipiell möglich, dass ein Programm andere Programme lesen kann, dass Programme also disassemblierbar bzw. nachvollziehbar lesbar werden können. Das kann einem garnicht passen, wenn man mit einem Programm Geld machen will und am liebsten auch verstecken möchte, welcher Blödsinn da teuer bezahlt werden soll. Und so kommt es, dass Firmen mit Milliardenumsätzen entstehen konnten, die ihre Geschäfte wie Verlage auf dem copyright aufbauen, aber "Bücher" verkaufen, die niemand lesen darf oder auch nur kann.
An die Stelle von solchem Geschäftsinn kann natürlich immer auch anderes Streben nach Allgewaltigkeit treten. Es ist deshalb festzustellen, dass kein Computer einen Sinn dafür hat (und deshalb auch nicht braucht), was der Programmzähler oder ein Indexregister gerade adressiert und damit zur Ausführung bringt. Nur Zwecke des Programms können wichtig machen, was als nächstes Kommando adressiert wird! Ein supervisor ist also prinzipiell absurd, wenn nicht bestimmte Zwecke in einem bestimmten Programm, dem Betriebssystem, gehalten werden sollen. Tatsächlich gibt es Zwecke, die besser an einer Stelle abgehandelt werden. Das gilt für jede Form der Speicherverwaltung und damit auch für den Programmstart, der im protected mode nur in einem Programmsegment stattfinden kann, das wiederum nur innerhalb einer Verwaltung der GDT bereit gestellt werden kann. Allerdings kann man diese Verwaltung auf wenige Tabellen und Variable stützen, die dann prinzipiell auch aus beliebigen Anwendungsprogrammen heraus benutzt werden können. Es ist also grundsätzlich möglich, ein Betriebssystem so zu gestalten, dass wirklich alles auch aus Programmen heraus gegriffen werden kann - ein Blick über die Schulter durch einen supervisor ist also nicht nur überflüssig, sondern vor allem mal störend und nicht ohne Kosten an Zeit und Code zu verwirklichen.
Inwieweit das die im Zusammenhang gerne beschworene Systemsicherheit betrifft, diskutiere ich weiter unten - zunächst noch einige Bemerkungen zu der Frage, ob "höhere" Programmiersprachen eine so grossartige Schöpfung sind, dass man mehr als ein paar Worte dazu verlieren muss.

Geschäftssinn hat nicht nur konstruktives Vorgehen von Ingenieuren bestimmt, er hat auch die Programmierer von "höheren" Programmiersprachen getrieben. Selbst wenn ursprünglich eine Verallgemeinerung von Assemblersprachen die Ausgestaltung solcher Programmiersprachen wie "BASIC", "FORTRAN" usw. prägte. Das Streben nach griffigeren Formulierungen in Programmen trat bald zurück hinter den Griff in die Taschen von Programmierern. Und es wurde auch noch einen weiterer Vorteil "höherer" Programmiersprachen entdeckt. Sie gestatten eine Formulierung von Programmen, die jede Disassemblierung ins Leere laufen lässt. Wichtige Teile der Verknüpfungen können ausgelagert werden in Abgründe von Konfigurationsdateien, Linkern und Präprozessoren. Und deren Wirkung kann in die Programmausführung verlegt werden, wo sie unauffindbar ist - vor allem dann, wenn man auch noch Privilegierungsstufen im protected mode nutzt.
Es ist ganz erstaunlich wie das vernebelt werden konnte mit Lobpreisungen der Vereinfachung und Beschleunigung der Programmierung. Bei näherer Betrachtung, die über die sensationelle Vereinfachung eines "add" zu "+" hinaus geht, stellt man nämlich fest, dass das Gegenteil wahr ist: "höhere" Programmiersprachen führen nicht nur zu mehr Binärcode, sondern auch mehr Quellcode. Sie führen sogar zu sehr viel mehr Code, wenn man den jeweils im Programm bezweckten Zusammenhang betrachtet! Zum Programm gehören nämlich auch die mit unscheinbaren include-Anweisungen zur Ausführung gebrachten Codemengen in Bibliotheken und die mit unscheinbaren Makros eingefügten Abläufe. Und das alles muss nicht nur mit gigantischen Programmen übersetzt, sondern ebenso gigantischen Umtrieben gelinkt werden.
Aber schon ein "+" ist nicht weniger Schreibarbeit als ein "add", mit dem weitaus knapper eine Adresskalkulation mit der Adressierung von Operanden verknüpft sein kann als mit "+" in "höheren" Programmiersprachen erlaubt ist (z.B. ;add eax,[gs:ebx+ecx*4+displ]; ).

Nun gibt es seit dem Geschenk von Richard Stallman (GNU-Compiler für C/C++) und den vielen anderen Geschenken unbezweifelbar liebens- und lobens-werter Mitmenschen aus der Welt der Gnus und Pinguine, keine verachtenswerten Motive mehr bei der Programmerzeugung. Man kann die Quellen lesen - aber wer liest mehrere GByte und wann in dieser Welt? Und muss das wirklich sein?
Antwort: Nein!
Diese Antwort drängt sich einem schon beim Auspacken der voluminösen Geschenke auf, sie auszusprechen kostet noch mehr Nerven und Einfühlung. Man muss sich nämlich nicht nur mit C/C++ befassen, mit dem wenigstens Mikro$oft keine allzu grossen Geschäfte mehr machen kann, sondern mit UNIX, das in einer ganz anderen Welt vor mehr als 30 Jahren geboren wurde. Mittlerweile gibt es mindestens 100 Abkömmlinge, die auch als Quellprogramme zu haben sind. Deshalb kann man im Detail studieren, welche Konsequenzen diese "Systemphilosophie" hat, bzw. welche Konsequenzen offenbar garnicht erst bedacht werden...

Ein UNIX-artiges System ist die verallgemeinerte Form eines Betriebssystems, das untrennbar mit der "höheren" Programmiersprache C/C++ vorkommt. Ursprünglich und meistens auch in den Abkömmlingen wird es selbst weitgehend in dieser Programmiersprache formuliert. Alle anderen eventuell benutzbaren Programmiersprachen sind dann nur Abkömmlinge von C/C++, in das sie normalerweise übersetzt werden, bevor die Assemblierung getan wird.
Mal ganz abgesehen von den im Kern durchaus vernünftigen Abstraktionen "file", "device", "process" usw. heisst das aber im AT-PC, dass in den page mode geschaltet werden muss, weil man mit C/C++ keine Selektoren programmieren kann. Und man braucht einen supervisor, weil IDT und GDT und auch im Arbeitsspeicher liegende I/O-Register nur mit absoluter, physischer Adresse erreichbar sind. Schliesslich kann kein Prozedurruf mehr ohne Linker getan werden, der ausserdem den wichtigsten Teil der Arbeit erst zur Laufzeit machen kann.
Diese Einschränkung wird einem mit der Behauptung angedient, dass diese "höhere" Programmiersprache schliesslich erlaube, Programme zu schreiben, die maschinenunabhängig nicht nur auf dem AT-PC dienen können. Dass das nicht wahr sein kann, lehrt ein Blick in die Liste der Optionen für den GNU-compiler. Es gilt nicht nur jede Menge Dialekte dieser Sprache, sondern auch Linker und Dateisysteme zu unterscheiden! Statt maschinenabhängig zu sein, ist man jedenfalls abhängig von Zielsystemen. Das gleiche C-Programm läuft noch nichtmal auf dem gleichen PC unter zwei verschiedenen UNIX-artigen Betriebssystemen. Und auch die Maschinenunabhängigkeit ist nicht so einfach gegeben. Das wird klar, wenn man sich durch die Treiber-Dateien gelesen hat.
Das kann auch garnicht anders sein!
Es wird aber nicht ausgeträumt. Jährlich entsteht mindestens ein neues Projekt mit dem Ehrgeiz, wenigstens den einen wenn schon nicht den anderen Fehler nicht zu machen. Keiner will nämlich auf all die geschenkt verfügbaren Pakete verzichten, und ziemlich sicher, weil keiner sieht, dass der grösste Teil des begehrenswerten Volumens Verpackung und damit Müll ist.
Dieser Müll ist aber erst als solcher entlarvt, wenn man es mal ganz anders versucht hat. Unbezweifelbar sind gigantische Linker, Compiler, Präprozessoren, Bibliotheken, bash-scripte, makefiles usw. im Rahmen dieser "Systemphilosophie" unverzichtbar! Deshalb ist jedes UNIX-artige Betriebssystem mit den gleichen Mängeln behaftet. Erst recht, wenn es auch noch multi tasking bietet. Erst recht, wenn es noch nichtmal reinrassig ist wie das am meisten verbreitete Betriebssystem, das in der neuesten Ausführung so ewig beim Booten rummacht, dass die Akkus eines Laptops allein durch ein paar Mal rauf- und runter-fahren des Systems bereits erschöpft sind.
Welch ein Fortschritt...
Und das sicherste an diesem Betriebssystem ist, dass der Akku schnellstens geleert wird... Ganz nebenbei bemerkt schädigt solche Systemphilosophie nicht nur die Nerven, sondern macht, weil sie in Millionen von Maschinen sitzt, auch weltweit einige grosse Kraftwerke mehr nötig.

Ich hatte allerdings eher meine Nerven als die Umwelt im Auge, als ich die Anwort auf die oben gestellten Fragen suchte. Und mein Interesse war zunächst nur auf einige eigene Ziele gerichtet. Ich wollte sie möglichst einfach und schnellstens erreichen und hatte schon vergeblich Lösungen unter Window$ und LINUX gesucht.
Bei der Arbeit stellte ich fest, dass nur wenig mehr als ich brauchte, nötig war, um ganze Klassen von Problemen zu lösen. Ich stellte mir deshalb eine etwas erweiterte Aufgabe für das Betriebssystem, das ich schrieb und fortschreibe:
1) Der Quellcode muss so kommentiert werden, dass ihn jeder auch verstehen und beliebig ergänzen kann.
2) Die Übersetzung in Opcode muss ohne Beziehung zu Datei- und Linker-Systemen schnell erledigt sein.
3) Alle Festlegungen müssen durch Programme abgeändert, umgangen oder erweitert werden können.
4) Jede Stelle in einem AT-PC muss auch ohne Code im Betriebssystem erreichbar sein.
5) Möglichst alle Abläufe im Betriebssystem müssen während der Laufzeit tauschbar sein.

Ausserdem sollte es natürlich so schnell, platzsparend und einfach wie möglich geschrieben sein.
Das kann nur wahr werden, wenn alles in Assembler programmiert wird - auch die Anwendungen.
Diese Programmiersprache ist alles, was man können muss, um wirklich alles regieren zu können! Man braucht nicht wenigstens vier verschiedene Programmiersprachen ( C/C++, inline-Assembler, Präprozessor, bash...) zu können, die ausserdem je nach Zielsystem verschieden zu schreiben und zu benutzen sind. Statt dessen muss man etwa zwei Dutzend Kommandos und Regeln (z.B."effektive Adresse") im Kopf halten und kommt damit durch 99% aller Aufgaben durch. Weitere Kommandos und Regeln kann man z.B. in den Quelltexten zu meinen Programmen ASMn, ASMnr und ASMat nachlesen (inclusive der Methode, die Opcodes zu basteln).
Damit kann meine Antwort auf die vierte Frage jedenfalls heissen:"Es wird nichts verschenkt und nichts verbaut"!
(...abgesehen von Geschenken, die in C/C++ geschrieben sind und deshalb auch geschenkt zu teuer sind.)
Wer glaubt, dass damit irgendwas schwieriger würde, liegt schwer daneben (wahrscheinlich in einer UNIX-Welt). Meine Antwort erfordert nicht nur weniger Binärcode als in anderen Betriebssystemen für gleiche Zwecke gebraucht wird, sondern auch weniger Quellcode nicht nur im Betriebssystem, sondern auch allen nur denkbaren "Anwendungen". Das muss genauer erklärt werden. Ich nenne es ...

ASMOS:

Oben habe ich das Problem der Bereitstellung von 0-Adressen vorgestellt, das im AT-PC mit Segmentierung des Arbeitsspeichers gelöst ist, und im protected mode auf besondere Weise. Basis-Adressen sind dabei in Deskriptoren in der GDT definiert und über Selektoren adressierbar, die im wesentlichen eine Offsetadresse in dieser Tabelle darstellen (Bits 0,1,2 sind Angaben über Privilegien und Zieltabelle GDT oder LDT). Und ich schalte nicht in den page mode weiter, dessen Benutzung ich aber niemand unbelehrbarem verbaue...
Eine Konsequenz ist, dass Prozeduren nur entweder NEAR oder FAR gerufen werden können und dass jede Adressierung über Segmentgrenzen hinweg (definiert in cs/ds) auch einen Segmentbezug enthalten muss, undzwar in den Segment-Registern, die im protected mode Selektoren enthalten müssen und automatisch zu einer Basis-Adresse der GDT im GDT-Register addiert werden, bevor damit ein Deskriptor adressiert wird.
Namen können dann in Assembler nicht verwendet werden, und normalerweise auch keine absoluten Adressen!
Will man aber Namen verwenden, dann muss zur Laufzeit eine aktuelle Erzeugung von Bezügen zwischen Namen und Adressen stattfinden. In jedem Betriebssystem ist es ein Linker (es gibt viele Abarten), der die Verknüpfung von Programmen untereinander und mit dem Kernel besorgt. In LINUX werden z.B. beim Booten über 30000 solcher Bezüge hergestellt. Weil dort keine beliebig grossen Segmente, sondern pages benutzt werden, ist das garnicht verwunderlich.
Das muss nur sein, wenn Namen gewollt sind!
Weil das Betriebssystem zeitlich vor einem Programm geschrieben wird, das seine Dienste nutzt, und im weiteren auch jede Dienstbarkeit vor nutzenden Programmen, lassen sich alle Offset-Anteile von Adressbezügen vollständig aus Quelltexten ableiten (die natürlich lesbar sein müssen!). Eine aktuelle Herleitung ist prinzipiell überflüssig! Und es ist bei vielen anderen Bezügen zu Dateinamen oder Konfigurationen auch überflüssig, sie jedesmal mit einer Initialisierung herzustellen. Sie werden am besten während einer Installation hergestellt!

Auch die meist notwendige Übergabe von "Parametern" kann enorm vereinfacht werden, wenn sie in Register geschrieben werden - was nur mit Assembler möglich ist. Die Zuordnung grosser Datenmengen kann über Basis-Adressen in Registern stattfinden und muss keineswegs über den Stack gewälzt werden - der damit kleiner bleibt und kalkulierbarer schwillt. Die durch "höhere" Programmiersprachen erzwungene Einsparung von Registertransfers (implizit in einer "Zuweisung" enthalten) ist nämlich nicht etwa ein Vorteil, sondern ein ebenfalls entscheidender Mangel, der zusätzliche Transfers nötig macht, die ein Assembler-Programmierer vermeiden kann. Der kann ausserdem viele Werte ohne Variablentransfer verfügbar machen - mit immediate-Kommandos. Schliesslich kann der Adressrechner in der CPU genutzt werden, der von Hochsprachen-Compilern nur in Ausnahmefällen nutzbar gemacht werden kann. Das allerdings geht nur einfach genug, wenn nicht der page mode eingeschaltet ist.

Es ist aber noch ein Problem zu lösen. Dabei ist der Ausgangspunkt, dass jedes(!) Programm im wesentlichen so aussieht:
1.Adresse enthält 1.Kommando
...........Initialisierung
X.Adresse enthält 1.Kommando in einer unendlichen Schleife mit Eingabeprüfungen oder Programmabbruch
...........Prozeduren, die aus der Initialisierung oder der Schleife gerufen werden
...........Variable und Konstanten
Dabei können die mit Punkten gekennzeichneten Programmteile durchaus in der Abfolge vertauscht sein.

Dieses Programm kann das einzige in einer Maschine sein. Um alle wünschenswerten Zwecke zu erfüllen, müsste es aber sehr gross sein. Weil gerade in der Frühzeit der Computerentwicklung Speicherplatz sehr knapp war, mussten von Anfang an Wege gefunden werden, das grosse einzige Programm in viele Teile zu zerlegen.
Wenn die Teile aber in beliebiger Abfolge im Arbeitsspeicher liegen sollen, geht es nicht nur darum, dass ein Programm ein anderes finden kann, mit dem es zusammen wirken soll, es muss es auch als ein bestimmtes erkennen können. Programme müssen deshalb mit einer Individualität ausgestattet werden. Sollen also die Programme A,B,C zusammenwirken, muss dem Programm B nicht nur eine Anfangsadresse von A vermittelt werden, sondern auch, dass es die von A ist und nicht die von C. Die herkömmliche Lösung des Problems ist ein Dateiname für jedes Programm. Es genügt aber eine Ziffer, weil nur ein Programm diesen Namen wirklich braucht! Je mehr Buchstaben für Namen oder gar "Pfad"-Namen verwendet werden, umso zeit- und platzraubender werden Suche und Suchprogramm. Ideal ist ein 64-stelliger Name, der mit der Offsetadresse im Selektor und einer anderen absoluten Basis-Adresse verfügbar wird. Der Name kann dann in den untersten 32 Stellen mit einer einzigen ;cmp;-Anweisung selektiert werden und die weiteren Stellen können sonstwie die Bedeutung des Inhaltes typisieren. Man braucht dann keinen Linker mehr. Jedes Programm, das in der GDT lesen kann, kann sehr einfach andere Programme finden, die es zur Ausführung braucht, und Daten, die es selbst nicht enthält. Im weiteren nenne ich diese zweite Tabelle "Global Nickname Table" (="GNT") und die 64-stelligen Namen "nickname" (="Spitznamen").

Da kein Linker verwendet wird, kann jedes Programm ein "standalone"-Programm sein, das mit einem ersten Opcode auf Adresse 0 des Programm-Segmentes beginnt. Da aber jede Information über Label-Adressen in diesem Programm in fester Relation zur Adresse 0 stehen muss, wenn weitere Programme diese adressieren können sollen, wird für die Struktur solcher Programme folgendes vorgeschrieben:

jmp START ; Dieses Kommando steht auf Adresse 0 und besteht immer aus 5 Byte nop ; Im einfachsten Fall ein Byte... nop ; ...und noch ein Byte nop ; ...und noch ein Byte @programend: DD programend ; Diese Adresskonstante steht auf Adresse 8 im binary, wo sie ein Installationsprogramm ; leicht finden kann. Dateilängen müssen also nirgends aufgehoben werden! nickname: DD XXXXXXX ; In diese Adresse trägt das Installationsprogramm einen Spitznamen ein. nickattribut: DD XXXXXXX ; Dieser Teil des Spitznamens kennzeichnet die Bedeutung von Binärwerten exitOffset: DD EXIT ; ein FAR-pointer zur exit-Sequenz, der mit der Initialisierung hier eingetragen wird. exitSelektor: DD 0 VARIABLE: ; weitere Variable und Adresskonstanten, ............ ; deren relative Adressen immer die gleichen bleiben sollen! START: ; Das eigentliche Programm ............ EXIT: ; ....falls nötig ............ programend: DD 0 ; Dieses label bezeichnet die Adresse der letzten 4 Byte im Programm
Der Programmstart kann ein FAR-jump oder FAR-call sein. Im Fall eines Sprunges müssen Rücksprünge natürlich besonders vorbereitet werden, was aber nicht nur einfach ist, sondern auch beliebige Rücksprungadressen erlaubt und schliesslich Schwierigkeiten mit dem stack vermeidet - insbesondere dann, wenn die in UNIX-Systemen übliche Parameterübergabe über den stack ebenfalls vermieden wird. Deshalb wird der normale Programmstart mit einem Sprung auf die erste Adresse eines standalone-Programms getan. Im Gegensatz zu anderen Betriebssystemen, die rufen, werden damit auch alle überflüssigen father-child-Verhältnisse und ihre Verwaltung erübrigt. Allerdings ist grosse Umsicht geboten, wenn zwei Programme in ihrer Initialisierung die gleichen Variablen in einem dritten Programm umschreiben! (...kein Problem ohne Multi-tasking.)
Manche Programme (Treiber, Bibliotheksfunktionen...) müssen zunächst nur intialisiert werden, bevor sie dann tatsächlich im Zusammenspiel mit anderen Programmen laufen. Dann wird ein Prozeduraufruf verwendet, der zur Ausführung der Initialisierung führt. Ein Ruf wird auch für den Fall vorgesehen, wo ein Treiber-Programm eine eigene Installationsprozedur enthält, die nur während der Installation ausgeführt wird - also nicht mehr während der Betriebszeit.

Selbstverständlich kann diese Programmstruktur beliebig verfeinert werden. Wesentlich ist nur, dass Adressvariable als FAR-Zeiger definiert werden, undzwar so:
label_offsetadresse: DD label
label_selector: DD xh
Diese können entweder die Adresse des Sprungziels enthalten oder die 1.Adresse eines weiteren Stapels von FAR-Zeigern. Im zweiten Fall muss der Selektor einen Daten-Deskriptor adressieren!
Im ersten Fall ruft man z.B. mit aus der GDT bekanntem Selektor xh und aus dem Quellprogramm bekannter Offsetadresse yh von "label_offsetadresse" eine Prozedur namens "label", muss allerdings zunächst den Selektor xh in ein Segmentregister transferieren, weil in Adressangaben für indizierte Adressierung kein Immediatewert für den Selektor erlaubt ist:
mov eax,10h ; immer gleicher Programm-Selektor des Kernels mov gs,ax ; z.B. nach gs... call FAR [gs:yh] ; womit z.B. die Sprungzieladresse im kernel direkt adressiert wird / yh =Ziffer Brauchbarer ist aber der zweite Fall, in dem zunächst die Adresse der Adresse der Sprungzieladresse geholt werden muss:
mov eax,18h ; immer gleicher Daten-Selektor des Kernels mov gs,ax ; z.B. nach gs... lfs eax,[gs:yh] ; Die 1.Adresse der Sprungzieladressen call FAR [fs:eax+displ] ; Mit displacement displ wird die Adresse des Sprungziels im weiteren Stapel adressiert ; Daten-Selektor in fs !
Der zweite Fall tritt immer dann auf, wenn ein Treiber, der austauschbar sein soll, angesprungen wird. In ASMOS stehen vor allem grosse Tabellen mit Adressen (="pointer"), die nicht nur den einfachen Zugriff auf Prozeduren und Sequenzen im Kernel möglich machen, sondern auch während der Laufzeit umgeschrieben werden können. Damit sind beliebige Abwandlungen von Abläufen möglich!
Ich weiss, dass dies für C-Programmierer fast unverdaulich ist. Wenn sie es begreifen, wird ihnen mit schauerlicher Kürze vermittelt, welcher Wahnsinn hinter einem scheinbar eleganten print(blabla); steckt! Dann nämlich muss eine ganze Datei mit %include "dingsbums" eingebunden worden sein, ein Linker musste Zeichenketten vergleichen und eine Tabelle anlegen und eine Prozessverwaltung muss zur Laufzeit weitere Tabellen anlegen. Womöglich muss auch noch eine Privilegierung über ein Dateiattribut ermittelt worden sein und und und...
Und während man bei der eben vorgestellten Methode völlig frei ist, die Parameterübergabe zu gestalten oder gar zu lassen, wird in C stets ein stack-frame angelegt - auch wenn der einzige Parameter in einem Register übergeben werden kann. Schliesslich kann man in Assembler eine Fehler-Rückmeldung mittels gesetztem flag veranstalten. Während also in einer "höheren" Programmiersprache ein "if (return==0);goto gutgegangen..." nötig ist, um eine Fehlermeldung zu ihren Konsequenzen zu führen, schreibt ein Assmbler-Programmierer nach dem Prozedurauf nur "jnc gutgegangen" und hat damit etwa um den Faktor 10 weniger Code erzeugt!

Nur ein Problem muss noch gelöst werden: Wie kommt der richtige Selektor für Programm-Daten in ds?
Während der richtige Selektor für cs mit der Sprunganweisung transferiert wird, muss ds ausdrücklich definiert werden. In ASMOS gilt das Prinzip, dass eine Prozedur, die FAR gerufen wird, den eigenen Programm-Daten-Selektor lädt und dem Rufer den seinen vor dem Rücksprung wieder in ds transferiert hat - also:
FP_FARprocedure: push ds mov eax,[eigenerSelektor] mov ds,ax ....... pop ds retf Bei Sequenzen, die FAR angesprungen werden, muss jede Sequenz zunächst ds definieren:
F_label: mov eax,[eigenerSelektor] mov ds,ax ....... jmp FAR [Irgendwoandershin]
Nun aber komme ich erst zu dem, was so in C nie zu machen wäre. Der Treiber, dessen Einsprungadresse ich oben unter der Adresse 18h:yh gelesen habe, ist ein anderer, wenn ich mit folgenden Kommandos eine andere Adresse dort eintrage:
mov eax,18h ; immer gleicher Daten-Selektor des Kernels mov gs,ax ; z.B. nach gs... mov DWORD [gs:yh],neuer_Treiber ; =die Offsetadresse des neuen Treibers mov ax,cs mov WORD [gs:(y+4)h],ax ; =der Selektor des Programms mit dem neuen Videotreiber Dies ist also, was ein Treiber mindestens an Initialisierung leisten muss, wenn er einen anderen ersetzen soll.

Auf ähnliche Weise können auch Variable (insbesondere Texte) umdefiniert werden. Es muss allerdings eine weitere Regel eingeführt werden. Diese Regel setzt die Adressen der Variablen in Bezug zu Einsprungadressen von Prozeduren. Weil nach einem Label Programm stehen muss (wenn nicht mit einem weiteren Sprung gestartet werden soll), wird der Platz oberhalb ( auf niedrigeren Adressen...) einer Einsprungadresse für "lokale" Variable benutzt, die dann mit negativem Displacement zur Adresse des Labels erreichbar sind. Im Prinzip also so:
lokaleVariable: DD 0 Label: .....................Programmcode Die lokale Variable ist damit nicht mehr lokal, sondern kann umdefiniert werden, indem die Adresse von "Label" benutzt wird und das Displacement -4 eingesetzt wird. Damit man da auch schreiben kann, muss natürlich der Programm-Selektor der Labeladresse gegen einen Daten-Selektor getauscht werden. Weil in ASMOS die Regel gilt, dass der zum Programm-Selektor kongruente Programm-Daten-Selektor stets (Programm-Selektor)+8 ist, ist das einfach machbar. Dieses Prinzip, lokale Variable anzuordnen, kann für jede beliebige Einsprungadresse angewendet werden und erlaubt auch grosse Anzahlen von Parametern zu transferieren, ohne den stack benutzen zu müssen - und ohne solche Variablen stets neu besetzen zu müssen.
Diese Methodik ist der Kern dessen, was als C++ verherrlicht wird - dort allerdings mit einem abwegigen Wust an Regeln und neuen Ausdrücken gepaart, die ganz eigenartige "Vererbung" und irritierende "Homonyme" einführen. Es kann aber kurz gesagt werden, dass mit C++ das gleiche wie mit Assembler nicht gemacht werden kann, und dass das, was gemacht werden kann, bei weitem umständlicher und nicht ohne langwierige Lernphasen ausfällt. Auch die verheissene "Portierbarkeit" ist keineswegs gegeben, weil C/C++ Compiler und ihre Wirts-Betriebssysteme keineswegs identisch sind. Ohne angepasste Ziel-Betriebssysteme, Ziel-Dateisysteme und Ziel-Linker ist garnichts portierbar!

Viele Tabellen in ASMOS haben aber eine absolute Basis-Adresse Offset+100000h, wobei der "Offset" die absolute Adresse im Programmsegment ist, während die 100000h die absolute Basis-Adresse des Kernels im Arbeitsspeicher ist. Wenn man sich an die in ASMOS verwendete Regel hält, im Segmentregister "es" stets den Selektor 8 zu halten ( adressiert einen Daten-Descriptor mit absoluter Basis-Adresse =0 ), dann ist also eine solche globale Variable sehr einfach zu adressieren:
mov [es:eax+100000h],ebx ; schreibe Wert in ebx an Kernel-Adressoffset in eax
Ein Betriebssystem kann nicht mit dem Wissen künftiger Komponenten geschrieben werden. Folglich werden Stellen für FAR-Zeiger im Kopf des Kernels fehlen. Es können aber all die Komponenten einer Maschine berücksichtigt werden, die ohne Zweifel immer nötig sind: Tastatur, Maus, IDE, PCI, FD...
Andere Treiber für eine Soundkarte oder einen Ethernetport können von dem Programm geladen, initialisiert und benutzt werden, die mit dem Teil umgehen. Der Speicher kann also zunächst frei von solchen Programmen bleiben, die nur gelegentlich oder gar niemals (bei nicht vorhandener Soundkarte z.B.) gebraucht werden. Aber auch ohne einen FAR-Zeiger in ASMOS können Treiber von ASMOS initialisiert werden! Und ihr Segment kann dann von Programmen, die damit umgehen, in der GDT zur Laufzeit gefunden werden. In ASMOS kann jedes Programm grundsätzlich alles tun, weil keinerlei Privilegebenen existieren!

Beim Programmstart wird der Programmzeiger auf die Adresse 0 im Segment des Programms gesetzt. Was da im weiteren veranstaltet wird, ist völlig dem Belieben des Programmierers überlassen! Weil nichts zu Programm werden kann, wenn es nicht zuvor in ein Programm-Segment gestellt wurde, ist ASMOS nicht etwa das "sicherste", sondern das einzig sichere Betriebssystem. Die Umwandlung von Binärcode zum ausführbaren Programm kann nämlich nur mit Hand- und Kopfbetrieb des Benutzers bewirkt werden (Menü hinter F9). Man müsste zusätzliches Programm schreiben, um das zu erreichen, was all die sichersten Betriebssysteme automatisch machen. Hintertüren, die erlauben beliebige Daten auf Parole zum Programm zu machen, existieren nämlich in allen wichtigen Betriebssystemen. Weil ein Mehr an Programm zur Schaffung solcher Hintertüren nötig ist, ist auch kein Zweifel erlaubt, dass die berüchtigten Viren, Würmer und Trojaner vom Erzeuger des Betriebssystems gewollt sind...
Unter ASMOS sind ausserdem Assembler-Übersetzungs-Programme ausführbar (ASMn, ASMnr, ASMat), die erlauben, grundsätzlich nur mit Quellprogramm umzugehen und das erst zum Binary zu machen, wenn sich der Benutzer überzeugen konnte, dass ihm nichts untergemogelt wird. Die Assemblierung geht so rasend, dass keine Systemkomponente jemals anders als in Form eines Quellprogramms verbreitet werden muss. Selbst die Übersetzung von ASMOS dauert gerade eine Sekunde...

DETAILS DER VERWENDUNG VON SPITZNAMEN IM ZUGE EINER INSTALLATION:

Wie ich eingangs bemerkt habe, sind individuelle Namen für Programme nötig, wenn sie während der Laufzeit an beliebiger Position stehen sollen ("relocation"). Nur über einen Namen kann dann die aktuelle Segmentadresse gefunden werden. Diese Namen bestehen aus einem 32-stelligen Binärwert, der erlaubt, 4G Programme zu unterscheiden.
Damit der Offset in einer Namenstabelle direkt als Selektor verwendet werden kann, gebe ich den Namen noch ein Attribut. Das kann zunächst mal dazu dienen, im gleichen Zug mit der Namensfindung zu entscheiden, ob das benamte Segment ein ausführbares Binary ist oder eine Datei, editierbar oder nicht usw...
Insbesondere kann man mit einem solchen Attribut auf einen weiteren notwendigen Namensstapel verweisen, der die Namen enthält, die der Benutzer tatsächlich lesen können muss: editierbare Dateien oder startbereite Programme.
Weitere Spitznamen werden als laufende Nummer während einer Programminstallation vergeben und in einer speziellen Installationsdatei bezogen auf ihre eigentlichen Namen, die dann 48 Zeichen lang sein können - in ASMOS eine elementare Länge. Die genauen Regeln für die Definition von Spitznamen entnimmt man dem Quell-Programm von ASMOS...
Diese Methode erübrigt eine insbesondere in UNIX-Systemen gepflegte Bürokratie des "process management" komplett.
Für die Installation eines Programmes genügen in ASMOS kaum mehr Programmzeilen als man in C/C++ Programmen für %include... und %define...Anweisungen benötigt, und ausserdem einige besondere Prozeduraufrufe und Sprünge, die man aus meinen Beispielprogrammen Kopieren&Einfügen kann. Abgesehen von einem Installationsverzeichnis, das die Namensbezüge von Dateinamen zu Spitznamen enthält, sind keinerlei weitere Strukturen nötig!
Nur wer die Qualen hinter sich hat, eine UNIX-Bürokratie zu durchschauen, kann ermessen, welche G-Byte-Müllberge diese Methode komplett erübrigt. Und wer jemals eine ELF-Datei gelesen hat und sich gewundert hat, warum bis zum zehnfachen des Opcodes Pfadnamen und irgendwelche Kürzel sind, kann ermessen, wieviel Arbeitsspeicher plötzlich verfügbar wird, wenn dieser Müll in Spitznamen umgesetzt wurde.

Eine weitere Frage ist: Wie werden Treiber in den Kernel integriert?
Davor steht die Frage, inwieweit sich ein Treiber von anderen Programmen unterscheidet. Ein Programm ist er nämlich insofern, als er eine eigene Basis-Adresse enthält, also in einem eigenen Segment arbeiten muss.
In ASMOS werden "Treiber" nur solche Programme genannt, die in einer Installation zu einem Teil des Kernels (oder auch eines anderen Programmes) werden und mit ihm zusammen geladen und initialisiert werden.
Das erfordert einige wenige Festlegungen, die einen Treiber dann von einem Programm unterscheiden, das separat gestartet wird, aber dennoch exakt das gleiche bewirkt (solche Quasi-Treiber könnte man "Module" nennen - ich bleibe aber bei "Programm", weil eine Unterscheidung sehr willkürlich wäre).
Die gängige Definition eines Treibers ist, dass er mit der Gerätschaft umgeht.
In ASMOS sind aber Treiber stets solche Programme, die beim ersten Ansprung nur initialisiert werden, dann aber nicht mehr auf ihrer Basis-Adresse angesprungen werden. Sie bestehen aus Prozeduren, Sequenzen oder Daten, die mit der Initialisierung Teil des Programms werden, mit dem sie verknüpft werden (insbesondere also ASMOS). Solche Treiber werden grundsätzlich mit einem Ruf initialisiert.

WEITERE AUSGESTALTUNG VON ASMOS:

Das Programm ist so gestaltet, dass es bereits ein komplettes System darstellt, in dem nicht nur Dateien bearbeitet und in Dateisystemen gespeichert werden können, sondern auch Kontrolle von Maschine und Start bzw. Installation von Programmen über Menüfunktionen erreichbar sind. Alles wird aus einer Tastaturabfrage heraus verzweigt, die auch durch eine andere Schleife ersetzt werden kann. In dieser Schleife sind drei Schleifen gekapselt, die im Effekt alles erlauben, was in anderen Betriebssystemen nur mit multi tasking erreicht werden kann. Es gibt einen Editier-Modus, der auch in Teil-Fenstern existieren kann, einen Kopier-Modus, der z.B. auch rohe Kopien (Binärcode) erlaubt, und es gibt schliesslich einen Menü-Modus, in dem unter anderem der Programmstart erledigt wird.
Was da alles zu haben ist, kann man in der Datei "ASMOSdocD" nachlesen. Bezogen auf die Funktionalität werden nicht nur MSDO$ und Derivate himmelweit übertroffen, sondern auch eine einfache Installation von LINUX ohne Xserver (=nur Text-Modus), mit C-Standard-library, mit Dateimanager "mc", Editier-Programm "vi" und Kommando-Interpreter "bash".
Dabei besteht ASMOS aus etwa 130000 Byte Binärcode, während allein der "vi" aus mehr als 1 MByte gemacht ist (und weniger bietet als meine zwei Editier-Modi, die ca.20000 Byte ausmachen). Ausserdem sind Prozeduren in ASMOS verfügbar, die weit über den Funktionsumfang der C-Standard-library hinaus gehen und die denkbar einfachste Einschleifung von Programmen in den Menü-Modus erlauben - mit Ein- und Ausgabe über die Tastatur.
Praktisch alle Teile des Programms sind aber austauschbar, weil Sequenzen oder Prozeduren indiziert und FAR angesprungen werden. Ein Programm (oder Treiber) kann also in seiner Initialisierung die Sprungzieladressen (="pointer") umdefinieren und auf diese Weise die in ihm enthaltenen Programmstücke zu denjenigen machen, die bei gegebener indizierter Adressierung angesprungen werden. So können vorhandene Abläufe ersetzt oder erweitert werden. Natürlich müssen die Prozeduren, die vorhandene ersetzen, äquivalent agieren, also z.B. bestimmte Statusbits benutzen oder bestimmte Variable definieren oder bestimmte Parameter aufnehmen oder zurückgeben. Es kann aber auch nur irgendeine Tabelle getauscht werden, die ebenfalls indiziert adressiert wird. Und falls das alles ist, was zu tun ist, kann ein Treiber bereits während der Installation verschwinden. Vor allem aber können mittels Programmen oder Treibern Verzweigungen eingerichtet werden, die die Funktionalität von ASMOS erweitern.
Das um Treiber erweiterte ASMOS kann nicht mehr vollständig re-initialisiert werden! Zukünftige Treiber können unkalkulierbar Änderungen setzen. Bei Fehlverhalten muss deshalb ausgeschaltet werden - auch das in ASMOS ohne Ritual. Treiber werden mit dem Kernel mittels eines besonderen Installationsprogrammes verknüpft. Dieses in FDOS2 enthaltene Programm formt aus ASMOS-Binary und Treiber-Binaries ein kohärentes Stück, das vom Bootloader in einem Zug vom Bootmedium in den Speicher transferiert wird und das ohne weiteren Dateitransfer initialisiert wird.
Damit sind ergreifende Bootrituale mit Willkommensgrüssen, Wolken und Fahnen zur Unterhaltung oder uferlose Textausgaben, die keiner lesen kann, Vergangenheit. ASMOS bootet in wenigen Sekunden in einen Editor mit einem Menü-Modus, der einen kargen "Prompt" ersetzt. Die meiste Zeit wird mit einer Speicherprüfung vertan, die äusserst gründlich ist.

INITIALISIERUNG UND INSTALLATION:

Der Ablauf der Installation von Treibern (und Tabellen) sieht immer so aus:
ASMOS enthält unter "programend:" eine besondere Adresse. Sie kann im Binary auf Adresse 8 gelesen werden. Nach "programend:" schreibt das Installationsprogramm die Anzahl der folgenden Basis-Adressen von Treiber-Binaries. Wenn z.B. ein einziger Treiber anschliesst, steht dort eine 1, gefolgt von der Offsetadresse im Kernelsegment, auf der der erste Opcode des Treibers steht - also so:

programend: DD 1 ; ein 32-stelliger Wert für die Anzahl folgender Offsetadressen, der vom Installationsprogramm ; in das binary geschrieben wird. Die Adresse dieses Wertes steht auf Adresse 8 des binaries! programend+4: DD x ; Adresse der 0-Adresse des Treibers = programend+8 (in diesem Fall) programend+8: jmp Init_Treiber ; Der erste Opcode des Treibers
Da das Installationsprogramm in jedem Treiber-binary auf Adresse 8 dessen Länge erfahren kann, kann der nächste Treiber auf der Adresse von "programend:" im Meldungstreiber beginnen usw...
Allerdings müssen zuvor alle vorhandenen Treiber um 4 nach oben verschoben werden, um einer weiteren Anfangsadresse Platz zu machen. Alle vorhandenen Adressen müssen dann noch mit 4 addiert werden und der Wert unter "programend:" muss inkrementiert werden - und fertig ist die Installation!
Die Initialisierung des Kernels kann unter "programend:" des Kernels die Anzahl der Treiber lesen. Vom ersten Treiber an wird dann zuächst ein Programm- und deckungsgleiches Programm-Daten-Segment in der GDT definiert mit der Basis-Adresse, die sich aus der im Adresstapel stehenden Adresse + der Kernelstartadresse (=100000h) ergibt. Dann wird der erste Opcode des Treibers mittel Prozeduraufruf angesprungen und damit seine Initialisierung ausgeführt. Ein Beispiel ist in der Datei "D_passw" enthalten. Das Progrämmchen stoppt die Initialisierung und fordert ein Passwort - was denkbar überflüssig ist.

Ein Treiber muss ausserdem (wie oben ausgeführt) zwei Variablenadressen für einen Spitznamen enthalten. Der Spitznamen wird vom Installationsprogramm erzeugt und dort eingetragen. Ausserdem wird er in ein Installationsverzeichnis mit Bezug zum Dateinamen eingetragen. Dieses Installationsverzeichnis ist ein Teil des Kernelbinaries und wird bei Installation weiterer Programme fortgeschrieben. Dann erhalten auch weitere Programme, die nicht Treiber sind, ihren Spitznamen.
Schliesslich hat der Wert der unter der Adresse des Programmendes eines Treibers steht, noch eine besondere Bedeutung im Installationprozess. Zwei Werte sind bislang definiert, die eine Verzweigung im Installationsprozess bewirken. In ASMOS kann ein Treiber nämlich wirken, ohne dass sein Binary mit dem des Kernels verbunden wird! Er kann z.B. nur im Binary des Kernels irgendwas umschreiben. Danach braucht man den Code des Treibers nicht mehr. Er wird vom Installationsprogramm mit dem nächsten Treiber überschrieben. Es kann aber auch nur ein Teil des Teibercodes in diesem Sinne überflüssig sein. Dann kann nur dieser Teil vom Installationsprogramm überschrieben werden. Solche Treiber eignen sich also vorzüglich zur Eintragung von Übersetzungen in Meldungstexte oder Filtern für Buchstaben.
Treiber müssen Daten stets innerhalb ihrer Länge halten und dürfen im Gegensatz zu anderen Programmen keine Dateien während der Initialisierung durch ASMOS gründen! Der Grund ist der Ablauf zum Löschen von Dateien und Programmen, mit dem in ASMOS Speicherplatz wieder verfügbar wird. Es kann aber eine zweite Stufe der Initialisierung gemacht werden, die bei erstmaligem Gebrauch des Treibers während der Laufzeit Dateien gründet, falls erforderlich (Beispiel sind meine Assembler-Übersetzungsprogramme).

Wie einfach die Installation eines Betriebssystems bei so einer Gestaltung wird, zeigen die paar KByte, mit denen ich FDOS1 zu einem FDOS2 erweitert habe. Damit ist eine komplette Installation so schnell zu machen, dass man von einer Änderung in ASMOS über eine Assemblierung bis zum laufenden neuen Betriebssystem in weniger als 5 Minuten kommt. In "FDOS2doc" sind die Einzelheiten der Installation beschrieben, die in allen wünschenswerten Varianten zu haben ist. Dabei können auch sämtliche Dateisysteme und Dateien erhalten bleiben!
Will man auf die gleiche Weise am Kernel eines LINUX (oder anderer UNIX-artiger Systeme) arbeiten, so braucht man schon für die Compilation rund 24 Stunden (auf einer Pentium-Maschine). Ein weiterer Tag vergeht mit der Installation. Anders ausgedrückt: Mein Betriebssystem wäre in zehn Jahren vergleichbar reif gewesen, hätte ich in C/C++ ein UNIX-artiges System geschrieben!

Nach mittlerweile bis zu vier Jahren täglichem Umgang mit bestimmten Teilen meiner Betriebssysteme kann ich noch zur Brauchbarkeit und Problemen Worte verlieren:
Booten, Installation, Umgang mit Disketten und IDE-Festplatten und Umgang mit Dateisystemen sind vollkommen fehlerfrei, die Editoren und verschiedene Abläufe zur Gestaltung von Dateiinhalten können aber noch mir noch nicht bekannte Fehler enthalten. Dabei spielt aber eine tragende Rolle die maschinelle Behausung der Programme!
Ich habe alles immer auf vier Maschinen mit verschiedenen CPUs (i486,Pentium,Duron,Athlone) und verschiedenen BIOS-Versionen (Award und AMI) ausführlich getestet und dabei folgende Probleme festgestellt:

Die BIOS-Gerätenummern für die Festplatten werden nach unterschiedlich komischen Regeln festgelegt, wenn mehrere Festplatten im System sind. Das kann zu Fehlverhalten beim Booten führen, bei dem nämlich zunächst mit diesen Nummern umgegangen werden muss. Bemerkbar macht sich ein solcher Fehler bei der Installation. Im Quelltext von FDOS2 kann eine kleine Änderung gemacht werden, um das Installationsprogramm, das im Ausgangszustand mit "AWARD-BIOS" umgeht an "AMI-BIOS" anzupassen. Das garantiert aber nicht den Erfolg! Eine perfekte Lösung ist deshalb eine Installation für Booten mit Bootloader auf FD, weil andernfalls auch die Bootsequenz im BIOS umdefiniert werden müsste.

Die Paarung alter Festplatten mit allzu schnellen, neuen CPUs kann zu Fehlverhalten bei Zugriffen auf Dateien führen. Eine Rettung ist normalerweise eine Wiederholung der Aktion, im verzweifelten Falle kann aber immer "roh" gelesen werden. Niemals können Dateiinhalte verloren gehen, wenn nicht die Festplatte kaputt ist!

Maschinen, die nicht nach dem VGA-Standard Daten in Bildschirminhalte umsetzen, nicht die von mir programmierten Diskettencontroller enthalten, oder die sonst irgendwie nicht dem AT-Standard entsprechen, sind natürlich untaugliche Behausungen für ASMOS. Schliesslich müssen Disketten richtig formatiert werden. Sie können dann aber immer noch untauglich sein! Auch die Fehlererkennung in meinem Treiber hat schon untaugliche Disketten nicht erkannt. Wenn also das Booten nicht klappt, liegt das oft nur an untauglichen Disketten - eine andere nehmen und nochmal versuchen!
Welche Hindernisse "anti-Virus"-Einstellungen im BIOS darstellen können, habe ich nie untersucht. Sie können aber nur in der Weise wirken, dass sie dem BIOS-Erzeuger bekannte Bootsektoren mit dem aktuellen vergleichen. Da meine Bootsektoren jedenfalls deutlich anders als M$-Produkte sind, würden sie dann verworfen werden. Jeder kann aber anders als bei M$-Produkten prüfen, was meine Programme tatsächlich treiben und deshalb solche Virenscanner sorglos ausschalten, wenn er nicht M$-Produkte booten will.
Kurzum: wer deutliche Probleme hat, FDOS oder ASMOS zu booten, hat entweder dumme Fehler gemacht oder die falsche Maschine.

Weitere Einzelheiten und Besonderheiten entnimmt man den Quelltexten oder weiterer Dokumentation, die Teil der Quelltextarchive ist und die ich stetig fortschreibe.
  • Hier kann man zur Hauptseite wechseln: www.rcfriz.de
    Wie man Programm gestalten muss, das unter ASMOS als Treiber oder sonstwas arbeiten soll, habe ich mit einigen Beispielen klar gemacht. Sie enthalten auch die nötige Erklärung. Auch Anfänger werden keine Probleme haben, eigene Erweiterungen zu schreiben. Es gibt keinerlei Einschränkungen, nach Belieben mit Gerät oder Dateiinhalten umzugehen! Die Verknüpfung mit bereits vorhandenem Code ist denkbar einfach. Beispiele für Schleifen, Fallabfragen, Wandlungen und indizierte Sprünge oder Rufe sind zahlreich vorhanden. Und wer etwa ein Dutzend Assemblerkommandos im Kopf behalten kann, wird schnell Erfolg haben.
    Schliesslich biete ich drei Assembler-Übersetzungsprogramme namens ASMn, ASMnr und ASMat an, die nur unter ASMOS laufen. ASMn übersetzt mit einigen Einschränkungen den NASM-Assembler-Dialekt, in dem meine Betriebssysteme und auch diese drei Programme geschrieben wurden. ASMnr übersetzt für den real mode geschriebenen Quellcode im NASM-Dialekt. ASMat übersetzt einen von mir entwickelten neuen Dialekt, der vom NASM-Dialekt abgeleitet ist, aber sowohl die Assemblierung schneller macht, als auch die Schreibarbeit im Zusammenspiel mit meinen Editoren deutlich vereinfacht. Woanders beschreibe ich die Programme genauer...
  • ...hier in einer Kurzfassung der Datei "ASMdocD" online.
    Mit diesen Programmen kann man Programme für ASMOS schreiben, ohne das Betriebssystem wechseln zu müssen. Wer also das Fehlen von Programm für den Umgang mit Geräten bemängelt, kann sich das selber schreiben. Er hat nicht nur weniger zu schreiben und keine Systemphilosophie zu beachten - er kann es vor allem mal einfacher testen und zum Laufen bringen als mit irgendeinem anderen Betriebssystem.
    Mit ASMn kann man sogar für andere Ziel-Betriebssysteme schreiben, weil wenige Änderungen genügen, um den Quelltext mittels NASM übersetzbar zu machen.