Das eigene Betriebssystem – Teil 1

Edit: Dieses Tutorial is veraltet, hier gibt es die neue Version.

Hallo liebe Leute,
heute wollen wir uns mal an etwas besonderes wagen und zwar an die Entwicklung eines kleinen Betriebssystems. Natürlich werden wir damit nicht an Windows / Linux rankommen (noch nicht mal an DOS) aber immerhin erhaltet ihr einen kleinen Einblick darin, wie der Ladeprozess eines Betriebssystems funktioniert, was alles in einem Kernel steckt und wieviel Arbeit es sein muss etwas Windows-ähnliches auf die Beine zu stellen 😉

Was benötige ich?
Zur Entwicklung brauchen wir im Prinzip nur ein Tool, und zwar einen Assembler der aus dem Assemblercode den wir schreiben werden Maschinenbytecode generiert. Ich empfehle NASM da dieser Assember frei verfügbar ist.
Jetzt könnt ihr entweder im Notepad entwickeln oder ihr besorgt euch eine vernünftige Umgebung mit Syntaxhighlighting wie etwa Notepad++. Gut, jetzt haben wir alles was wir zum Programmieren brauchen, aber wie testen wir was wir geschrieben haben? Da haben wir im Prinzip 2 Möglichkeiten:

  1. Wir besorgen uns einen alten PC und spielen unser Geschreibsel jedesmal auf eine Diskette um es dann laufen zu lassen.
  2. Wir besorgen uns eine Virtual Machine. Eine Virtual Machine simuliert einen Computer samt Hardware und BIOS. Die meisten haben virtuelle Laufwerke in die wir Images einlesen können, unabhängig davon ob in das Disketten- oder ins CD-Laufwerk. Als Virtual Machine empfehle ich Virtual PC von Microsoft da es schlank und kostenlos zu haben ist. Wer sich schon ein bisschen auskennt kann natürlich auch zu dem kostenfreien VM Ware Player oder zu Bochs greifen.

Dann brauchst du noch das kostenlose Tool RaWrite damit du Diskettenimages erstellen kannst um diese dann in die Virtual Machine zu laden und/oder auf Diskette zu schreiben.

Noch einige Theorie vorweg
Was passiert eigentlich wenn wir unseren PC starten? Das BIOS schaut welche Hardware es findet, initialisiert diese und guckt dann auf den Laufwerken die es gefunden hat ob es auf irgendeinem einen Bootloader findet. Ein Bootloader ist ein Prgramm, dass auf den ersten 512 Bytes eines Datenträgers liegt. Wenn dieses am Ende eine Signatur mit dem HexCode “0x055AAh” aufweist, dann identifiziert das Bios dieses Laufwerk als Bootlaufwerk. Hierbei ist es egal ob es sich dabei um ein Diskettenlauferk, eine Festplatte, einen USB-Stick oder ein CD/DVD-Laufwerk handelt! Ist nun unser vermeidliches Betriebssystem gefunden, läd es das Bios automatisch an die Adresse “0x7C00h” des Speichers.

Jetzt wird programmiert!
Wir beginnen mit dem Bootloader.

ORG 0x7C00
;Sorgt dafür, dass unsere Speicherverwaltung funktioniert.
;ORG setzt die Startadresse des jeweiligen Segments.
;Wird ORG nicht angegeben, startet das Programm bei der
;Adresse 0.
 
;-------------------------
;Bootloader
;-------------------------
jmp 0x0000:start
start:
 
;Zuerst bauen wir uns einen Stack, wer nicht weiß
;was genau das ist sollte hier mal nachlesen
;http://de.wikipedia.org/wiki/Stapelspeicher.
;Allerdings würde ich dann eher raten, dass ihr eure
;Nase vor diesem Tutorial lieber erstmal in folgenden
;Crashkurs steckt:
;http://www-ivs.cs.uni-magdeburg.de/bs/lehre/
;sose99/bs1/seminare/assembler.shtml
 
cli
;Wir lassen keine Interrupts (Unterbrechungen /
;Sprünge im Programm) zu damit das Erstellen
;unseres Stacks nicht unterbrochen werden kann!
 
mov ax,0x9000 ;Hier legen wir unseren Stack ab
mov ss,ax     ;SS ist nun die Adresse unseres Stacks
mov sp,0      ;SP ist der Stackpointer, er zeigt im Moment
              ;auf 0, also auf 0x9000
sti           ;Interrupts werden nun wieder zugelassen
 
