{{Navigation
|Zurück = [[Teil 7 - Physische Speicherverwaltung]]
|Vor = [[Teil 9 - Paging]]
}}
== Ziel ==
Eigentlich wäre der Kernel inzwischen so weit, dass man kleine Programme dafür schreiben könnte, die aus einer externen Datei geladen werden. Versuchen wir es!
== Ein kleines Testprogramm ==
Der einfachste Test ist wie immer, ein paar Zeichen auf den Bildschirm auszugeben - das ist es also, was wir als erstes erreichen wollen. In unseren bisherigen Tasks haben wir dazu direkt kprintf() aufgerufen. Wenn wir ein externes Programm ausführen, geht das nicht mehr so einfach - das Programm müsste dazu ja wissen, an welcher Adresse diese Funktion liegt. Innerhalb des Kernels hat der Linker die Adresse für uns aufgelöst, aber im externen Programm gibt es kein kprintf, das der Linker auflösen könnte.
Wir gehen daher erst einmal wieder einen Schritt zurück und schreiben direkt in den Videospeicher. Wir werden das weiter unten dann ordentlich machen. Wir erstellen also zunächst einmal eine Datei ''test.c'' in einem anderen Verzeichnis (die Makefile des Kernels sammelt alle .c-Dateien im Kernelverzeichnis und seinen Unterverzeichnissen auf, und das wollen wir ja gerade nicht):
/* Bei _start soll die Ausfuehrung losgehen */
ENTRY(_start)
OUTPUT_FORMAT(binary)
/*
* Hier wird festgelegt, in welcher Reihenfolge welche Sektionen in die Binary
* geschrieben werden sollen
*/
SECTIONS
{
/* Das Programm wird an 2 MB geladen */
. = 0x200000;
.text : {
*(.text)
}
.data ALIGN(4096) : {
*(.data)
}
.rodata ALIGN(4096) : {
*(.rodata)
}
.bss ALIGN(4096) : {
*(.bss)
}
}
Und schließlich wieder eine Makefile, um das Programm zusammenzubauen (auch hier müssen für Crosscompiler die Werte wieder entsprechend angepasst werden):
SRCS = $(shell find -name '*.c') OBJS = $(addsuffix .o,$(basename $(SRCS))) CC = gcc LD = ld ASFLAGS = -m32 CFLAGS = -m32 -Wall -g -fno-stack-protector -nostdinc -I include LDFLAGS = -melf_i386 -Ttest.ld test.bin: $(OBJS) $(LD) $(LDFLAGS) -o $@ $^ %.o: %.c $(CC) $(CFLAGS) -c -o $@ $^ clean: rm $(OBJS) .PHONY: cleanEin einfaches ''make'' erstellt uns jetzt unser Testprogramm als flache Binary in ''test.bin''. == Multiboot-Module == Als nächstes müssen wir das Programm irgendwie in den Speicher bekommen, um es im Kernel starten zu können. Wir haben noch keine Treiber für Festplatten oder irgendwelche anderen Speichermedien, aber wir haben eine Möglichkeit, Dateien zu laden, die wir auch schon für den Kernel benutzen: Der Bootloader kann das für uns machen. [[Multiboot]] erlaubt es, sogenannte ''Module'' laden zu lassen, die dem Kernel dann in der [[Multiboot#Multiboot-Structure|Multibootstruktur]] aufgelistet sind (relevant sind die Felder [[Multiboot#mbs_mods_count|mbs_mods_count]] und [[Multiboot#mbs_mods_addr|mbs_mods_addr]]). Mit dieser Information können wir jetzt ''init_multitasking'' umschreiben: Falls ein Multibootmodul übergeben worden ist, starten wir nicht die vier Testtasks, sondern dieses eine Modul. Der Bootloader legt uns dieses Modul allerdings einfach irgendwo hin, während das Programm davon ausgeht, an 2 MB geladen zu werden. Wir müssen es also erst einmal dorthin kopieren.
OUTPUT_FORMAT(elf32-i386)Anschließend kompilieren wir neu und zeigen uns dann mit ''readelf -l test.bin'' erst einmal an, wie die entstandene ELF-Datei aussieht:
Elf file type is EXEC (Executable file) Entry point 0x200000 There are 3 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x001000 0x00200000 0x00200000 0x00029 0x00029 R E 0x1000 LOAD 0x002000 0x00201000 0x00201000 0x00004 0x00004 RW 0x1000 GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4Dazu lässt sich folgendes sagen: * Eine ELF-Datei hat mehrere Program Header. Alle Header vom Typ LOAD (entspricht dem Wert 1) müssen geladen werden. * Die Spalte Offset gibt an, an welcher Stelle in der Datei der jeweilige Abschnitt anfängt. * FileSiz gibt an, wie groß der Abschnitt in der Datei ist, MemSiz wie groß er im Speicher ist. Wenn MemSiz größer als FileSiz ist, muss hinten mit Nullen aufgefüllt werden. * VirtAddr gibt an, an welcher Adresse der Abschnitt geladen werden muss, damit das Programm funktioniert. * Und den Entry Point haben wir auch wieder, an 2 MB. Zunächst brauchen wir eine Headerdatei ''elf.h'', die uns die nötigen Datenstrukturen - den ELF-Header und die Program Header - zur Verfügung stellt: