1Zum Geleit
Ich werde in diesem Tutorial nicht beschreiben, wie man ein komplettes Betriebssystem programmiert, das mit Windows oder gar Linux gleichziehen kann. Das wäre auch etwas zu viel für diese Seite – und außerdem würde das auch kein Einsteiger-Tutorial mehr bleiben.
Vorkenntnisse in Assembler sind für dieses Tutorial sicher von Vorteil, wenn auch nicht zwingend notwendig. In jedem Fall solltet Ihr aber wissen, wie ein Computer arbeitet.
Um die Beispielcodes aus diesem Tutorial zu benutzen braucht Ihr erst mal ein paar kleine Programme. Die direkten Links kenn ich nicht, aber bei einer Suche mit Google werdet Ihr da mehr als genug finden. Es werden benötigt:
- Netwide Assembler (NASM)
- RaWrite oder irgendein anderes Programm, mit dem man Images auf Disketten schreiben kann
- Eine leere Diskette
- Gesunder Menschenverstand und Kaffee
Ein nicht unerheblicher Teil des nötigen Codes aus diesem Tutorial wird in Assembler geschrieben. Da man mit Assembler viel machen (und noch mehr kaputt machen) kann, übernehme ich für eventuelle Schäden an Eurem Computer keine Verantwortung. Wer seinen selbstgeschriebenen Bootloader auf die Festplatte schreibt und deswegen nicht mehr an sein richtiges System kommt, der ist selber Schuld!
2Grundlagen
Um zu verstehen, wie ein Betriebssystem arbeitet, muss man erst mal wissen, was in einem Computer genau passiert. Dazu geh ich jetzt einmal kurz auf das ein, was passiert, wenn man seinen Computer startet. Dabei bezieh ich mich natürlich nur auf die heute verbreiteten 80x86-Prozessoren und deren BIOS-Versionen.
Beim Anschalten eines Computers wird als erstes das BIOS gestartet, welches die Hardware initialisiert. Sobald die wichtigen Komponenten (CPU, RAM, etc.) gefunden wurden, ruft das BIOS das eigentliche Betriebssystem auf. Das spätere Betriebssystem kommuniziert dann mit dem BIOS um Befehle an die CPU zu schicken.
Im BIOS kann eingestellt werden, von welchem Laufwerk zuerst gebootet werden soll. Wenn von dem Laufwerk nicht gebootet werden kann, wird versucht, von dem nächsten Laufwerk zu booten. Und so weiter. Bei den meisten BIOS-Versionen kann man drei Laufwerke in eine Reihenfolge setzen. Für unser Betriebssystem setzen wir das Diskettenlaufwerk nach vorne.
Das soll aber erst mal genug Theorie sein – wir fangen jetzt mal an, das eigentliche Betriebssystem zu programmieren.
3 Ein erster Kernel
Eigentlich wollte ich das eigentliche Betriebssystem ja ganz gerne in C schreiben, aber da die Header-Dateien jeweils an ein bestimmtes Betriebssystem gebunden sind, können wir in unserem Kernel keine Funktionen einbinden. Wir schreiben unseren Kernel also mit Assembler.
Der "Kernel" kann zwar eigentlich nur eine Meldung anzeigen und den Computer neu starten, aber das ist auch schon etwas. Der Code für unser ganzes Betriebssystem sieht folgendermaßen aus:
Code:
mov ax, 1000hmov ds, axmov es, axstart: ; Hier fängt unser eigentliches "Betriebssystem" anmov si, nachricht ; Wir zeigen einfach nur einen String ancall schreiben ; "schreiben" gibt den String am Bildschirm auscall lesen ; "lesen" wartet bis eine Taste gedrückt wurdejmp reset ; Danach wird die Funktion "reset" aufgerufen
Die Funktionen "schreiben", "lesen" und "reset" müssen wir allerdings noch selber schreiben. Und die Variable "nachricht" muss ja auch irgendwo stehen. Also muss hinter den Code von oben noch folgendes:
Code:
nachricht db "Eine Taste drücken, um neu zu starten...",13,10,0
Diese Zeile speichert einen String in der Variable "nachricht", die im eigentlichen Programm benutzt wird. Um den Inhalt der Variable auch tatsächlich auf dem Monitor auszugeben, definieren wir die Funktion "schreiben":
Code:
schreiben:lodsbor al, aljz short schreiben_dmov ah, 0x0Emov bx, 0x0007int 0x10jmp schreibenschreiben_d:retn
Dann natürlich noch eine Funktion, die einen Tastendruck abfängt:
Code:
lesen:mov ah, 0int 016hret
Und zum Schluss schicken wir noch einen Befehl zum neu starten an den Prozessor:
Code:
reset:db 0Eahdw 0000hdw 0FFFFh
Diesen Code speichern wir irgendwo und nennen die Datei kernel.asm. Diese Datei kompilieren wir nicht zu einer normalen ausführbaren Datei, sondern nur zu einer rohen Binärdatei. Der Aufruf für NASM ist dabei wie folgt:
Code:
nasm –f bin –o kernel.bin kernel.asm
4Ein Bootmanager
Die alles entscheidende Frage, die jetzt aufkommen dürfte, ist sicher "Wie kann ich meinen Kernel jetzt booten?". Die Antwort darauf lautet zwar nicht 42, aber dafür 512.
Im zweiten Teil hab ich schon erklärt, dass das BIOS von einem bestimmten Datenträger bootet, und das führe ich jetzt weiter aus:
Die Diskette (und überhaupt jeder andere Datenträger auch) auf dem unser Betriebssystem liegt, ist in Sektoren unterteilt. Jeder Sektor ist genau 512 Bytes groß. Wenn das BIOS auf dem ersten Sektor eines Datenträgers eine 512 Bytes große Binärdatei findet, die mit 0x055AAh aufhört, dann stellt diese Datei den Bootsektor dar und wird vom BIOS in die Speicheradresse 0x7C00 geladen.
Mit anderen Worten: Wir brauchen ein 512 Bytes großes Programm, das unseren Kernel aufruft und im ersten Sektor der Diskette liegt. Und dieses Programm schreiben wir uns jetzt.
Als erstes legen wir fest, dass das Programm in der Speicheradresse 0x7C00 startet:
Code:
org 0x7C00
Danach startet der eigentliche Bootloader. Zuerst basteln wir uns einen Stack, dessen Adresse wir auf 0x9000 legen. Den Stackpointer setzen wir dabei auf 0. Während wir unseren Stack zusammenbauen, dürfen wir KEINE Interrupts verwenden!
Code:
start: cli ; Keine Interrupts verwenden!mov ax, 0x9000 ; Adresse des Stack speichernmov ss, ax ; Stackadresse festlegenmov sp, 0 ; Stackpointer auf 0 setzensti ; Jetzt lassen wir wieder Interrupts zu
Wenn wir unseren Stack haben, speichern wir das Laufwerk, von dem aus gebootet worden ist...
Code:
mov [bootdriv], dl
Und jetzt rufen wir die Funktion auf, die unseren Kernel lädt...
Code:
call load
Wenn unsere "Shell" geladen worden ist, springen wir dort hin...
Code:
mov ax, 0x1000 ; 0x1000 ist die Speicheradresse unserer Shellmov es, axmov ds, axpush axmov ax, 0push axretf
Dann definieren wir noch ein paar Funktionen und Variablen...
Code:
bootdriv db 0 ; Das Bootlaufwerk loadmsg db "Lade VitaXia...",13,10,0; Mit dieser Funktion geben wir einen String ausputstr:lodsbor al,aljz short putstrdmov ah,0x0Emov bx,0x0007int 0x10jmp putstrputstrd:retn ; Mit dieser Funktion laden wir unsere Shell vom Bootlaufwerkload:push dsmov ax, 0mov dl, [bootdriv]int 13hpop dsjc loadload1:mov ax,0x1000mov es,axmov bx, 0mov ah, 2mov al, 5mov cx, 2mov dx, 0int 13hjc load1mov si,loadmsg call putstrretn
Und ganz zum Schluss sorgen wir dafür, dass unser Bootsektor nicht größer ist als 512 Byte und am Ende den Wert 0x0AA55h steht.
Code:
times 512-($-$$)-2 db 0dw 0AA55h
Diesen Assembler-Code nennen wir boot.asm und speichern wir im gleichen Verzeichnis wie den Code unseres Kernels. Dann assemblieren die Datei mit NASM ebenfalls zu einer rohen Binärdatei:
Code:
nasm –f bin –o boot.bin boot.asm
5Und jetzt?
Jetzt, wo wir einen "Kernel" und einen Bootloader haben, wollen wir das natürlich auch ausprobieren. Dazu kopieren wir erst mal beide Binärdateien zusammen in eine Image-Datei:
Code:
copy boot.bin+kernel.bin vitaxia.img
Als letzten Schritt schreiben wir dieses Image mit RaWrite auf eine Diskette. Alle Daten auf der Diskette gehen dabei verloren und formatiert ist die Diskette dann auch nicht mehr!
Diese Diskette legen wir ein und starten den Computer neu. Danach müsste das eigene Betriebssystem gestartet werden.
Das ganze ist natürlich nur ein kleines Beispiel, wie man ein Betriebssystem programmieren kann. Wenn man den Kernel erst mal gebootet hat, kann man später auch mit C oder C++ weiter programmieren. Das Problem ist einfach nur, dass die Funktionen printf() und scanf() nicht Bestandteil der Sprache selber sind, sondern in der Headerdatei stdio.h stehen. Und in dieser sind die Funktionen abhängig von einem bestimmten Betriebssystem.
Man wird also erst mal nicht um Assembler herum kommen. Zumal damit auch ein schnellerer und besserer Zugriff auf die Hardware möglich ist.
Ich werde später noch genaueres darüber schreiben, wie man die richtigen Aufgaben eines Betriebssystems realisiert. Aber bis dahin wird das hier als Einstieg erst mal reichen müssen. Kritik bitte per PM an mich. Ansonsten viel Spass damit.