
Statistik Anzeige | Tutorial: Ein eigenes Betriebssystem programmieren - Ein Kenrel in CEin eigenes Betriebssystem programmieren - Ein Kenrel in CEin eigenes OS programmieren mit Assembler und C Ein C Kernel (mit Dateisystem und halben Textmodus Treiber) so, in diesem Tutorial soll gezeigt werden, wie man auf einfachste Weise ein Betriebssystem in C schreiben kann. Ein C Kernel Wenn man so durch das Internet surft und die vielen Foren, die sich mit OS Coding beschäftigen, durchliest, bemerkt man sehr oft fragen, wie man denn ein OS nur mit C schreiben kann. Als Antwort kommt dann meist, man müsse nur den Bootloader in ASM machen, den Rest kann man mit C machen. Leider ist diese Antwort völlig falsch. Man kann kein OS ohne ASM schreiben. Die Sprache C hat ja selber nur sehr wenig Umfang. z.B. benötigt man, sobald man einen String ausgeben will, eine Headerdatei. Und hier ist das Problem. Diese Headerdatei ist für ein bestimmtes OS zugeschnitten. Man muss sich also selber seine Stringausgabe Funktionen etc. schreiben. Und dies geht nun mal nur mit ASM. Man kann natürlich auch bereits vorgefertigte Headerdateien verwenden, die speziell für das OS Developing programmiert wurden. Allerdings bin ich der Meinung, man sollte schon den größten Teil seines OS selber machen. Sonst braucht man ja gleich nicht anfangen ;) Hier ist mal ein graphischer Aufbau eines ASM Bootloaders mit C Kernel: Bootloader: lade_daten lade_printfunktion lade_kernel springe_zu_kernel Kernel: print(xy); Es ist hier vielleicht schlecht dargestellt, aber so um die schaut es aus. Zuerst wird der Stack initialisiert, Interrupts gesetzt, etc. Dann müssen irgendwo die zu verwendenden Funktionen stehen Anschließend kann der Kernel gestartet werden und die Funktionen können aufgerufen werden. Vielleicht fragt ihr euch jetzt, ob denn die C Funktionen unbedingt schon im Bootloader stehen müssen. Natürlich nicht. Sie können genauso zusammen mit dem Kernel erst geladen werden. Wie man das macht, bleibt aber jedem selber überlassen. Wenn man die Funktionen aber im Kernel erst definiert, so bitte nicht vergessen, es mit Inline Assembler zu machen. ;) Wir stellen also fest, dass man diese Funktionen auch im C Kernel selber schreiben könnte, wieso MUSS dann der Boot-Loader in Assembler geschrieben sein? Man könnte doch gleich mit C starten? Nun ja, wir dürfen ja nicht direkt nur den C Kernel starten. Wir müssen vielmehr zu der main() Funktion springen. Und dies ist mit C nicht realisierbar, dazu benötigt man ASM. Um einen Kernel in C zu coden benötigen wir 3 Programme: Einen C Compiler Einen Assembler Einen Linker Als Compiler verwende ich den Turbo C(++): http://bdn.borland.com/museum/ Als Assembler den NASM: http://sourceforge.net/project/showfiles.php?group_id=6208 Als Linker den JLoc: http://my.execpc.com/~geezer/johnfine/ Alle 3 Programme sind Freeware und müssen von der Kommandozeile aus bedient werden. Dazu würde ich empfehlen, alle 3 Programme in einen einzigen Ordner zu kopieren und die Programme mit einer Batch Datei aufzurufen. Wie sie aufgerufen werden müssen, steht weiter unten. Wir möchten ja im Bootloader Funktionen (bzw. Unterprogramme) definieren, die vom Kernel aus aufgerufen werden. Dazu muss man natürlich einige Eigenschaften von C beachten. Zum einen: Die Funktionsnamen im ASM Quellcode, die dann von einem C Programm aufgerufen werden, müssen mit einem Unterstrich („_“) beginnen. Allerdings benötigt man diesen Unterstrich nur bei Windows/DOS. Bei Linux nicht. Ich möchte darauf hinweisen, dass ich nur Windows/DOS kompatible Beispiele zeige. Zum anderen: Die Parameter, die einer Funktion in C übergeben werden müssen richtig vom Stack gelesen werden. Wird eine Funktion aufgerufen mit einem Parameter, so wird als erstes die Offsetadresse des Parameters in den Stack gepusht (es kommt auf den Datentyp an, ob die Offsetadresse, oder ein direkter Wert gepusht wird), dann wird die Funktion aufgerufen. Da der Stack nach unten wächst, müssen wir den SP erhöhen. Wir sollten allerdings den SP nicht ändern, deshalb pushen wir zuerst BP, kopieren SP nach BP und addieren 4 auf BP. Warum man 4 addieren muss, möchte ich hier kurz erklären: Der Stack wächst ja nach unten. D.h. angenommen, der SP zeigt auf 12. Dann wird die Offsetadresse des Parameters gepusht. Der SP zeigt auf 10 (hier befindet sich auch unsere Offsetadresse). Nun wird die Funktion aufgerufen per SHORT CALL. Es wird also der SP wieder um 2 dekrementiert und nur der IP gesichert. Der IP steht an der Stelle 8. Jetzt pushen wir BP, der SP zeigt auf den Wert 6. Würden wir jetzt 2 auf den SP addieren, wäre das nächste Wort auf dem Stack die Rücksprungadresse. Addieren wir wiederum 2, ist das nächste Wort die Offsetadresse des Parameters. Deshalb +4. Hat man mehrere Parameter, so muss man, um an die Adresse des 2. Parameters zu kommen, wieder 2 addieren. Also SP+6 wäre der 2. Parameter. SP+8 der 3. und immer so weiter. Ein weiterer wichtiger Punkt: Ruft man in C eine Assemblerfunktion auf, sollte man in der Funktion unbedingt alle Register, die man benötigt, auf den Stack sichern. Ansonsten kann es sein, dass das Programm abstürzt. Noch ein Wort zur Rückgabe von Werten: Möchte man, dass eine ASM Funktion einen Wert zurückgibt, so sollte man sich schlau machen, welche Register dazu verwendet werden. Ich kann mir zwar denken, dass standardmäßig das (E)AX Register dazu verwendet wird, wissen tu ich es aber nicht. Aber ist eine Funktion vom Typ CHAR, so muss sich der Rückgabewert in AL befinden. Um herauszufinden, welches Register verwendet wird, kann man sich den Assembler Output des Compilers anschauen. Beim Turbo C(++) geht das mit der Option -S: tcc –S datei.cpp Nun beginne ich endlich mit dem Kernel. Wir benötigen ja erstmal etwas Boot-Loader Code: PHP:
Wichtig ist das EXTERN _main. Damit wird dem NASM gesagt, dass die Funktion _main nicht im aktuellen Code zu finden ist. Das [BITS 16] sollte nicht vergessen werden. Die Direktive sagt dem NASM, dass er 16Bit Anweisungen benutzen soll. Ist zwar heutzutage nicht mehr so wichtig, will man aber den Code auf einer alten CPU ausführen, und der NASM hat 32Bit Befehle verwendet, kann der Prozessor damit nichts anfangen. PHP:
Das meiste Code ist halt das Registersicherungs Zeugs. GLOBAL _prnt _prnt: So muss übrigens der „Funktionskopf“ aussehen, wenn sie von C aus aufgerufen werden soll. Das GLOBAL _prnt muss angegeben werden. Es wird vom Linker benötigt. PHP: Dürfte ebenfalls klar sein.
Um Variablen im ASM Code für den C Kernel zugänglich zu machen, muss der Variable ebenfalls ein Unterstrich vorangesetzt sein und die Variable muss als GLOBAL definiert sein. Haben wir nun unseren ASM Code, brauchen wir noch den C Code. Dieser ist jetzt nicht mehr schwierig. PHP: Das wichtigste habe ich kommentiert.
Übrigens empfehle ich für’ s erste, Funktionen als C Funktionen zu definieren. Dies geht mittels: extern "C" das hier: char *str; str=&boot; war notwendig, um die Adresse von BOOT richtig zu übergeben. So hier noch mal der ganze Code: start.asm: PHP:
PHP: Umwandeln in eine Binärdatei:
Dies geht ganz einfach. Zuerst jagen wir die start.asm durch den NASM: nasm -f obj start.asm -o start.obj Dann schicken wir die kernel.cpp durch den Turbo C(++): tcc -c kernel.cpp Und anschließend müssen wir beide noch verlinken. Der JLoc benötigt aber eine Musterdatei, um zu wissen, wie er die beiden Dateien zusammenlinken soll. Deshalb folgenden Text abtippen und in make.txt abspeichern: PHP: Dann den JLoc aufrufen:
jloc make.txt kernel.bin Die entstandene Kernel.bin kann nur in einem Emulator aufgerufen werden. Wieso? Nun, die Datei ist keine 512 Bytes groß und enthält die Bootsignatur nicht. Eine Möglichkeit wäre nun, den Bootloader bootfähig zu machen, dann den Kernel aus dem 2.Sektor laden. Das ist auch nicht weiter schwierig (ich habe neue Codeteile dick markiert): start.asm: PHP:
Wieder die 2 Dateien zu einer Binärdatei machen und dann kann man sie auch mit RAWRITE auf eine Diskette kopieren. Dann sollte sie bootbar sein. Die Dateien auf der Diskette gehen dann übrigens verloren. Hinweis: Da es sehr mühselig ist, die 3 Schritte jedes Mal einzeln in der Kommandozeile einzutippen, empfehle ich, zuerst den Turbo C++ zu installieren. Dann kopiert den JLoc und den NASM in den Ordner BIN vom Turbo C++. Legt dann folgende Batchdatei an: PHP: Dann ist es einfach wie nie zuvor und man spart sich die mühselige Tipparbeit.
Den Code + Binärdatei könnt hier runterladen: http://biehler.cybton.com/cytut/os_tut_3_c1.zip Jetzt wo ihr euren C Kernel habt, könnt ihr ziemlich einfach weiter in C programmieren. Wichtig ist, dass ihr nicht vergesst, bei Funktionen die entsprechenden Register zu sichern und vor dem RET wieder herzustellen. Zum Schluss zeige ich noch kurz den Gebrauch von Rückgabewerten anhand einer get_char() Funktion. Sie liest ein Zeichen von der Tastatur ein und speichert es in einer Variablen. Der Code der get_char() Funktion: (Diesen Code in der start.asm hinzufügen) PHP: In die kernel.cpp kommt folgender Code:
PHP:
(2): key[0] bekommt den Rückgabewert der Funktion get_char(). Dieser steht in AL. (3): Da die Variable key der Print Funktion übergeben wird, und diese Funktion eine ASCII 0 als Begrenzungszeichen erwartet, muss der String mit einer 0 beendet werden. (4): Die Variable wird der Funktion übergeben. Diesen Code könnt ihr hier runterladen: http://biehler.cybton.com/cytut/os_tut_3_c2.zip Jetzt noch ein Wort zur Programmierung von Anwendersoftware in C für das Betriebssystem: Wir haben zwar noch kein Dateisystem, demzufolge können wir noch nicht ohne größeren Aufwand Dateien verwalten, allerdings passt dieses Thema hier wunderbar rein. Wie ihr vielleicht wisst, muss ein C Programm immer mit der main() Funktion aufgerufen werden. Normalerweise ist dies kein Problem, da der Code in der Main Funktion theoretisch immer an erster Stelle steht. Mann kann nun, z.B., ein C Programm an 07c0h:7000h laden und direkt dahin springen. Wenn man allerdings vor der MAIN() Funktion andere Funktionen einbindet oder hinschreibt, so verschiebt sich der Startpunkt mehr in die Mitte des Programmes. Würde man nun auch an die Stelle 07c0h:7000h springen, so würde der Code, der vor der MAIN() Funktion steht, auch ausgeführt werden. Das sollte natürlich nicht sein. Aktuelle Betriebssysteme haben es, soweit ich weis, so geregelt, dass die Dateien einen Header besitzen. In diesem ist der Startpunkt des Programms eingetragen. Das Programm wird also an 07c0h:7000h geladen. Dann wird im Header nachgeschaut, welche Startadresse das Programm hat und fertig ist die Laube. Nun möchte ich aber 1. kein Dateiformat verwenden, dass ne Menge Bytes an Header verbraucht. und 2. will ich kein neues Dateiformat erfinden. Wie löst man dann so ein Problem? Nun ja, mein Vorschlag wäre, dass man am Anfang folgende Codesequenz einfügt: PHP: Allerdings muss dieses Stück Code direkt vor der ersten Funktion stehen.
Das müsste dann normalerweise funktionieren. Ansonsten gibt es eigentlich nichts mehr zum sagen. Das nächste Tutorial der Betriebssystem Reihe behandelt das Programmieren eines Dateisystems. Bis dahin, viel Spaß beim Programmieren. -THE END- Dieses Tutorial wurde von biehler am 22.01.2006 verfasst. Kommentare
|
nach oben
Copyright © 2012 cybton-network