Ein eigenes Betriebssystem schreiben
Das wohl interessanteste Thema in der Programmierung.
Wohl jeder Programmierer träumt davon, einmal ein OS zu schreiben.
Nun, in diesem Tutorial versuche ich genau dieses einfach zu erklären!
Anfangen werde ich mit der Theorie.
Dann kommt wohl ein erster Bootloader.
OS Coding - Teil 1 - Grundlagen
Teil 1.1 Definition
Teil 1.2 Voraussetzungen
Teil 1.3 Bootvorgang
OS Coding - Teil 1.1 Definition
Anstatt von "Betriebssystem" hört man auch oft "OS".
Diese Abkürzung kann mitunter für leichte Verwirrungen sorgen, da "OS" entweder "Operating System" oder "Open Source" heißen kann.
In diesem Tutorial meint es immer "Operating System".
Operating System ist übrigens Englisch und bedeutet Betriebssystem.
Wenn der willige Coder so im Internet surft und nach Infomaterial zu OS Programmierung sucht, wird er des öfteren Texte finden, die ein OS definieren.
Da heißt es dann z.b.:
Ein OS muss ein gutes Dateisystem haben...
Ein OS muss Multitasting fähig sein...
Ein OS muss entsprechende Treiber für alles mögliche aufweisen (z.b. USB)...
Ein OS muss Systemcalls zur Verfügung stellen...
Bla bla bla
Ich könnte diese Liste unendlich weiter führen.
Letzendlich führen solche Texte dazu, dass der Leser unsicher ist, ob sein eigenes OS überhaupt ein echtes Betriebssystem ist.
Es hat ja keinen einzigen Treiber, läuft nicht im Protected Mode, hat ja nicht mal ein Dateisystem!
Um solche Zweifel aus der Welt zu schaffen, hier mal ein Zitat aus einem Lexikon aus den älteren jahren:
Unter einem Betriebssystem versteht man die Software, die überhaupt vorhanden sein muss, damit auf dem Rechner Anwenderprogramme laufen.
Wie man aus diesem Text wunderbar rauslesen kann, steht da nix drinn von Dateisystem, Multitasking,... .
Und was nun "Anwenderprogramme" sind, ist auch Definitionssache!
Wenn mein OS nur dazu da sein soll, um Texte zu schreiben und diese aus dem RAM zu drucken, wozu benötige ich dann Systemcalls, Multitasking oder gar Soundkartentreiber?
D.h. ein Betriebssystem kann etwas großes sein, wie z.b. Linux oder auch nur ein Bootloader, der einen Text ausgibt!
Und sowas zustande zu kriegen ist dann auch nicht mehr schwer .
Unser OS wird übrigens (hoffentlich :-) ) dazu da sein, beliebige Programme zu starten.
Die Programme sind einfach und ohne Header (oder nur ein 2 Byte Header) aufgebaut.
Beliebige Programme von einem eigenen OS zu starten ist übrigens überhaupt nicht schwer, wie ihr später sehen werdet!
So, dass war schonmal die Definition eines Betriebssystemes.
Im Teil 1.2 werden die Voraussetzungen besprochen.
OS Coding - Teil 1.2 Voraussetzungen
Es werden auf jeden Fall Assemblerkenntnisse sinnvoll sein.
Wer kein Assembler kann, kann sich ja meine Assembler Grundkurs Tutoriale ansehen.
Auf einigen Seiten wird ja behauptet, man könne auch ohne ASM programmieren, z.b. in C.
Ob das aber stimmen mag?
Nun, meiner Ansicht nach nicht! Man kann zwar auf einfache Art und Weise Schleifen und IF-Abfragen etc. machen, sobald man aber z.b. einen Text ausgeben möchte (oder sei es nur ein Zeichen), muss man auf ASM zurückgreifen.
Wieso? Nun, bei C sind ja in den Headerdateien alle Funktionen definiert.
Nun sind diese Funktionen aber (bei Windows) wahrscheinlich mit dem INT 21h programmiert!
d.h. man muss die ganzen Headerdateien, bzw die Funktionen, die man benötigt, mit Assembler auf seine Bedürfnisse coden.
EIn möglicher Weg wäre eventuell den gesamten INT 21h nachzubilden, aber wo bliebe dann die Individualität?
Das heißt im Endeffekt: Man kan nicht ohne ASM ein OS coden.
So, kommen wir zu den anderen Voraussetzungen. Wir benötigen:
Einen Assembler
Einen Texteditor
Am besten einen Emulator
Eine Interruptliste
Zum Assembler:
Ich würde hier den FASM empfehlen.
Ich arbeite nur mit diesem an meinem OS.
Eine Alternative wäre der NASM, den ich aber noch nicht ausprobiert habe.
Beide Assembler müssten kostenlos zu beziehen sein.
Der NASM bei Sourceforge und für den FASM einfach mal googlen.
Zum Texteditor:
Es reicht ja eigentlich der Notepad von Windows aus.
Da aber ein OS etwas mehr Code werden kann, wäre vielleicht ein etwas komfortablerer Editor praktisch.
Hier würde ich den "gvim" von Linux vorschlagen.
Ich weis allerdings nicht, ob es ihn auch für Windows gibt.
Aber normalerweise schon.
Zum Emulator:
Ein Emulator ist ein Programm, dass z.b. einen speziellen CPU emuliert.
So ist es damit möglich, unser Betriebssystem mit einem Emulator zu starten.
Der Vorteil liegt auf der Hand: Man muss nicht jedesmal den Rechner rebooten sondern kann das OS direkt von der normalen Arbeitsumgebung testen.
Ich arbeite mit Bochs. Dieser ist Opensource und auch auf Sourceforge zu finden.
Zur Interruptliste:
Zwar nicht notwendig, aber nützlich.
Weil da wir ja den DOS Interrupt 21h nicht zur Verfügung haben, müssen wir auf die BIOS Interrupts zurückgreifen.
Diese sind aber höchstwahrscheinlich nicht so bekannt, weshalb es empfehlenswert wäre, im Internet nach einer Liste zu suchen.
Ralf Brown's Files
finden.
So, dass waren eigentlich schon alle Voraussetzungen.
Ein paar starke Nerven wären noch empfehlenswert .
OS Coding - Teil 1.3 Bootvorgang
Um mal kurz den wichtigen Teil des Bootvorgangs zu beschreiben:
Das Bios durchsucht in jedem Laufwerk (CDROM,FD,HD) den 1. Sektor.
Weist dieser Sektor die Bootsignatur (siehe unten) auf, wird dieser Sektor in den RAM geladen.
Wichtig ist hierbei, dass das BIOS nur einen Sektor in den RAM lädt.
Sollte der Bootloader größer sein als der Sektor, muss der Rest des Bootloaders von Hand nachgeladen werden.
BTW: Im Allgemeinen lädt der Bootloader nur den Kernel und macht sonst nix.
Wir werden übrigens von der Diskette booten.
Warum? Nun, die Ansteuerung ist einfacher und die Handhabung auch.
Doch wie genau funktioniert das Booten von Diskette?
Diese Frage soll nun genauer betrachtet werden:
Wenn das BIOS im 1.Sektor der FD (=Floppy Disk) eine Datei findet, die in den 2 letzten Bytes die Bootsignatur 0AA55h enthält, wird der gesamte Sektor in den RAM geladen.
Da ein Sektor 512 Bytes umfasst und die Bootsignatur 2 Bytes benötigt, hat man 510 Bytes für den Botloader zur Verfügung.
Die 512 Bytes werden also in den RAM geladen.
Natürlich nicht irgendwo hin, sondern an die Stelle 07c0h:0000h
Ich hatte anfangs leichte verständnisprobleme mit der Adressierung und lud die Segmentregister mit 0h und sprang dann an die Stelle CS:07c0h.
Eigentlich ist es ja egal, welche Schreibweise man verwendet, aber es gibt einen wesentlichen Unterschied:
Eine RAM Adresse wird ja immer in der Form x:y angegeben.
Wenn nun x das CS ist, folglich also 0 enthalten würde, MUSS y mit 07c0h geladen werden!
--> CS:07c0h
Wer nun etwas kombiniert, erkennt, dass er nicht mehr die vollen 64kb, die normalerweise zur Verfügung stehen würden, ausnutzen kann.
Im gehen 07c0h=1984 Bytes verloren.
Letzendlich ist es theoretisch egal, da das OS nie 64kb groß wird und man außerdem CS mit einem neuen Wert laden könnte bei Bedarf.
Aber warum sollte man den Platz verschenken .
Naja, bevor ich weiter vom Thema abschweife:
Ihr habt vielleicht schon mal gelesen, das manche BIOSe die Bootsignatur nicht überprüfen, folglich also den Sektor immer laden.
Dies kann sicherlich sein, man sollte sie allerdings immer angeben, weil nicht garrantiert werden kan, dass der gewillte Nutzer deines OSes ein solches BIOS besitzt.
So, das war der komplette 1. Teil des Tutorials. Ich hoffe, ich habe nirgendwo etwas relevantes vergessen.
Wenn irgendwo ein Fehler auftaucht, Kommentar oder PN oder Mail oder von mir aus auch einen Brief schreiben
OS Coding - Teil 2 - Ein erster Bootloader
Codes und Binärdatei:
Bild:
Also, nachdem ihr die Theorie hoffentlich verstanden habt, kommt nun der langersehnte erste Bootloader.
Den Code dafür erreicht ihr über den obigen Link.
Ich möchte an dieser Stelle nochmal darauf hinweisen, dass ich für den FASM kompatiblen Code schreibe.
Soweit ich weis, muss der Code für den NASM zumindest teilweise abgeändert werden.
Aber gut, fangen wir an:
PHP:
org 0
jmp 07c0h:s
s:
mov ax,7c0h
mov ds,ax
mov es,ax
Wenn man den Code so ansieht, könnte man denken, dass er vielleicht irgendwie sinnlos sei.
Dem ist aber nicht so!
Das org 0 ist ja klar, damit wird festgelegt, dass der FASM die Adressen ab Offset 0 berechnen soll.
Nur warum wird denn dann zu 07c0h:s gesprungen?
Nun, der Bootloader wird ja an die Adresse 07c0h:0000h geladen.
Nun ist es aber so, dass jedes Segmentregister mit 0 initialisiert ist.
Nun benötigen wir aber gültige Segmentregisteranfänge.
Deshalb springen wir zuerst an die Stelle 07c0h:s (damit wird das CS mit dem richtigen Wert geladen) und kopieren dann die gleiche Startadresse in DS und ES.
PHP:
;### Bootstrings ausgeben
;Die Beiden gleichscheindenden Funktionen geben lediglich 2 Strings
; aus.
lea bx,[bootstr]
pr_str6:
cmp byte [bx],0
JE ende
mov ah,0eh
mov al,[bx]
int 10h
inc bx
jmp pr_str6
ende:
lea bx,[boot_ok]
pr_str66:
cmp byte [bx],0
JE ende6
mov ah,0eh
mov al,[bx]
int 10h
inc bx
jmp pr_str66
ende6:
jmp $
bootstr db "Lade OS v0.01...",13,10,0
boot_ok db "OK.",13,10,0
db 510-$ dup(0)
dw 0aa55h
In diesem Stück Code passiert nun eigentlich nichts interessantes mehr.
Hier werden lediglich 2 Messages ausgegeben.
Danach hängt sich das Programm in einer Endlosschleife auf (jmp $).
Das hier übrigens:
db 510-$ dup(0)
dw 0aa55h
sollte nie vergessen werden im Bootloader.
Das db 510-$ dup(0) ist jetzt nicht so wichtig, es sorgt nur dafür, dass der Bootloader 510 Bytes groß ist.
Das kann man schon weglassen, nur muss man dann die Bytes von Hand zählen, und das wird schon eher eine Plagerei.
Das dw 0AA55h hingegen muss immer drinnstehen.
Das ist eben die Bootsignatur.
Tja, und was machen wir nun damit, wenn wir den COde geschrieben haben?
Richtig! Wir jagen ihn durch den Assembler mittels fasm os.asm
Man braucht beim FASM kein extra Outputfile oder Dateiformat angeben.
Es wird so automatisch eine OS.BIN im gleichem Verzeichnis erstellt.
Diese kann man nun mittels BOCHS (und Konsorten) emulieren, oder auf eine Diskette in den 1. Sektor bannen und den PC damit neu starten.
Ich möchte an dieser Stelle nochmals den Tipp geben, einen Emulator (wie z.b. Bochs) zu benutzen.
Manche sind zwar langsam, aber für OS lange ausreichend .
Da Bochs mitunter nicht einfach ist, habe ich hier mal eine Beispielkonfigurationsdatei.
Mit dieser bringe ich mein OS zum laufen:
PHP:
# how much memory the emulated machine will have
megs: 32
# filename of ROM images
romimage: file=../BIOS-bochs-latest, address=0xf0000
vgaromimage: file=../VGABIOS-lgpl-latest
# what disk images will be used
floppya: 1_44="C:\Dokumente und Einstellungen\JOE jun\Desktop\nasm\nos\os.bin", status=inserted
#floppyb: 1_44="C:\Dokumente und Einstellungen\JOE jun\Desktop\nasm\os.bin", status=inserted
ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
ata1: enabled=0
ata2: enabled=0
ata3: enabled=0
# choose the boot disk.
boot: floppy
##eigen
floppy_bootsig_check: disabled=0
vga_update_interval: 30000
keyboard_serial_delay: 20000
keyboard_paste_delay: 100000
floppy_command_delay: 50000
ips: 500000
text_snapshot_check: 0
private_colormap: enabled=0
i440fxsupport: enabled=0
clock: sync=none, time0=local
keyboard_mapping: enabled=1, map=..\keymaps\sdl-pc-de.map
keyboard_type: mf
#eigen ende
#floppy_bootsig_check: disabled=0
# default config interface is textconfig.
#config_interface: textconfig
#config_interface: wx
#display_library: x
# other choices: win32 sdl wx carbon amigaos beos macintosh nogui rfb term svga
# where do we send log messages?
log: bochsout.txt
# disable the mouse, since DLX is text only
mouse: enabled=0
pit: realtime=1
Wie man sieht, ein rechtes Durcheinander
Es ist halt viel vom Linuxbeispiel, das bei Bochs mit dabei ist kopiert und teils aus einem Tutorial, das Bochs näher beschreibt kopiert.
Auf jeden Fall funktioniert es