Die Programmierung eines [[Kernel|Kernels]] in [[C++]] unterscheidet sich in einigen Punkten von der mit [[C]]. Da wären zum Beispiel der Aufruf der Constructoren, bzw. der Destructoren, 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 Cross-Compiler von 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; //erzeugen von screen
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; //Linksoben.
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 Linksoben
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; //eigentlcih selbsterklärend
}
#endif
=== cKernel.c ===
Um den Constructor bzw. den Destructor aufzurufen, verlinken wir später diese zwei Funktionen mit:
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 -m32 -ffreestanding -fno-leading-underscore -c -o cKernel.o cKernel.c -Wall -Werror -nostdinc
Nun zu einem der wichtigsten Stellen: das Linken.
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, wir speichern es in 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 sagen dem Linker zusätzlich zu einem Script für eine C-Kernel, dass wir die Labels __CTOR_LIST__, __CTOR_END, __DTOR_LIST__ und __DTOR_END__ haben.
Nur noch ein kurzer Aufruf von LD, und wir haben unseren fertigen Kernel in der Hand, äh Maus ^^.
ld -T link.txt -o kernel.bin asmKernel.o cKernel.o Kernel.o
Und fertig ist unser Kernel! Unsere Kernel.bin kann die Kernel.bin auf dem Disketten-Image aus [[C-Kernel mit GRUB]] ohne weiteres ersetzen, und schon Startet unsser C++-Kernel!
== 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.