====== Der erste Start ====== Zunächst einmal möchten wir uns in diesem Abschnitt einen Kernel schreiben, der nichts weiter tut als starten. Klingt nicht besonders aufregend und anspruchsvoll. Aber ich kann dir versichern: Anspruchsvoll ist es schon ein bisschen. Vielleicht mehr, als mancher denkt. Aufregend wird es dennoch nicht aussehen. Genauer gesagt werden wir sogar sehr wenig sehen... ===== Wie starten wir? ===== Wie wir starten ist schnell geklärt. Es ist nur wichtig, dass du verstehst, wie was warum gemacht wird. Wir lassen unseren Kernel von GRUB starten. GRUB lädt unseren Kernel in den Speicher und wechselt dann zu dieser Stelle im Speicher, sodass der Prozessor den Code ausführt. In unserem Code befindet sich ein Abschnitt, der GRUB zeigt, das unser Kernel ein Kernel ist und geladen werden möchte. ===== Der erste Code ===== ==== Wirklich (!) nur ein bisschen Assembler ==== Erstellen wir also eine erste Datei (//basic-kernel-asm.asm//): [BITS 32] [GLOBAL start] start: mov esp, _sys_stack ; ##1 jmp c_kernel_entry ; In den C-Kernel wechseln c_kernel_entry: [EXTERN _c_kernel_main] call _c_kernel_main hlt ; Unser Stack SECTION .bss resb 8192 _sys_stack: ; Hier ist der Multibootheader. Wird gebraucht, um mit GRUB zu booten SECTION .multiboot_data ALIGN 4 multiboot_header: dd 0x1BADB002 ; Magic Nummer dd 0x0 ; Flags dd (-0x1BADB002) ; Prüfsumme In der ersten Zeile erklären wir NASM, dass wir 32 Bit Code benutzen. 32 Bit Code ist Code, der nur im Protected Mode ausgeführt wird. Den Wechsel in den Protected Mode hat GRUB schon für uns übernommen. Machen wir uns darüber also keine weiteren Gedanken. Die nächste Anweisung markiert den Punkt "start" als GLOBAL. Damit ist es auch außerhalb der Datei sichtbar. Wir brauchen das, um unsere Datei richtig zusammenlinken zu können. In der Zeile ##1 legen wir die Adresse unseres Stacks auf den Stackpointer. Unseren Stack definieren wir am Ende der Datei. Wir reservieren dafür 8 KB ( 8192 Byte ). Da der Stack "nach unten" (von der Adresse her) wächst, definieren wir den "Startpunkt des Stacks" ( _sys_stack ) erst nach der Reservierung. Anschließend springen wir zum Label c_kernel_entry ein Stück weiter unten im Code. Nach dem Label rufen wir vorerst nur unsere Mainfunktion auf. Diese schreiben wir in einem Moment gleich noch. Sollten wir durch einen unglücklichen Umstand zurück an diesen Punkt kommen, dann halten wir den Prozessor mit //hlt// an. Ganz am Ende befindet sich der Multiboot Header. Dieser muss sich im Code befinden, andernfalls lädt GRUB unseren Kernel nicht. Außerdem muss sich der Header auf einer 16 Bytegrenze im "vorderen Bereich"((Genauer Bereich nicht bekannt)) der fertigen Kerneldatei befinden. Das erreicht man mit der Anweisung //ALIGN 4// und durch das Schieben in eine eigene Section, die wir später beim Linken an den Beginn unserer Kerneldatei linken lassen. Die 4 bedeutet 2^4 = 16. ((NOTE: Sicher...?)) ==== Der erste C Code ==== Nachdem wir nun unseren grundlegenden Assemblercode geschrieben haben, sollten wir ein bisschen C Code schreiben, um zu testen, ob wir das auch ausführen können. Unser C Code wird vorerst nichts besonderes tun. Er wird einfach nur ein Zeichen auf den Bildschirm schreiben und dann in einer Endlosschleife bleiben. void _c_kernel_main(void) { //Wir geben ein Zeichen aus. //Dazu schreiben wir an eine bestimmte Adresse im Speicher. unsigned short *video_buffer = (unsigned short *)0xB8000; *video_buffer = 'A' | 0x7; for(;;); } ===== Zusammenbasteln ===== Nun müssen wir aus unserem Code natürlich auch noch etwas machen. Compiler und Co kommen jetzt zum Einsatz. ==== Kompilieren ==== Zuersteinmal müssen wir unseren Code kompilieren. Ich verwende dazu den GCC. Wer einen anderen Compiler verwenden möchte, soll sich nicht behindert fühlen. gcc -m32 -fno-builtin -fno-leading-underscore -fno-strict-aliasing -fno-stack-protector -Wall -O0 -c -g SOURCEDATEI.c Gehen wir diese Parameter nacheinander durch: ^ Parameter ^ Wirkung & Warum ^ | -m32 | Adressen im Code werden als 32 Bit Adressen gesehen. Hat den Grund, da wir für eine 32 Bit CPU programmieren. | | -fno-builtin | Es werden keine Bibliotheken verwendet. Wichtig, da Bibliotheken in unserem Kernel nicht funktionieren werden. | | -fno-leading-underscore | Kein "_" vor die Funktionsnamen setzen. Erleichtert uns den Aufruf von C-Funktion aus Assemblercode heraus. (Wir müssten sonst //__c_kernel_main// schreiben) | | -fno-strict-aliasing | FIXME(Weiß ich selbst gerade nicht) | | -fno-stack-protector | Deaktiviert den Stackschutzmechanismus. Wir werden uns selbst um unseren Stack kümmern (müssen). | | -Wall | Alle Warnungen ausgeben. Führt zu vielen Warnungen, aber besser wir wissen, wo ein Fehler stecken könnte, als das wir ihn erst ewig suchen müssten. | | -O0 | Optimierungsgrad auf 0 stellen. Optimierungsaufgaben werden wir selbst erledigen müssen(!)... | | -c | Nur kompilieren, nicht linken. Das wird ld in ein paar Momenten für uns erledigen. (Wichtig!) | | -g | FIXME (Genaue Funktion nachsehen) | ==== Assemblieren ==== Zum Assemblieren benutze ich nasm. Andere Assembler sollten kein Hindernis sein, es muss nur auf die richtige Syntax geachtet werden. Wichtig ist auch, dass das Ausgabeformat ELF32 ist. nasm -f elf ASMCODE.asm Der Parameter //-f elf// legt das Ausgabeformat auf ELF fest. Ansonsten benutze ich keine weiteren Parameter. ==== Linken ==== Nachdem wir nun unseren gesamten Code compiliert haben, sollten wir die einzelnen Objektdateien auch zusammenlinken. Ich verwende dazu den Linker ld, der eigentlich bei jeder Linuxdistribution vorhanden ist. Um den Linker zu steuern werden wir ein Linkerscript verwenden. Eine Erklärung dazu ist im [[kernel:tut:used_linkerscript| nächsten Abschnitt]] zu finden