Revision 641417 of "Betriebssystementwicklung" on dewikibooks<div id="Vorlage_Loeschantrag" style="margin-left:5px;font-size:-1;border-width: 2px; border-style: solid; border-color:#FF0000">
{| border="0" cellspacing="8" cellpadding="0"
|
| [[Bild:Fairytale Trash Questionmark.png|32px]]
| '''Dieses Buch oder diese Seite wurde zur Löschung vorgeschlagen.'''
Dieses Buch oder diese Seite erfüllt nach Ansicht des Antragstellers die Qualitätskriterien der Wikibooks nicht – das Buch beziehungsweise die Seite sollte daher verbessert werden. Geschieht dies nicht, wird das Buch beziehungsweise die Seite nach Ablauf einer Frist von zwei Wochen gelöscht. Auf der unten angegebenen Seite kann gegen die Löschung Einspruch erhoben werden.
'''Begründung:'''
: ''zu wenig Inhalt und verwaist ---- [[Benutzer:Tandar|Tandar]]<small>([[Benutzer_Diskussion:Tandar|D]], [[Spezial:Beiträge/Tandar|B]])</small> 13:41, 6. Jun. 2012 (CEST) ''
'''[[Wikibooks:Löschkandidaten/ 2012-06#{{PAGENAME}}|Diskussion über den Löschantrag]]'''
<small>Vor dem Löschen bitte Interwiki Links prüfen: [http://de.wikipedia.org/w/index.php?title=Special%3ASearch&search=%7B%7BWikibooks%7C{{urlencode:{{FULLPAGENAME}}}}&fulltext=Suche W] [http://de.wikiversity.org/wiki/Spezial:Search?ns0=1&ns106=1&ns108=1%3ASearch&search=%7B%7Bb%7C{{urlencode:{{FULLPAGENAME}}}}&fulltext=Suche V] [http://de.wikiversity.org/wiki/Spezial:Search?ns0=1&ns106=1&ns108=1%3ASearch&search=%7B%7BWikibook%7C{{urlencode:{{FULLPAGENAME}}}}&fulltext=Suche V]</small>
|}{{#if: ||[[Kategorie:Wikibooks: Löschkandidaten|{{FULLPAGENAME}}]]}}
</div>
{{Regal|ort=EDV}}
{{TOCright}}
=Vorwort=
== Zusammenfassung des Projekts ==
* '''Zielgruppe:''' Erfahrene Programmierer
<!-- Für welche Leute soll das Buch geschrieben werden? z.B. "Bergsteiger" oder "EDV-Dozenten". Es gibt keine Bücher, die für Schüler der 9. Klasse und Professoren gleichermaßen geeignet sind. Welche Vorkenntnisse sind erforderlich? -->
* '''Lernziele:''' Programmierung eines Betriebssystems für die i386-Architektur und besseres Verständnis für die Arbeitsweise eines PCs
<!-- Was sollen die Leute aus der Zielgruppe nach dem Lesen des Buches gelernt haben? -->
* '''Buchpatenschaft / Ansprechperson:''' *niemand*
<!-- Sollten Anmerkungen und/oder Fragen zum Inhalt oder auch organisatorischer Art zum Buch bestehen, so kann es nützlich sein, einen Hauptautoren / eine Ansprechperson / Kümmerling / Buchpaten zu benennen. Auch kann hier der Kontakt genutzt werden, um nicht gleich bei einer Löschdiskussion - sondern eben weit davor - über den Inhalt zu diskutieren. Da wir Wiki-User an den Lehrbüchern in unserer Freizeit freiwillig arbeiten, kann es mitunter vorkommen, dass ein einzelner Autor eine Zeit lang nicht sofort auf eine Anfrage antwortet. -->
* '''Sind Co-Autoren gegenwärtig erwünscht?''' Co-Autoren sind sehr erwünscht, vor allem für betriebssystemspezifische Belange wie die Wahl des Compilers
<!-- Manche Autoren wünschen jede Menge Beteiligung, andere möchten in der Anfangsphase FÜR EINEN BEGRENZTEN ZEITRAUM ungestört schreiben. Stelle bitte zweifelsfrei klar, was DU möchtest, und passe deine gegenwärtige Festlegung an den Buchfortschritt an! Die meisten Wikibookianer werden sich rücksichtsvoll daran halten. Im folgenden drei Formulierungsvorschläge: -->
<!-- Schreibt, was ihr wollt, ich freu mich darüber! Kaputtmachen könnt ihr nichts! -->
<!-- Inhaltliche Änderungen bitte nur auf die Diskussionsseite schreiben, ich füge sie an geeigneter Stelle ein! -->
<!-- Wenn ich erst mal die detaillierte Gliederung fertig habe, könnt ihr euch gern beteiligen! -->
<!-- Du kannst auch die Vorlagen {{Bitte Mitarbeit mit Hauptautor vorab abstimmen}} oder {{Hauptautor wünscht Mitarbeit}} verwenden. -->
* '''Richtlinien für Co-Autoren:''' Möglichst nicht an eine spezielle Sprache binden, verschiedene Möglichkeiten Aufzeigen. Nur vereinzelte Code-Beispiele zur Anschaulichkeit, kein komplettes Beispielprojekt. Erklärung eher theoretischer Aspekte in Hinsicht auf deren praktische Anwendung.
<!-- Ein Liste aller Übereinkünfte, an die sich Buchschreiber halten sollten. Etwa Siezen/Duzen, Rechtschreibung, Absprachen, Platzierung der Links etc. -->
* '''Projektumfang und Abgrenzung zu anderen Wikibooks:''' Im Gegensatz zum Buch [[Betriebssystemtheorie]] soll hier eher das praktische Wissen für die Programmierung eines eigenen Betriebssystems vermittelt werden. Aufgrund der Tatsache, dass es zu viele unterschiedliche Computerarchitekturen gibt, um auf sie alle einzugehen, beschränkt sich dieses Buch auf die Entwicklung für einen x86er- oder x86-64er-Prozessor, also für einen handelsüblichen PC.
<!-- Gibt es andere Wikibooks, die sich mit dem gleichen oder einem ähnlichen Thema beschäftigen, und was soll an diesem Buch anders sein? Was soll alles in das Buch rein, was nicht? -->
=Voraussetzungen=
Wer sich an dieses Buch heranwagt, sollte in jedem Fall in den Grundlagen der Betriebssystemtheorie bewandert sein, da diese essentiell für eine praktische Umsetzung in Form eines eigenen Betriebssystems ist. Sehr hilfreich kann es hierbei sein, sich mit einem freien Betriebssystem zu beschäftigen, etwa Linux oder BSD, da diese sehr tiefe Einblicke in die Funktionalität eines Betriebssystems liefern. Hilfreich hierfür ist etwa das Buch [[Betriebssystemtheorie]] oder das erste Kapitel des openBooks [http://openbook.galileocomputing.de/linux/linux_01_kernel_001.htm#mjcc5fee32671650af41899a3969b75025 Linux - Das distributionsunabhängige Handbuch].
Neben Kenntnissen in der Systemtheorie ist auch die kompetente Beherrschung einer kompilierten Programmiersprache vonnöten, da nur eine solche unter der Abschaltung systemspezifischer Eigenheiten in der Lage ist, freistehenden Binärcode zu erzeugen, der ohne Betriebssystem bzw. Interpreter lauffähig ist. Zwar ist es möglich, bereits auf sehr tiefer Ebene einen Interpreter in das Betriebssystem zu integrieren, dennoch muss die Basis aus freistehendem, betriebssystemunabhängigem Binärcode bestehen. Interpretersprachen wie Java, Python oder die .NET-Sprachen scheiden damit aus. Die deutlich am häufigsten verwendete Sprache für die Systemprogrammierung ist C (manchmal auch C++), auf dem nahezu alle UNIX-Derivate (damit auch MacOS X) und Windows aufbauen. Ebenfalls verwendet wurde auch Pascal für Mac OS/2, das frühere Mac-Betriebssystem, das mittlerweile allerdings durch das BSD-ähnliche OS X ersetzt wurde.
Etwas, um das man auch heute bei der Betriebssystemprogrammierung noch nicht herumkommt, ist Assembler. Auch wenn moderne Betriebssysteme zu einem Großteil (gewöhnlich weit über 90% des Codes) in einer höheren Programmiersprache geschrieben sind, können Hardware-Funktionalitäten auf tiefer Ebene, die für ein Betriebssystem benötigt werden, doch nur mit Assembler realisiert werden, wie etwa das Laden des Kernels oder das auslösen von Interrupts. Da die Syntax der meisten Assembler ebenso wie ihr Befehlssatz relativ simpel ist, zählt hierfür vor allem die Hardware-Kenntnis, insbesondere über die CPU und ihre Register. Zu den Teilen des Betriebssystems, die in Assembler entwickelt werden müssen, existieren jedoch im Internet sehr viele Tutorials und Code-Beispiele, die das ganze selbst für jene, die Assembler nicht beherrschen oder sich nicht allzu sehr damit außeinandersetzen wollen, relativ einfach machen.
== Werkzeuge ==
Aus der Wahl der Programmiersprache ergeben sich letztendlich die benötigten Werkzeuge. Diese unterscheiden sich von Plattform zu Plattform. Deshalb findet sich im folgenden eine Liste von Werkzeugen und Tools für verschiedene Programmiersprachen, die wiederum nach dem verwendeten System geordnet sind. All diese Programme sind Open Source Software und können somit frei verwendet werden. Sie bieten sich sogar für eine eventuelle Portierung auf das eigene Betriebssystem an, sobald dieses sich in einem fortgeschritteneren Status befindet.
=== Assembler ===
Da Assembler auf jeder Plattform und unabhängig von der Wahl der Programmiersprache immer benötigt wird, wird mit der Beschreibung der Einrichtung eines Assemblers begonnen.
==== Linux/UNIX ====
Auf einem Linux/UNIX-System kommen hauptsächlich zwei Assembler in Frage, die beide freie Software und für die meisten UNIX-Derivate verfügbar sind. Der Erste ist der ''GNU Assembler'', kurz <code>gas</code>. gas verwendet die AT&T-Syntax und wird von der ''GNU Compiler Collection'' intern benutzt. In C/C++ eingebundene Inline-Assemblerdirektiven entsprechen der Syntax von gas.
Die zweite Möglichkeit ist der ''Netwide Assembler'', kurz <code>nasm</code>. Dieser folgt der einfachereren Intel-Syntax und existiert auch für Windows, weswegen er in Tutorials bevorzugt verwendet wird. Ein weiterer Vorteil ist das mächtige Makro-System, das bei richtigem Einsatz eine enorme Einsparung von Aufwand erlaubt. Aus diesen Gründen sind auch die Assembler-Beispiele dieses Buches in der nasm-Syntax verfasst. Das Buch x86 Assembly aus dem englischen Wikibooks stellt die Unterschiede zwischen gas- und nasm-Syntax gut dar. Der Code bleibt jedoch prinzipiell der gleiche.
Durch das folgende Kommando erzeugt man mit nasm eine betriebssystemunabhängige Binärdatei (natürlich ist diese nur solange betriebssystemunabhängig wie keine systemspezifischen Funktionsaufrufe getätigt wurden) im ''Executable and Linkable Format'', kurz <code>elf</code>. Diese Binärdatei kann nun in den Kernel gelinkt werden.
nasm -f elf assembler.asm assembler.o
Will man eine eigenständige (nicht mehr zu linkende) Datei erzeugen, muss man das durch den Schalter <code>-f</code> definierte Format elf durch <code>bin</code> ersetzen. Das ist etwa erforderlich, wenn man einen eigenen Bootloader schreibt.
=== C/C++ ===
Die meisten aktuellen Betriebssysteme sind zu einem Großteil in C oder C++ geschrieben, da diese Sprachen sehr hardwarenah sind, aber gleichzeitig auch alle Eigenschaften einer Hochsprache aufweisen. Besonders bei C++ gestaltet sich die Einrichtung und Verwendung für ein Betriebssystem schon etwas schwieriger, da es standardmäßig einige plattformspezifische Features direkt in die Sprache integriert hat, die erst einmal deaktiviert werden müssen. Wie einige dieser Features auch in das eigene Betriebssystem integriert werden können, wird [http://wiki.osdev.org/C%2B%2B hier] sehr gut beschrieben (englisch). Mit den folgenden Kommandos sollte es auch auf modernen 64bit-Systemen möglich sein, einen 32bit-Kernel zu kompilieren.
==== Linux/UNIX ====
Der ultimative Compiler unter Linux/UNIX ist zweifellos der <code>gcc</code> aus der ''GNU Compiler Collection''. Dieser ist auf so gut wie jeder Linux-Distribution der Standard-Compiler und auf nahezu alle UNIX-Derivate portiert. Außerdem unterstützt er eine Vielzahl an Features und Optimierungsmöglichkeiten und erlaubt eine präzise Konfiguration, weswegen auch ausschließlich auf diesen eingegangen werden muss.
Für eine betriebssystemunabhängige Binärdatei müssen dem Compiler mehrere Parameter übergeben werden. Unter C muss signalisiert werden, dass es sich um eine freistehende Bibliothek handelt, startfiles müssen abgeschaltet werden und die Standardbibliothek sowie andere standardmäßig hinzugelinkten Bibliotheken ausgeschlossen. Der Befehl lautet dann folgendermaßen:
gcc -ffreestanding -fno-builtin -nostdlib -nostartfiles -nodefaultlibs -c -o ausgabedatei.o eingabedatei.c
Bei C++ kommen noch Exceptions und ''runtime type information'' hinzu, die beide standardmäßig nicht verfügbar sind. Durch die Parameter -fno-rtti und -fno-exceptions werden diese deaktiviert:
g++ -ffreestanding -fno-builtin -nostdlib -nostartfiles -nodefaultlibs -fno-rtti -fno-exceptions
Diese Kommandos erzeugen aber lediglich nicht ausführbare Objektdateien (ebenso wie der Assembler dies tut). Damit diese zu einem einzigen Kernel verschmelzen, müssen sie noch gelinkt werden. Unter Linux/UNIX ist hierbei ld die erste Wahl, der ebenfalls Teil der ''GNU Compiler Collection'' ist und von gcc intern benutzt wird. Das von ld standardmäßig vorgegebene Linkerscript ist jedoch nicht für die Zwecke eines Betriebssystems geeignet und muss deshalb durch ein eigenes ersetzt werden. Ein passendes könnte etwa so aussehen:
<source lang="cpp">
/* linkerscript.ld */
/* Original file taken from Bran's Kernel Development */
/* tutorials: http://www.osdever.net/bkerndev/index.php. */
OUTPUT_FORMAT(elf32-i386)
OUTPUT_ARCH(i386:i386)
ENTRY(start)
SECTIONS
{
.text 0x100000 :
{
code = .; _code = .; __code = .;
*(.text)
. = ALIGN(4096);
}
.data :
{
data = .; _data = .; __data = .;
*(.data)
*(.rodata)
. = ALIGN(4096);
}
.bss :
{
bss = .; _bss = .; __bss = .;
*(.bss)
. = ALIGN(4096);
}
end = .; _end = .; __end = .;
}
</source>
Mit dem folgenden Kommando wird dann der Linker aufgerufen:
ld -T linkerscript.ld -o kernel.bin objektdatei.o [objektdatei2.o [objektdatei3.o [...]]]
== Testen ==
=== Unter Linux ===
Um den logischen Aufbau des Buches nicht zu stören, wird schon an dieser Stelle beschrieben, wie das fertig kompilierte Betriebssystem getestet werden kann. Dazu muss es zuerst einmal in ein Diskettenimage verpackt werden. Die einfachste Möglichkeit dazu ist es, ein bereits vorhandenes Diskettenimage zu bearbeiten und für die eigenen Zwecke zu verwenden. Ein geeignetes Image wäre beispielsweise das hier: [http://errorx.er.ohost.de/zeros.img].
Um dieses Image zu bearbeiten, muss es zuerst gemountet werden, beispielsweise in /media/floppy. Das Shell-Kommando dazu sieht folgendermaßen aus:
mount Pfad_zum_Image /media/floppy -o loop
Dies bewirkt, dass der Inhalt des Images sich jetzt im Ordner /media/floppy befindet, wo er beliebig verändert werden kann.
Zuerst müssen alle Dateien außer dem Ordner grub gelöscht und die eigene ausführbare Kerneldatei eingefügt werden. Danach muss die vorhandene menu.lst durch die eigene überschrieben werden (siehe Abschnitt [[Betriebssystementwicklung#GRUB verwenden|GRUB verwenden]]). Um die Änderungen zu übernehmen, muss man das Image einfach unmounten.
Mit dem fertigen Image kann nun zweierlei angestellt werden:
==== Image auf Diskette schreiben ====
Die erste Möglichkeit ist es, das Image auf eine Diskette zu schreiben und damit eine Bootdiskette zu erzeugen. Dazu wird das Programm dd verwendet.
Zuerst muss die Diskette gemountet werden. Ist dies geschehen, kann das Image folgendermaßen auf die Diskette geschrieben werden:
dd if=Pfad_zum_Image of=/dev/fd0
Bei einem Neustart mit der Diskette im Laufwerk sollte nun von dieser aus gebootet werden.
==== Image in einem Emulator testen ====
Die eben beschriebene Variante hat jedoch zwei entscheidende Nachteile. Zum einen muss der Rechner andauernd neugestartet werden und zum anderen können Programmierfehler im schlimmsten Falle Hardware zerstören. Deshalb ist es sicherer, einen Emulator zu verwenden, der einen virtuellen PC bereitstellt. Hier wird auf den QEMU-Emulator eingegangen, eine Alternative wäre beispielsweise Bochs.
QEMU kann von [http://wiki.qemu.org/Download dieser Website] bezogen werden und lässt sich nach dem üblichen Schema für die eigene Plattform installieren. Um damit das Image zu starten, reicht das Kommando
qemu -fda Pfad_zum_Image
aus. Es öffnet sich daraufhin ein kleines Fenster, in dem das Betriebssystem erscheint.
Mehr Infos zu QEMU gibt es im [http://qemu-buch.de Wikibook "QEMU und KVM"].
Ein weiterer, sehr verbreiteter Emulator ist ''bochs'' (sprich engl. box), der allerdings eine weitreichendere Konfiguration erfordert, bevor er verwendet werden kann. Eine (englische) Dokumentation findet sich [http://bochs.sourceforge.net/cgi-bin/topper.pl?name=New+Bochs+Documentation&url=http://bochs.sourceforge.net/doc/docbook/user/index.html auf der offiziellen Website] des Projektes. Eine Beispiel-Konfigurationsdatei, die ein Diskettenimage mit dem Dateinamen ''floppy.img'' beim Aufruf des Kommandozeilenbefehls <code>bochs</code> im selben Verzeichnis ausführt, sieht folgendermaßen aus:
megs: 128
cpu: count=1, ips=43000000
romimage: file=$BXSHARE/BIOS-bochs-latest, address=0xe0000
vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest
vga: extension=none
floppya: 1_44=icyx.img, status=inserted
boot: floppy
display_library: x
log: bochs.log
clock: sync=realtime
mouse: enabled=0
keyboard_mapping: enabled=1, map=$BXSHARE/keymaps/x11-pc-de.map
Diese Datei muss einfach als bochsrc.txt abgespeichert werden und gibt bochs alle Informationen, die es benötigt, um von der Diskette zu starten.
=Der Bootvorgang=
== Das BIOS ==
Im Buch [[Betriebssystemtheorie]] kaum beschrieben ist der Bootvorgang. Deswegen wird hier genauer darauf eingegangen. Dazu muss erst einmal klargestellt werden, was passiert, nachdem bei einem Rechner der Anschaltknopf gedrückt wurde. Zuerst wird das [[BIOS]], das <i>Basic Input/Output System</i> geladen, die Firmware des PCs. Es initialisiert die Hardware, führt einige Checks durch (etwa den ''Power on self test'') und stellt einige BIOS-Interrupts zur Verfügung, mit denen sich etwa der Grafikspeicher oder die Festplatte ansteuern lässt. Das BIOS sucht nun im ersten Sektor (den ersten 512 Bytes) jedes Datenträgers, der in der von Seiten des BIOS eingestellten Boot-Device-Liste vorkommt, nach einem Bootloader, der durch einen bestimmten Code gekennzeichnet wird. Findet es einen Bootloader, wird dieser aufgerufen und übernimmt die Kontrolle.
== Der Bootloader ==
Wie erkennt BIOS nun den Bootloader? Es sucht im ersten Sektor jedes Datenträgers nach einer ausführbaren Datei, die mit der sogenannten <i>Magicnumber</i> AA55<sub>hex</sub> endet. Diese Datei wird dann aufgerufen und lädt für gewöhnlich den Kernel des Betriebssystems.
Dadurch, dass die Datei den ersten Sektor ausfüllen muss, hat sie exakt 512 Bytes groß zu sein. Aufgrund dieser Beschränkung wird normalerweise Assembler für den Bootloader verwendet, der schlanken Code erzeugt und den direktesten und damit einfachsten Weg darstellt, den Betriebssystemkernel zu laden. Es ist theoretisch auch möglich, einen Bootloader in einer Compilersprache wie C zu schreiben, was aber sehr aufwändig und schwierig ist, außer man setzt einen sogenannten 2-Stage-Bootloader ein. Die erste Stufe (oben beschrieben) startet die zweite Stufe, die überall auf der Festplatte liegen kann. Dies hat zu Beispiel den Vorteil, dass man sich kaum um die 512 Byte Beschränkung kümmern braucht.
Sie sind jedoch nicht gezwungen, einen eigenen Bootloader zu schreiben. Es gibt viele gute Bootloader, auf die Sie zurückgreifen können. Von diesen ist GRUB empfehlenswert, der <i>GRand Unified Bootloader</i>. Wie man einen Kernel mit diesem bootbar macht, wird später beschrieben. Zuerst einmal erfolgt eine kurze Anleitung, was ein selbstgeschriebener Bootloader können sollte und wie man ihn realisieren könnte. Es empfiehlt sich auch denjenigen, die nicht vorhaben, einen eigenen Bootloader zu schreiben, diesen Abschnitt durchzulesen, da er das allgemeine Verständnis verbessert.
=== Einen eigenen Bootloader schreiben ===
Wenn Sie selbst einen Bootloader schreiben wollen, werden Sie sich sicher fragen, was genau ein Bootloader alles tun muss. Im Prinzip gar nichts, außer 512 Bytes groß sein, sich im ersten Sektor zu befinden und mit der <i>Magicnumber</i> zu enden. Aber ein solcher Bootloader wäre sinnlos, da er ja nichts tut. Zuerst einmal muss der Bootloader an die Speicheradresse 0x7C00 geladen werden. Der NASM stellt dafür beispielsweise die Assemblerdirektive ORG bereit. Dann muss ein Stack an der Adresse 0x9000 initialisiert werden, indem das Stacksegmentregister diese Adresse als Wert erhält und das Stackpointerregister auf 0 gesetzt wird. Währenddessen sollten Interrupts deaktiviert sein.
Damit wäre das Programm geladen. Danach geht es darum, die Kernel-Datei zu finden. Dabei gibt es zwei Möglichkeiten: Entweder man macht sich die Eigenschaften des jeweilig verwendeten Dateisystems zu Nutze und nimmt dabei eine gewisse Plattformabhängigkeit in Kauf, oder man arbeitet mit BIOS Interrupts, die beispielsweise das Lesen von Sektoren erlauben, und unabhängig vom verwendeten Dateisystem sind. Im Folgenden wird auf die Variante der BIOS Interrupts näher eingegangen.
Das BIOS übergibt die Nummer des Bootlaufwerkes über das dl-Register. Dieses Laufwerk wird nun mit Hilfe von BIOS-Interrupts Sektor für Sektor ausgelesen. Der darin befindliche Kernel wird in den Arbeitsspeicher geladen und durch einen jump zur Ausführung gebracht. damit ist die Arbeit des Bootloaders getan und das eigentliche Betriebssystem steuert den Rechner nun.
=== GRUB verwenden ===
Anfängern wird häufig davon abgeraten, einen eigenen Bootloader zu schreiben, da dies sehr fehleranfällig ist und auch einen gewissen Grad an Erfahrung erfordert. Ein Vorteil dieser Praktik ist zwar, dass man nebenbei einiges über den Bootvorgang des PCs und BIOS-Interrupts lernt, das Verwenden von GRUB ist aber deutlich komfortabler und, da GRUB der wohl am häufigsten verwendete Bootloader ist (wenn man von der Zahl der Betriebssysteme ausgeht, die ihn verwenden und nicht von den Anwendern), auch portabler.
GRUB ist hierbei ein sogenannter ''multistage''-Bootloader. Wie nach der Lektüre des vorherigen Abschnittes bekannt ist, durchsucht das BIOS jeweils nur den ersten Sektor, die ersten 512 Bytes eines jeden bootbaren Gerätes nach dem Bootloader. Da diese 512 Bytes jedoch kaum ausreichen, um einen so komplexen und umfangreichen Bootloader wie GRUB vollständig zu beherbergen, greift man auf das sogenannte ''multistage''-Prinzip zurück. Dabei wird ein genau 512 Bytes großes Programm, ''stage1'', in den Bootsektor abgelegt. Diese Datei bootet nun jedoch nicht selbst den Kernel, sondern lädt erst einmal den eigentlichen Bootloader, ''stage2'', der beliebig groß sein kann, da er außerhalb des Bootsektors liegt.
''stage2'' erkennt eine bootbare Kerneldatei dann dadurch, dass sie den Multiboot-Header enthält. Dabei handelt es sich um einen Bereich im Speicher des Programms, das, an 4 Bytes ausgerichtet, die ''Magicnumber'', eine bestimmte Zahl, die den Kernel GRUB erkenntlich macht, Flags und eine Prüfsumme enthält. Ein Beispielcode dafür wäre z.B.:
<source lang="asm">
;für mehr Informationen schauen Sie sich am besten die GRUB-Dokumentation an
multiboot_header:
dd 0x1BADB002 ;Magicnumber
dd 0x0 ;Flags (können verändert werden)
dd (-0x1BADB002) ;Prüfsumme
</source>
Dieses Label kann an jeder beliebigen Stelle im Quelltext des Programmes stehen, das - wie ein Bootloader auch - in Assembler geschrieben werden MUSS, da noch kein Stack initialisiert ist, was C/C++-Programme jedoch benötigen. Folglich ist es die Aufgabe des Programmes, den Stack zu initialisieren, der normalerweise an Adresse 0x200000 beginnt, und die in C/C++ geschriebene Hauptfunktion des Kernels aufzurufen. Die assemblierte Datei wird dann zum Kernel hinzugelinkt, der fortan für GRUB erkennbar ist. Beim nächsten Boot ruft GRUB also den Kernel mit der ''multiboot structure'' als Parameter auf, einer Struktur, die einige Informationen über den Kernel, seinen Ort im Speicher und eventuelle GRUB-Module wie eine initrd enthält. Zu Beginn muss man sich um diese Informationen jedoch kaum kümmern, sie werden erst später, bei der Implementierung einer eigenen ''Initial RAM Disk'' interessant.
Momentan wäre der kompilierte Kernel allerdings nur aus der Kommandozeile von GRUB bootbar, da noch ein Eintrag in der menu.lst fehlt, die Konfigurationsdatei von GRUB. Im Bootmenü wäre das Betriebssystem damit praktisch unsichtbar. Um das zu ändern, sollte die Datei menu.lst (im Ordner ''grub'' zu finden) editiert bzw. angelegt werden. Ein simpler Eintrag sieht folgendermaßen aus:
timeout Sekunden_bis_autostart
title Name_des_Betriebssystems
kernel Pfad_zum_Kernel
= Erste Schritte =
== Textausgabe ==
Die erste Aufgabe eines Betriebssystemkernels ist zweifellos die Möglichkeit bereitzustellen, Text auf dem Bildschirm auszugeben. Das geht jedoch nicht so leicht, wie es sich anhört. Es existiert nämlich noch keine Standardbibliothek - die wird erst vom Betriebssystem bereitgestellt. Folglich kann nicht einfach eine vorgefertigte Funktion wie printf() (C) oder die ostream-Klasse (C++) verwendet werden. Die Ausgabefunktion muss selbst geschrieben werden. Die Frage ist nun, wie man das anstellt. Im Realmode könnte man über BIOS-Interrupts Text ausgeben, da, wie wir bereits wissen, GRUB jedoch von Anfang an im ''Protected Mode'' bootet, kommt diese Möglichkeit nicht in Frage, auch, da diese bei komplexeren Aufgaben zu aufwändig zu bedienen wäre.
Die andere und weit empfehlenswertere Möglichkeit ist es, einen Texttreiber für den VGA-Controler der Grafikkarte zu schreiben. Das ist bei weitem nicht so schwer, wie es sich anhört. Wenn man die Grundprinzipien verstanden hat, ist die Textausgabe eine der einfachsten Aufgaben eines Betriebssystems. Der Texttreiber sollte in etwa folgende Funktionalitäten bereitstellen:
* ein Zeichen/String auf den Bildschirm schreiben
* den Text scrollen
* den Bildschirm leeren
* den hardwareimplementierten Cursor versetzen
=== Zeichen ausgeben ===
Um diese Funktionalitäten verfügbar zu machen, muss man zuerst einmal verstehen, wie die Textausgabe überhaupt realisiert wird. Das funktioniert nach einem relativ simplen Prinzip: Im Grafikspeicher jeder Grafikkarte gibt es einen genau 4.000 Byte großen Bereich, der sich Textspeicher nennt und sich wie ein Teil des Arbeitsspeichers addressieren lässt. In diesem Bereich ist Platz für 2.000 ASCII-Zeichen, jedes zwei Byte groß. Dabei ist jedoch nur
[[Bild:Zeichenbyte.png|left|200px|Aufbau des Speicherbereiches für ein einzelnes Zeichen]]
das erste Byte für den Zeichencode reserviert, das zweite Byte enthält Informationen über die Vorder- und Hintergrundfarbe des Zeichens, die in jeweils einem 4-Bit-Abschnitt definiert werden, der 16 verschiedene Werte erlaubt. Dabei entsprechen die oberen 4 Bit der Hintergrund- die unteren 4 der Schriftfarbe. Das Attributbyte (der Name des Bytes, das die Farben definiert, im Gegensatz zum Zeichenbyte, in dem der Zeichencode gespeichert ist) liegt wiederum unterhalb des Zeichenbytes (siehe Abbildung).
Diese 2.000 Zeichen teilen sich auf 25 Zeilen zu je 80 Zeichen auf. Dabei wird kein Extra-Zeichen für einen Zeilenumbruch benötigt, alles, was über eine Zeile hinausgeht, wird in die nächste geschrieben. Deshalb verwendet man normalerweise zwei globale Variablen (bzw. eine globale Struktur, die diese beiden Variablen beinhaltet), eine für die y-Position des Cursors (also die aktuelle Zeile) und eine für die x-Position des Cursors (die aktuelle Spalte). Verwendet man nun einen 1 Byte großen Zeigerdatentyp, der mit 0xB8000 auf die Basisaddresse des Textspeichers zeigt, kann man mit folgender Formel die aktuelle Cursor-Position auf ein Byte im Videospeicher projizieren:
Addresse: 0xB8000 + (position_y * 80 + position_x) * 2
Dadurch erhält man die Adresse des Zeichenbytes an der aktuellen Cursorposition. Für das Attributbyte muss die Adresse einfach um eins inkrementiert werden.
Die Verwendung der Positionsvariable bringt viele Vorteile mit sich, etwa eine vereinfachte Behandlung von Zeilenumbrüchen: Es muss einfach nur die position_y-Variable inkrementiert werden und das nächste auszugebende Zeichen wird in die nächste Zeile geschrieben. Eine Funktion zur Ausgabe eines einzelnen Zeichens sähe etwa so aus:
# Berechne die aktuelle Zeichenadresse aus den Positionsvariablen
# Wenn das angegebene Zeichen nicht druckbar ist, realisiere es anderweitig
# Generiere ein Attributbyte (etwa 0x0F für schwarz-weiß)
# Schreibe das Zeichenbyte an die Zeichenadresse und inkrementiere sie
# Schreibe das Attributbyte an die neue Adresse
# Aktualisiere die Positionsvariablen
# Setze den Hardware-Cursor
# Prüfe, ob gescrollt werden muss
=== Scrolling ===
Hierin werden gleich zwei noch nicht behandelte Funktionalitäten benötigt: Scrolling und das Setzen des Hardware-Cursors. Das Scrolling lässt sich recht einfach realisieren: Es wird einfach in einer Schleife jedes Zeichen mit dem Zeichen an der selben Position in der nächsten Zeile überschrieben. Ein kurzes C-Beispiel zur Demonstration:
<source lang="c">
for(i = 0;i < 24 * 80 * 2;i++)
// videospeicher ist ein Zeiger auf die Adresse 0xB8000
videospeicher[i] = videospeicher[i + 80];
</source>
=== Hardware-Cursor ===
Das Setzen des Hardware-Cursors gestaltet sich schon etwas schwieriger, da hierbei der VGA-Controller direkt über den I/O-Bus angesprochen werden muss. Dazu verwendet man den im vorherigen Abschnitt beschriebenen x86-Assembler-Befehl ''outb'' (für engl. outbyte), mit dem man über die I/O-Ports des VGA-Controlers zuerst das Kommando zum Setzen des Cursors und schließlich die Position des Cursors sendet. Schritt für Schritt läuft das ganze folgendermaßen ab:
# Über die Formel position_y * 80 + position_x wird die (zwei Byte große) Positionsvariable berechnet
# Über den Kommandoport 0x3D4 wird das Kommando 14 gesendet, das dem VGA-Controler sagt, dass wir als nächstes über den Datenport das untere Byte der Cursor-Positionsvariable (Bits 0 bis 7 wenn von rechts nach links gelesen wird, wie das auf dem PC als Little-Endian-System üblich ist)
# Über den Datenport 0x3D5 werden die unteren Bits der Positionsvariable (= cursorLocation >> 8) gesendet
# Über den Kommandoport 0x3D4 wird das Kommando 15 gesendet, das dem VGA-Controler die nächsten 8 Bit ankündigt.
# Über den Datenport 0x3D5 wird das obere Byte (= cursorLocation) gesendet
=== Bildschirm leeren ===
Bleibt noch das Leeren des Bildschirms, das nach allem extrem einfach ist: Es wird schlicht und ergreifend jedes Byte im Videospeicher in einer Schleife mit 0 initialisiert, was den Bildschirm schlagartig leert.
=== Attributbyte ===
{| class="wikitable" center style="float:left;"
! Nummer
! Farbe
|-
| 0 || schwarz
|-
| 1 || dunkelblau
|-
| 2 || dunkelgrün
|-
| 3 || dunkeltürkis
|-
| 4 || dunkelrot
|-
| 5 || dunkellila
|-
| 6 || dunkelgelb/orange
|-
| 7 || hellgrau
|-
| 8 || dunkelgrau
|-
| 9 || hellblau
|-
| A || hellgrün
|-
| B || helltürkis
|-
| C || hellrot
|-
| D || helllila
|-
| E || hellgelb
|-
| F || weiß
|}
Bisher nur kurz erwähnt wurde das Attributbyte, das die Farbe eines jeden Zeichens auf dem Bildschirm definiert. Da jedes Attributbyte zweigeteilt ist und je vier Bit für Schrift- und Hintergrundfarbe bereitstellt, lässt sich daraus schlussfolgern, dass der VGA-Controler nativ 16 Farben unterstützt. Eine Tabelle der Hexadezimalen Farbwerte folgt:
Das Attributbyte, das für gewöhnlich standardmäßig verwendet wird, ist schwarz-weiß, also 0x0F, manchmal auch 0x07, hellgrau auf schwarz. Zu beachten ist, dass (bei einem hexadezimalen Wert) die erste Ziffer den Hintergrund und die zweite den Vordergrund darstellt. 0xF0 würde also einen schwarzen Schriftzug auf weißem Hintergrund bedeuten.
Im Folgenden ein simples Beispiel, das den Text ''Hello World!'' in hellroter Schrift auf dem Bildschirm ausgibt:
<source lang="c">
void kmain(void)
{
char *video = 0xB8000;
char *str = "Hello World!";
unsigned short i;
while(str[i] != '\0')
{
*video = str[i];
++video;
*video = 0x0C;
}
return;
}
</source>
{{clear}}
=Weiterführende Informationen=
==Links==
===Tutorials===
*[http://www.lowlevel.eu/ lowlevel – Deutschsprachige Community rund um die Betriebssystementwicklung]
*[http://www.osdever.net.tc/ osdever.net.tc – Sammlung von Dokumenten]
*[http://computer.howstuffworks.com/operating-system.htm How Stuff Works – Erklärt, wie ein Betriebssystem funktioniert (en)]
*[http://www.osdever.net BonaFide OSDevelopment – Eine DER Seiten rund um Betriebssystementwicklung! (en)]
*[http://www.nondot.org/sabre/os/articles OSRC – Sehr viele Tutorials (en)]
*[http://www.proggen.org/doku.php?id=kernel:start Neu entstehendes Kernel Tutorial auf Proggen.org]
*[http://www.osdev.org/ OSDev.org – Ein Wiki und ein Forum zum Thema (en)]
*[http://osdev.bplaced.net/ osdev.bplaced.net – Neu enstehendes Wiki und Forum zum Thema OS-Dev de/en]
===Microkernel===
====L4====
* [http://www.l4hq.org/ L4Hq] – L4 Headquarters, Community-Seite für L4-Projekte
* [http://www.l4ka.org L4Ka] – Implementierungen L4Ka::Pistachio und L4Ka::Hazelnut
* [http://os.inf.tu-dresden.de/fiasco/ Fiasco] – Eine freie C++-Implementierung für x86- und ARM-Prozessoren
* [http://www.cse.unsw.edu.au/~disy/L4/ UNSW] – Portierung auf die Architekturen [[Alpha-Prozessor|wp:Alpha]] und [[MIPS-Architektur|wp:MIPS]]
* [http://os.inf.tu-dresden.de/L4/LinuxOnL4/ L4Linux] – Linux auf dem L4 Microkernel
* [http://os.inf.tu-dresden.de/drops/ DROPS] – The Dresden Real-Time Operating System Project
* [http://os.inf.tu-dresden.de/vfiasco/ VFiasco] – Verified Fiasco Project
* [http://os.inf.tu-dresden.de/L4/l3.html L3] – Vorgänger-System zu L4, beim [[TÜV Süd]] im Einsatz
[[Kategorie:Buch]]All content in the above text box is licensed under the Creative Commons Attribution-ShareAlike license Version 4 and was originally sourced from https://de.wikibooks.org/w/index.php?oldid=641417.
![]() ![]() This site is not affiliated with or endorsed in any way by the Wikimedia Foundation or any of its affiliates. In fact, we fucking despise them.
|