Ein eigenes kleines Betriebssystem | Tutorial (2025)

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. Ein eigenes kleines Betriebssystem | Tutorial (1)
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 eigenes kleines Betriebssystem | Tutorial (2)

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. Ein eigenes kleines Betriebssystem | Tutorial (3)
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. Ein eigenes kleines Betriebssystem | Tutorial (4)

Ein eigenes kleines Betriebssystem | Tutorial (2025)
Top Articles
Latest Posts
Recommended Articles
Article information

Author: Saturnina Altenwerth DVM

Last Updated:

Views: 6733

Rating: 4.3 / 5 (64 voted)

Reviews: 95% of readers found this page helpful

Author information

Name: Saturnina Altenwerth DVM

Birthday: 1992-08-21

Address: Apt. 237 662 Haag Mills, East Verenaport, MO 57071-5493

Phone: +331850833384

Job: District Real-Estate Architect

Hobby: Skateboarding, Taxidermy, Air sports, Painting, Knife making, Letterboxing, Inline skating

Introduction: My name is Saturnina Altenwerth DVM, I am a witty, perfect, combative, beautiful, determined, fancy, determined person who loves writing and wants to share my knowledge and understanding with you.