Die Programmierung eines [[Kernel|Kernels]] in [[C++]] unterscheidet sich in einigen Punkten von der mit [[C]]. Da wären zum Beispiel der Aufruf der Konstruktoren, bzw. der Destruktoren, die vor bzw. nach dem eigentlichen Kernel aufgerufen werden. Unser Kernel wird mittels [[GRUB]] geladen.
== Kernel ==
Wir werden mit [[GCC]] und [[NASM]] arbeiten. Ich verwende den [[Crosscompiler_f%C3%BCr_Windows]]Cross-Compiler von [[Benutzer:Jidder|Jidder]], es sollte aber keinen Unterschied zu einem "normalen" gcc unter Linux geben.
=== Kernel.cpp ===
Da wir ja schon mit C++ arbeiten, wäre auch etwas OOP angebracht. Deshalb erstellen wir ein Klasse namens Video, die wir in einen Header stecken. Dadurch ist unsere kernel.cpp sehr übersichtlich:
#include "MyOS.h" // Einbinden unseres Header
void main(void){
Video screen; // Screen instanzieren
screen.color(0x7000); // Vordergrund Schwarz, Hintergrund Dunkelgrau
screen.clear(); // Bildschirm leeren
screen.write((char *)"Wilkommen im C++-TestKernel!"); // Textausgabe
}
=== MyOS.h ===
Der Header ist dann doch etwas umfangreicher:
#ifndef MyOS_H
#define MyOS_H
class Video
{
public:
Video(); // Konstruktor
~Video(); // Destruktor
void clear(); // Leeren des Bildschirms, die Größe liegt bei 80x25 Zeichen
void write(char *cp); // Textausgabe
void put(char c); // Ausgabe eines einzelnen Zeichens
void color(int color); // Setzen der FG/BG Farben
private:
unsigned short *videomem; // Zeiger auf den Videospeicher
unsigned int off; // Y-Position der Textausgabe, je volle Zeile +80
unsigned int pos; // X-Position der Textausgabe, ab Zeilenanfang
unsigned int screen_color; // FB/BG Farbe
};
Video::Video(){
screen_color = 0x0700; // Setzen der Farbe auf Dunkelgrau auf Schwarz
pos=0; // Setzen der Textausgabe auf
off=0; // link soben.
videomem = (unsigned short*) 0xb8000; // Startadresse des Videospeichers
}
Video::~Video(){}
void Video::clear(){
for(int i=0;i<(80*25);i++)
{
videomem[i] = (unsigned char)' ' |screen_color; // Füllen des Bildschirms mit Leerzeichen
}
pos = 0; // Zurücksetzen der Textausgabe nach links oben
off = 0;
}
void Video::write(char *cp){
char *str = cp, *ch;
for(ch = str; *ch; ch++){
put(*ch); // Für jedes einzelne Zeichen wird put() aufgerufen
}
}
void Video::put(char c){
if(pos >= 80){ // Wenn die Textausgabe den rechten...
pos = 0;
off += 80;
}
if(off>=(80*25)){ // ...oder den unteren Bildschirmrand erreicht, gibt es einen
clear(); // Umbruch bzw. es wird aufgeräumt.
}
videomem[off + pos] = (unsigned char)c|screen_color; // Setzen des Zeichens und der Farbe in den Videospeicher
pos++;
}
void Video::color(int color){
screen_color = color;
}
#endif
=== cKernel.c ===
Um den Konstruktor bzw. den Destruktor aufzurufen, werden diese beiden Funktionen später mit in den Kernel gelinkt:
void _cmain()
{
extern void (*__CTOR_LIST__)();
void (**constructor)() = &__CTOR_LIST__;
int total = *(int *) constructor;
constructor++;
while(total){
(*constructor)();
total--;
constructor++;
}
}
void _catexit()
{
extern void (*__DTOR_LIST__)();
void (**deconstructor)() = &__DTOR_LIST__;
deconstructor++;
int total = *(int *) deconstructor;
while(total)
{
(*deconstructor)();
total--;
deconstructor++;
}
}
=== asmKernel.asm ===
Auch bei C++ kommen wir nicht um ein wenig Assembler herum:
global loader ; Unser Einsprungspunkt
extern main ; main() aus Kernel.cpp
extern _cmain ; _cmain() aus cKernel.c
extern _catexit ; _catexit() aus cKernel.c
FLAGS equ 0
MAGIC equ 0x1BADB002 ; Magicnumber - Erkennungsmerkmal für GRUB
CHECKSUM equ -(MAGIC + FLAGS) ; Checksum
section .text
align 4
MultiBootHeader:
dd MAGIC ; Magic number
dd FLAGS ; Flags
dd CHECKSUM ; Checksum
loader:
mov esp,0x200000 ; Stack an die 2MB-Grenze platzieren
push eax ; Multiboot Magicnumber auf den Stack legen
push ebx ; Adresse der Multiboot-Structure auf den Stack legen
call _cmain ; main-Funktion des C++-Kernels aufrufen
call main ; cmain-Funktion des C-Kernels aufrufen
call _catexit ; atexit-Funktion des C-Kernels aufrufen
jmp stop
stop: ; endlos Schleife nach Beendung unseres Kernels
jmp stop
== Kompilieren und Linken ==
Die asmKernel.asm:
nasm -f elf -o asmKernel.o asmKernel.asm
Kernel.cpp:
gcc -nostdlib -fno-builtin -fno-rtti -ffreestanding -fno-exceptions -c -Os -o Kernel.o Kernel.cpp
cKernel.cpp:
gcc -Wall -Werror -nostdinc -m32 -ffreestanding -fno-leading-underscore -c -o cKernel.o cKernel.c
Nun zu einer der wichtigsten Stellen: Dem Linkvorgang:
Wir müssen die asmKernel.o, die Kernel.o und die cKernel.o zu einer Kernel.bin linken, die dann von GRUB geladen werden kann.
Unser Linkerscript (link.txt)
ENTRY(loader)
OUTPUT_FORMAT(elf32-i386)
OUTPUT_ARCH(i386:i386)
SECTIONS
{
.text 0x0100000 : {
code = .; _code = .; __code = .;
*(.text)
. = ALIGN(1);
}
.data : {
__CTOR_LIST__ = .; LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2) *(.ctors) LONG(0) __CTOR_END__ = .;
__DTOR_LIST__ = .; LONG((__DTOR_END__ - __DTOR_LIST__) / 4 - 2) *(.dtors) LONG(0) __DTOR_END__ = .;
data = .; _data = .; __data = .;
*(.data)
. = ALIGN(1);
}
.bss :
{
bss = .; _bss = .; __bss = .;
*(.bss)
. = ALIGN(1);
}
end = .; _end = .; __end = .;
}
Wir teilen dem Linker mit, dass wir die Labels __CTOR_LIST__, __CTOR_END, __DTOR_LIST__ und __DTOR_END__ in der Datei cKernel.c haben und er diese beim Linken berücksichtigen soll.
Nur noch ein kurzer Aufruf von LD, und wir haben unseren fertigen Kernel in der Hand (bzw. Maus ;-)):
ld -T link.txt -o kernel.bin asmKernel.o cKernel.o Kernel.o
Und fertig ist unser Kernel! Wie der Kernel nun mitsamt GRUB auf einer Diskette landet kann im Artikel [[C-Kernel_mit_GRUB|C-Kernel mit GRUB]] nachgelesen werden, dort verfährt man einfach analog.
== Anmerkungen ==
Es kann unter Umständen nötig sein, __CTOR_LIST__ etc. durch _CTOR_LIST__ zu ersetzen, also das _ weglassen. Ich vermute, dass das von der GCC Version abhängig ist.
== Siehe auch ==
* [[C-Kernel_mit_GRUB|C-Kernel mit GRUB]]
* [[GRUB]]
* [[C++]]
* [[Multiboot]]
* [[Linkerscript]]
[[Kategorie:Tutorials]]