;Nun initialisieren wir unser Segmentregister (Einen Teil haben
;wir mit demStack-Segment (ss) schon initialisiert).
mov ax, 0x0000
mov es, ax ;Wir setzen das Extra Segment auf 0x0000
;Das Extra Segment ist dafür da um einzuspringen falls das Data
;Segment(DS) nicht genügend Speicher bietet.
 
mov ds, ax ;Ebenso das Data Segment das für Variablen,
           ;Konstanten etc. da ist.
 
;Hier holen wir aus dem DL-Register (Ein einfaches
;Zwischenspeicherungsregister) die ID unseres
;Bootlaufwerks und schreiben sie in die Variable
;"bootdrv". (Achtung, diese wird erst später deklariert!)
mov [bootdrv], dl
 
;Nun rufen wir die FUnktion "load" auf und laden damit
;unseren Kernel (Was das ist, wird später erklärt)
call load
 
;Nun setzen wir unsere Arbeitsregister auf die
;Startadresse unseres Kernelprograms
mov ax, 0x1000    ;0x1000 ist besagte Adresse
mov es, ax        ;Auch das Register von eben nehmen wir mit
mov ds, ax
jmp 0x1000:0x0000 ;Nun springen wir zum Anfang des Kernels
 
;-------------------------
;Variablendeklaration
;-------------------------
bootdrv db 0 ;Wie schon oben gesehen erstellen wir
             ;hier eine Variable in die wir später
             ;die ID unseres Bootlaufwerks schreiben
loadmsg db "Laden...",13,10,0 ;Diese Variable enthält einen
                              ;String der beim Laden des
                              ;Beriebssystems angezeigt werden
                              ;soll. Die 13, die 10 und die 0
                              ;stehen für ein Return (13 + 10)
                              ;und die 0 für das Ende des
                              ;Strings (0-Byte).
 
;-------------------------
;Ein paar Funktionen
;-------------------------
;Hier definieren wir nun eine Funktion um einen String
;auszugeben
putstr:
lodsb ;Läd ein Byte aus dem AL Register. Dafür müssen
      ;wir zuerst das SI Register auf das erste Byte
      ;des Strings setzen das wir ausgeben wollen.
      ;Für alle die sich wundern wo das ganze
      ;passiert: Später ;) Schaut einfach mal weiter
      ;runter im Code!
or al,al ;Der logische Operator "OR" schaut hier nach
         ;ob im AL Register eine 0 steht, sprich ob
         ;das Ende des Strings erreicht ist. Wenn AL 0
         ;ist, ergibt das Ergebnis des Oder-Vergleichs
         ;0 und...
jz short putstrd ;...wir springen aus der Schleife raus
mov ah, 0x00E ;Funktion zur Zeichenausgabe
mov bx, 0x0007 ;Farbatribut des Zeichens
int 0x10      ;Schreibt das Zeichen
jmp putstr    ;Und springt zum nächsten Byte
putstrd:
retn          ;Ende der Funktion zu Ausgeben von Strings
 
;Laden des Kernels vom Bootlaufwerk
load:
mov ax,0         ;Zurücksetzen des Diskdrives
mov dl,[bootdrv] ;Gewünschtes Laufwerk ins dl-Register
                 ;schreiben
int 13h          ;Interrupt ausführen um Auf Laufwerk
                 ;zugreifen zu können
jc load          ;Falls das nicht funktioniert hat,
                 ;nochmal probieren!
 
load1:
mov ax,0x1000
mov es,ax
mov bx,0
 
;Hier lesen wir den 2. Sektor nach dem Bootsektor aus
;Die folgenden Angaben sind nötig um das Areal auf dem Speicher
;zu lesen das das Segment enthält
mov ah, 2 ;Wir benutzen Funktion "2" (Lesen)
mov al, 5  ;Und zwar lesen wir 5 Sektoren
mov cx, 2  ;in Sektor 2
mov dh, 0  ;Gibt den Head des Speichers an
mov dl, [bootdrv] ;Wir schreiben unser Bootlaufwerk wieder
                  ;ins DL Verzeichnis
int 13h  ;...und schalten wieder einen Interrupt um aus
         ;dem Speicher lesen zu können
jc load1 ;Falls das schiefging probieren wir es nochmal...
mov si,loadmsg ;Hier zeigt nun das SI-Register auf das erste
;Byte aus "loadmsg" so wie wir es für "lodsb" brauchen
call putstr ;Nun geben wir unsere "Laden..."-Meldung aus
retn
 
;Hier wird es nochmal interessant. Wir schreiben nun
;unseren Datenträger auf dem der Code vorhanden ist mit
;Nullen voll und ganz ans Ende kommt das benötigte Wort
;"0x0AA55h". Wir erinnern uns: Dieses Wort idenzifiziert
;unseren Bootloader.
 
times 512-($-$$)-2 db 0
dw 0AA55h

Na das war doch garnicht soooo schwer! Wer noch ein paar mehr Erklärungen haben möchte ist herzlich eingeladen sich die Links am Ende dieses Artikels anzugucken. Speichert diese Datei als boot.asm ab!

Was kommt jetzt, Colonel? Na klar, der Kernel!

Damit user Bootloader auch was zu tun bekommt brauchen wir noch einen Kernel. Ein Kernel ist der Kern eines jeden Betriebssystems, quasi die Logik die die Resourcen verwaltet die die Hardware bietet.

;-------------------------
;Kernel
;-------------------------
 
;Zu beginn updaten wir wie immer unsere Segmentregister
mov ax, 0x1000
mov ds, ax
mov es, ax
 
;Hier zeigen wir mal 2 Messages an, alles nix neues
start:
mov si, msg
call putstr
 
mov si, msg_boot
call putstr
 
call getkey ;Hier warten wir auf einen Tastendruck
jmp reboot ;Wenn dieser gekommen ist, dann reboote
 
;-------------------------
;Variablen
;-------------------------
msg db "Herzlich willkommen bei deinem ersten Betriebssystem",13,10,0
msg_boot db "Drücke eine Taste um neuzustarten",10,0
 
;-------------------------
;Funktionen
;-------------------------
 
;Die kommende Funktion kennen wir schon, müssen sie aber
;nochmal schreiben, da wir keinen Verweis auf die andere
;Assemblerdatei haben in der wir die Funktion schonmal
;geschrieben haben
putstr:
or al,al
jz short putstrd
mov ah,0x0E
mov bx, 0x0007
int 0x10
jmp putstr
putstrd:
retn
 
;Funktion zum Warten auf den Tastendruck
getkey:
mov ah, 0 ;Funktion "0" (Warten auf Tastendruck)
int 0x16  ;Ausführen der Funktion
ret
 
;Neustarten
reboot:
jmp 0xffff:0x0000

Speichert diese Datei nun als kernel.asm ab! Na also, das war schon alles! Nun müssen wir die 2 Dateien noch in bytecode umwandeln, das ganze machen wir mit nasm über die Konsole:

C:nasm -f bin -o boot.bin boot.asm
C:nasm -f bin -o kernel.bin kernel.asm

Nun schreiben wir die beiden Dateien zusammen zu einem Image mit folgendem Befehl:
C:copy /b boot.bin + kernel.bin meinOS.img

Mit dem Tool Rawrite schreiben wir nun die Daten auf Diskette:
C:rawwritewin (und den Anweisungen folgen)

Nun einfach Diskette einlegen und booten! Oder ihr bindet das Image in eure Virtual Machine ein und startet diese, auch das sollte auch gut funktionieren 😉

Linkliste

Wer es nicht erwarten kann bis das nächste Tutorial rauskommt kann sich auf folgenden Links schlauer lesen:

http://www.tutorials.de/forum/programming-tutorials/20706-ein-eigenes-kleines-betriebssystem.html
http://osix.net/modules/article/?id=359
http://www.nondot.org/sabre/os/articles
http://moos.cvs.sourceforge.net/viewvc/moos/moOS/
http://www.osdever.net/bkerndev/index.php
http://lowlevel.brainsware.org/wiki/index.php/Ausgabe_1
http://lowlevel.brainsware.org/wiki/index.php/Eigener_Boot_Record
http://jumpsite.kilu.de/

Im nächsten Tutorial wollen wir ein Menü erstellen in dem wir mehrere Auswahlmöglichkeiten haben anstatt nur eine Taste zu drücken!

4 thoughts on “Das eigene Betriebssystem – Teil 1”

  1. Kam so wenig Resonanz drauf da hab ich aufgehört… Mal sehen vielleicht mach ich in der kommenden Zeit noch weiter. Falls dich das Thema sehr interessiert leg dir das Buch “Operating Systems – Design and Implementation” zu, das ist super 😉

  2. Ich hab mittlerweile was eigenes Zusammen getippt (deines ging irgendwie nicht) aber währe schön wenn du trotz dem weiter machen würdest…

  3. Hallo lieber jofre,
    ich habe dein Tutorial bis auf das kleinste Detail nachgemacht, aber mein WinXP macht das *.img-File aber Virtual Box nimmt das nicht an genausowenig wie Bochs!? Kannst du mir das fertige *.img-File geben oder als Appliance exportieren etc.?
    Das wäre sehr schön. Einfach über email die du ja sehen müsstest!

Comments are closed.