| Zeile 24: | Zeile 24: | ||
Fangen wir mit dem einfachen an: Direkt nach dem Booten ist ein PC im Textmodus. Im Textmodus werden auf dem Bildschirm die Zeichen dargestellt, die im Grafikspeicher ab der Adresse 0xb8000 liegen. Dabei braucht jedes Zeichen zwei Bytes: Das erste Byte ist das Zeichen selbst. Das zweite Byte ist das sogenannte Attributbyte, das die Farbe und Hintergrundfarbe des Texts festlegt - mehr Details stehen bei Bedarf im Artikel [[Textausgabe]]. Wir legen also folgendermaßen los (die Datei heißt bei mir init.c): | Fangen wir mit dem einfachen an: Direkt nach dem Booten ist ein PC im Textmodus. Im Textmodus werden auf dem Bildschirm die Zeichen dargestellt, die im Grafikspeicher ab der Adresse 0xb8000 liegen. Dabei braucht jedes Zeichen zwei Bytes: Das erste Byte ist das Zeichen selbst. Das zweite Byte ist das sogenannte Attributbyte, das die Farbe und Hintergrundfarbe des Texts festlegt - mehr Details stehen bei Bedarf im Artikel [[Textausgabe]]. Wir legen also folgendermaßen los (die Datei heißt bei mir init.c): | ||
| − | <c>void init(void) | + | <syntaxhighlight lang=c>void init(void) |
{ | { | ||
const char hw[] = "Hello World!"; | const char hw[] = "Hello World!"; | ||
| Zeile 39: | Zeile 39: | ||
video[i * 2 + 1] = 0x07; | video[i * 2 + 1] = 0x07; | ||
} | } | ||
| − | }</ | + | }</syntaxhighlight> |
Nachdem wir das erste Problem souverän gelöst haben, müssen wir den Code jetzt nur noch zur Ausführung bringen. Wir müssen also irgendwie den Multiboot-Header in unsere Binary bekommen. Außerdem benutzt unsere Funktion ein paar lokale Variablen. Lokale Variablen landen auf dem [[Stack]], deswegen müssen wir etwas Speicherplatz für den Stack vorsehen. Diesen Teil erledigen wir in Assembler (diese Datei ist die start.S mit großgeschriebenem ".S"): | Nachdem wir das erste Problem souverän gelöst haben, müssen wir den Code jetzt nur noch zur Ausführung bringen. Wir müssen also irgendwie den Multiboot-Header in unsere Binary bekommen. Außerdem benutzt unsere Funktion ein paar lokale Variablen. Lokale Variablen landen auf dem [[Stack]], deswegen müssen wir etwas Speicherplatz für den Stack vorsehen. Diesen Teil erledigen wir in Assembler (diese Datei ist die start.S mit großgeschriebenem ".S"): | ||
| − | <asm>.section .text | + | <syntaxhighlight lang=asm>.section .text |
// Init ist eine Funktion aus init.c | // Init ist eine Funktion aus init.c | ||
| Zeile 82: | Zeile 82: | ||
.section .bss | .section .bss | ||
.space 8192 | .space 8192 | ||
| − | kernel_stack:</ | + | kernel_stack:</syntaxhighlight> |
An dieser Stelle wären wir mit dem Code fertig. Jetzt müssen wir ihn noch kompilieren und irgendwie ausprobieren. Man kann natürlich von Hand den Compiler und den Linker aufrufen, aber es empfiehlt sich, <i>make</i> dafür zu benutzen. make übersetzt immer nur alle Dateien neu, die sich tatsächlich geändert haben, und es vergisst dabei auch nichts. Eine passende <i>[[Makefile]]</i> (das ist der Dateiname der Steuerdatei für make) könnte beispielsweise so aussehen: | An dieser Stelle wären wir mit dem Code fertig. Jetzt müssen wir ihn noch kompilieren und irgendwie ausprobieren. Man kann natürlich von Hand den Compiler und den Linker aufrufen, aber es empfiehlt sich, <i>make</i> dafür zu benutzen. make übersetzt immer nur alle Dateien neu, die sich tatsächlich geändert haben, und es vergisst dabei auch nichts. Eine passende <i>[[Makefile]]</i> (das ist der Dateiname der Steuerdatei für make) könnte beispielsweise so aussehen: | ||
| Zeile 131: | Zeile 131: | ||
Dazu ändern wir den Anfang der start.S folgendermaßen ab: | Dazu ändern wir den Anfang der start.S folgendermaßen ab: | ||
| − | <asm>.section multiboot | + | <syntaxhighlight lang=asm>.section multiboot |
#define MB_MAGIC 0x1badb002 | #define MB_MAGIC 0x1badb002 | ||
#define MB_FLAGS 0x0 | #define MB_FLAGS 0x0 | ||
| Zeile 148: | Zeile 148: | ||
// _start muss global sein, damit der Linker es findet und als Einsprungspunkt | // _start muss global sein, damit der Linker es findet und als Einsprungspunkt | ||
| − | ...</ | + | ...</syntaxhighlight> |
Außerdem erstellen wir ein <i>Linkerskript</i>, die Datei kernel.ld: | Außerdem erstellen wir ein <i>Linkerskript</i>, die Datei kernel.ld: | ||
| Zeile 200: | Zeile 200: | ||
Für die Implementierung von printf benötigt man <i>stdarg.h</i>, um die variable Parameterliste auszuwerten. gcc bietet dafür bereits Builtins an (bzw. eine komplette <i>stdarg.h</i>, wenn man einen [[Cross-Compiler]] benutzt), so dass die Headerdatei nur folgendes enthalten muss: | Für die Implementierung von printf benötigt man <i>stdarg.h</i>, um die variable Parameterliste auszuwerten. gcc bietet dafür bereits Builtins an (bzw. eine komplette <i>stdarg.h</i>, wenn man einen [[Cross-Compiler]] benutzt), so dass die Headerdatei nur folgendes enthalten muss: | ||
| − | <c> | + | <syntaxhighlight lang=c> |
typedef __builtin_va_list va_list; | typedef __builtin_va_list va_list; | ||
#define va_start(ap, X) __builtin_va_start(ap, X) | #define va_start(ap, X) __builtin_va_start(ap, X) | ||
#define va_arg(ap, type) __builtin_va_arg(ap, type) | #define va_arg(ap, type) __builtin_va_arg(ap, type) | ||
#define va_end(ap) __builtin_va_end(ap) | #define va_end(ap) __builtin_va_end(ap) | ||
| − | </ | + | </syntaxhighlight> |
Außerdem könnte es eine gute Idee sein, alle Ausgaben auch auf der [[serielle Schnittstelle | seriellen Schnittstelle]] auszugeben. Die Emulatoren können eigentlich alle die Ausgaben auf der seriellen Schnittstelle auf der Konsole anzeigen oder in eine Datei schreiben. Das macht es unnötig, Dinge aus dem Emulator abzuschreiben (z.B. Adressen in Fehlermeldungen). | Außerdem könnte es eine gute Idee sein, alle Ausgaben auch auf der [[serielle Schnittstelle | seriellen Schnittstelle]] auszugeben. Die Emulatoren können eigentlich alle die Ausgaben auf der seriellen Schnittstelle auf der Konsole anzeigen oder in eine Datei schreiben. Das macht es unnötig, Dinge aus dem Emulator abzuschreiben (z.B. Adressen in Fehlermeldungen). | ||
Die Datei init.c sollte hinterher nur noch so aussehen (abgesehen vielleicht von einem Aufruf, um den Bildschirm zu löschen): | Die Datei init.c sollte hinterher nur noch so aussehen (abgesehen vielleicht von einem Aufruf, um den Bildschirm zu löschen): | ||
| − | <c>#include "console.h" | + | <syntaxhighlight lang=c>#include "console.h" |
void init(void) | void init(void) | ||
{ | { | ||
kprintf("Hello World!\n"); | kprintf("Hello World!\n"); | ||
| − | }</ | + | }</syntaxhighlight> |
== Troubleshooting == | == Troubleshooting == | ||