Autor Thema: Implementatoin eines Kernels  (Gelesen 8691 mal)

jeb

  • Beiträge: 341
    • Profil anzeigen
    • http://www.jebdev.net
Gespeichert
« am: 22. July 2005, 17:33 »
hallo
Solange mein OS nicht richtig läuft (versuchs grad mit GRUB) mache ich mir immer wieder mal theoretische Gedanken. Doch ich versteh überhaupt nicht, wie ich den Kernel implementieren soll. Der Grundgedanke ist doch dieser.

1. Das OS startet und initialisiert alles.
2. Es möchte gerne einen Text ausgeben.
3. Es sagt dem Kernel, dass dieser für es einen Text ausgeben soll.

Nun muss das OS doch irgendwie mit dem Kernel kommunizieren können. Wenn ich jedoch beide Teile einzeln kompilieren will, kann ich ja keine externen Verlinkungen machen, sonst müsste ich sie zusammen kompilieren und kann z.B. nicht einfach mal schnell den Kernel wechseln.

Nächste Frage: Wie funktioniert ein Mikrokernel? Ich kann ihn irgendwie nicht vom Monolithischen Kernel unterscheiden. Der Mikrokernel muss ja wissen, dass Treiber usw existieren. Wie aber kann er das, wennn diese gar nirgends bei ihm registriert sind? Wenn der Kernel weiss, welche Treiber vorhanden sind und diese dann über externe Verlinkungen aufruft ist das ja dann schon ein monolithischer Kernel. Wie funktioniert das?

Letzte Frage: Was hat es mit den Speichermodellen ELF und PE auf sich. Wahrscheinlich habe ich bis jetzt immer das flache Binärformat verwendet. Wo finde ich Informationen, wie ich diese Formate verwenden kann?
Heute musste ich lange Zugfahren und las wieder einmal ein paar OS-Tuts durch. Da stand was von Cygwin-Port für Win um das OS zu kompilieren. Könnte mir das jemand mal genauer erläutern?

Ich weiss es sind etwas viel und komplexe Fragen. Vielen Dank für die Antworten


JEB

SSJ7Gohan

  • Beiträge: 398
    • Profil anzeigen
Gespeichert
« Antwort #1 am: 22. July 2005, 18:35 »
Deine Programme können entweder über Callgates oder über Interrupts/Traps mit dem Kernel kommunizieren.

In einem Microkernel sind die Treiber Prozesse, und sie werden über IPC wie z.b. Sockets oder FIFOs angesprochen.

Cygwin stellt Posixfunktionen unter Windows bereit, so kann man z.B. den gcc von Cygwin benutzen um ein OS zu kompilen, DJGPP ist aber auch nicht schlechter.

ELF Files sind eigentlich ganz leicht zu lesen, dazu gibt es z.B. bei OSDR ein Dokument.

Hier sind einige Codeschnippsel. Der Code stammt eigentlich aus LegendOS, ich habe ihn nur geringfügig geändert
Header:
/*  LegendOS - Operating System
 *  Copyright (C) 2003 Joachim Nock
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
#ifndef __ELF_H__
#define __ELF_H__

/* *** ELF Header *** */

#define EI_NIDENT 16

/* Elf32_Ehdr->e_ident */
#define EI_MAG0   0
#define EI_MAG1    1
#define EI_MAG2    2
#define EI_MAG3    3
#define EI_CLASS   4
#define EI_DATA    5
#define EI_VERSION 6

#define ELFMAG0 0x7F
#define ELFMAG1 'E'
#define ELFMAG2 'L'
#define ELFMAG3 'F'

#define ELFCLASS32 1

#define ELFDATA2LSB 1
#define ELFDATA2MSB 2

/* Elf32_Ehdr->e_type */
#define ET_EXEC 2
#define ET_DYN  3
/* Rest seems unintersting for a kernel */

/* Elf32_Ehdr->e_version */
#define EV_CURRENT 1

/* Elf3_Ehdr->e_machine */
#define EM_386 3
/* Only platform that I'm developing for at this moment */

struct Elf32_Ehdr {
unsigned char e_ident[EI_NIDENT];
unsigned short e_type;
unsigned short e_machine;
unsigned long e_version;
unsigned long e_entry;
unsigned long e_phoff;
unsigned long e_shoff;
unsigned long e_flags;
unsigned short e_ehsize;
unsigned short e_phentsize;
unsigned short e_phnum;
unsigned short e_shentsize;
unsigned short e_shnum;
unsigned short e_shstrndx;
} __attribute__ ((packed));

typedef struct Elf32_Ehdr Elf32_Ehdr;

/* *** Program header *** */

/* Elf32_Phdr->p_type */
#define PT_NULL    0
#define PT_LOAD    1
#define PT_DYNAMIC 2
#define PT_INTERP  3
#define PT_NOTE    4
#define PT_SHLIB   5
#define PT_PHDR    6
#define PT_LOPROC  0x70000000
#define LT_HIPROC  0x7FFFFFFF

struct Elf32_Phdr {
unsigned long p_type;
unsigned long p_offset;
unsigned long p_vaddr;
unsigned long p_paddr;
unsigned long p_filesz;
unsigned long p_memsz;
unsigned long p_flags;
unsigned long p_align;
} __attribute__ ((packed));

typedef struct Elf32_Phdr Elf32_Phdr;

#endif /* __ELF_H__ */


Diese Funktion erzeugt ein Prozess aus einer in den Speicher geladenen ELF File: void createProcessFromElf( char *id, char *buffer ) {
tProcess *newProcess;

unsigned long i;
Elf32_Ehdr* header = (Elf32_Ehdr*)buffer;

if ((header->e_ident[EI_MAG0] != ELFMAG0) ||
(header->e_ident[EI_MAG1] != ELFMAG1) ||
(header->e_ident[EI_MAG2] != ELFMAG2) ||
(header->e_ident[EI_MAG3] != ELFMAG3)) {
printf("Unsupported file format\n");

return;
}

newProcess = createProcess( id, (void *)header->e_entry, 0, 0 );
setPageDirectory( resolveAddress( g_memorySystemDirectory, (unsigned long)newProcess->processPages) );

Elf32_Phdr *pheader = (Elf32_Phdr*)&buffer[header->e_phoff];

for( i = 0; i < header->e_phnum; i++, pheader++) {
// allocate memory for the section
if( pheader->p_memsz > 0 ) {
unsigned long padding = pheader->p_vaddr % 4096;

directoryAllocateBetween( newProcess->processPages, pheader->p_vaddr - padding, pheader->p_vaddr + pheader->p_memsz + padding );

memset( (void *)(pheader->p_vaddr - padding), 0, pheader->p_memsz + padding );
memcpy( (void *)(pheader->p_vaddr - padding), &buffer[pheader->p_offset - padding], pheader->p_filesz + padding );
}
}

setPageDirectory( resolveAddress( g_memorySystemDirectory, (unsigned long)g_memorySystemDirectory ) );

printf( "ELF File Entry %X\n", header->e_entry );

newProcess->next = parentProcess->next;
parentProcess->next = newProcess;
}

Die obrige Funktion erwartet einen String, der jedoch nur der Identifikation des Prozesses in meinem OS dient und den Pointer auf eine ELF File und erzeugt einen Prozess aus der ELF File, indem sie ein Pagedirectory für den Prozess erstellt, die Segmente der ELF File dort reinkopiert und den Prozess in eine linked list für den Scheduler schreibt.
Die Funktion directoryAllocateBetween( unsigned long *directory, unsigned long start, unsigned long end ) allocated alle pages zwischen den Addressen start und end im directory directory.
Du wirst warscheinlich die memory und prozesse nicht so wie ich implementieren, aber so gewinnst du schonmal einen Eindruck, wie man die Files läd. Bei shared libs wird das ganze noch komplizierter, da man dann die Relocate und Symboltables usw. lesen muss.

Legend

  • Beiträge: 635
    • Profil anzeigen
    • http://os.joachimnock.de
Gespeichert
« Antwort #2 am: 22. July 2005, 20:20 »
Na nu, jemand der meinen Code fuer gut genug befunden hat um ihn zu uebernehmen! ;)

Nun ja, es gibt verschiedene Definitionen was ein OS ist. Eine ist das der Kernel gerade das OS ist. Das ist bei monolithischen Kernel auch noch relativ passend.
D.h. bei deinem Hello World-Beispiel laeuft dies dann eher so ab:
1. Der Kernel wird vom Bootloader geladen.
2. Der Kernel gibt was auf dem Bildschirm aus.

Das sieht, wenn man es so betrachtet, noch nicht sehr spannend aus, besonders da wir ja noch keine Programme haben, welche der Kernel verwalten koennte.


Der Microkernel, im Extremfall, brauch gar nicht zu wissen welche Treiber laufen. Er muss sich nur um Prozesse, wohl noch um Speicher, und den Hardwarezugriff der Prozesse kuemmern.
Dies wird natuerlich komplizierter wenn man beim Microkernel Texte im Kernel ausgeben will, oder Swappen (wenn der Kernel das denn uebernimmt) will.
*post*

jeb

  • Beiträge: 341
    • Profil anzeigen
    • http://www.jebdev.net
Gespeichert
« Antwort #3 am: 22. July 2005, 22:17 »
Wenn du sagst Callgates: Wie gross ist denn ein Kernel in der Regel, dass er eigentlich die ganze Zeit im Speicher geladen sein muss?

SSJ7Gohan

  • Beiträge: 398
    • Profil anzeigen
Gespeichert
« Antwort #4 am: 22. July 2005, 22:34 »
Das kommt auf den Kernel an, ein Microkernel kann mit sehr wenig Speicher auskommen, ein monolithischer Kernel nimmt viel Speicher ein, da die gesammten Treiber evt. in alle Prozesse gemappt werden.

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #5 am: 22. July 2005, 23:55 »
Ein Mikrokernel kann in ein paar Kilobyte passen, ein monolithischer halt je nachdem. Einen Linuxkernel kriegst du auf 200 KB, aber auch auf 4 MB ...

Du kannst auch einen monolithischen Kernel auslagern, wenn du Module oder ähnliche Strukturen reinbringst (also den Kernel im Speicher dynamisch zusammenlinkst etc) und dann muss halt nur noch die Nachladeroutine (und der Plattentreiber o.ä.) mi Speicher gehalten werden. So ähnlich wie die command.com an zwei Speicherbereiche geladen wird und bei großen Programmen überschrieben wird (Oberkante 640 KB; untere Kante 640 KB)

Einen Mikrokernel stelle ich mir irgendwie anders vor... der hat ja keine Ahnung, was Sache ist und müsste demnach immer komplett im Speicher sitzen.

Der Kernel läuft woanders und du weißt nicht wo, also was machst du? Rufst die Auskunft an *g* oder nimmst einen Interrupt oder ein Gate. Dann landet die Anfrage ja immer an der gleichen Stelle. Wobei ich nicht weiß, ob Windows oder Linux den Kernel auch immer an die gleiche Stelle laden oder nicht.

Ein Mikrokernel kriegt (so vermut ich mal) eine Anfrage "neuer Task xyz gestartet, Adressbereich abc-def" und richtet diesen Speicherbereich als "belegt" ein. Ein Treiber meldet sich mit "Brauche Hardware xy, Ressourcen yz" und der Kernel erlaubt oder verweigert dies, auch die Speicheraufteilung (ich brauch Speicher!) muss er verwalten. Alles andere macht dann ein als Anwendung(?) realisierter Treibermanager, der die Hardware und die Treiber kennt und alles derartige verwaltet. Läuft dieser als Kernelprogramm mit Kernelrechten ist es ein monolithischer :-) Lieg ich da richtig?

ELF und PE sind imo Binärformate, keine Speichermodelle. Dadrin stehen Sachen wie Startadresse, Betriebssystem(mindest)version und andere mehr oder weniger unwesentliche Informationen.

Cygwin emuliert einen POSIX-Layer, um Posix-kompatible Anwendungen (gcc, bash, X, eggdrop, ...) auf einem Nicht-Posix-kompatiblem System (Windows) ohne Codeänderung zum Laufen zu kriegen. Man muss es trotzdem neukompilieren, aber da es bestimmte Regeln gibt, läuft der Code wie er ist mit keinen bis wenigen Änderungen.

Hoffe, geholfen zu haben und nicht zu viel Unsinn geschrieben zu haben :)

Svenska

joachim_neu

  • Beiträge: 1 228
    • Profil anzeigen
    • http://www.joachim-neu.de
Gespeichert
« Antwort #6 am: 23. July 2005, 09:04 »
Hi,

Ich gehe nach dem System, die wichtigsten Daten immer im Speicher zu halten (VideoRAM, VideoRAMBuffer, PageStack, KernelTables, Kernel, Sonstige Tabellen) und damit ist mein Kernel im Speicherverbrauch bei 16 MB. Auf der Diskette hat er noch nichteinmal 9k. ;) Dadurch habe ich bei Zugriffen systemintern sehr schnelle Reaktionsraten, allerdings sind auch geringfügig einige Megabytes weg, wobei ich denke, dass 64MB RAM wohl heute das minimum sein werden für einen Rechner mit dem man was anfangen will. ;)

Praktisch kann es sein, wenn du die API schon in einen anderen Task auslagerst. Damit entlasstest du den Kernel, weil er nichtmehr so viele Aufrufe abbekommt und die API kann gewechselt werden, ohne, dass der Kernel geändert wird.

J!N
http://www.joachim-neu.de | http://www.orbitalpirates.de | http://www.middleageworld.de

System: 256 RAM, GeForce 2 MX 400, AMD Athlon XP 1600+, Windows XP, 1x Diskette, 1x DVD-ROM, 1x CD-R(W) Brenner,...

mastermesh

  • Beiträge: 341
    • Profil anzeigen
    • http://www.kostenloser-laptop.de/
Gespeichert
« Antwort #7 am: 23. July 2005, 11:14 »
Anmerkung: ein Microkernel ist nur in Relation zu einem monolithischen Kernel klein. In Wirklichkeit erhöht sich durch die vielen Abstraktionen der Overhead erheblich. Das ist auch der Grund, warum Microkernels meistens langsamer sind.

jeb

  • Beiträge: 341
    • Profil anzeigen
    • http://www.jebdev.net
Gespeichert
« Antwort #8 am: 23. July 2005, 11:17 »
Also ich forumiler das ganze jetzt mal etwas um:

Das Betriebsystem braucht keinen Kernel sondern Treiber. Der sogenannte Mikrokernel ist eigentlich nur der Treiber für die Speicherverwaltung.

Gut. Wenn das stimmt, wäre das denn eine sinnvolle implementation:

Das Betriebssystem startet und lädt den Mikrokernel. Danach schaut es nach, wieviele Treiber in eimem definierten Ordner vorhanden sind und erstellt mit diesen eine Tabelle. Dass OS kann dann wenn es diesen Treiber braucht, in der Tabelle nachschauen, ob der Treiber im Speicher steht. Wenn ja, so ruft man das dort angegebene Callgate auf. Ansonsten macht man einen neuen Deskriptor und lädt den Treiber in den Speicher. Danach wird der Deskriptor in der Tabelle eingetragen. Der Treiber kann nun verwendet werden.

So langsam versteh ich den Zusammenhang. Bewertungen erwünscht.

jeb

jeb

  • Beiträge: 341
    • Profil anzeigen
    • http://www.jebdev.net
Gespeichert
« Antwort #9 am: 23. July 2005, 12:49 »
@ELF: Kann mir jemand ein gutes Tutorial zu ELF sagen. Ich finde kein gutes. Muss ich denn die ganzen Daten aus den Headern auslesen können? Wenn ich das richtig verstanden habe liegt der Vorteil von ELF darin, dass man zuerst eine Unterroutine starten kann, bevor das Programm startet. Kann mir das jemand auch nochmal etwas genauer erklären?

Legend

  • Beiträge: 635
    • Profil anzeigen
    • http://os.joachimnock.de
Gespeichert
« Antwort #10 am: 23. July 2005, 14:09 »
Zitat von: mastermesh
Anmerkung: ein Microkernel ist nur in Relation zu einem monolithischen Kernel klein. In Wirklichkeit erhöht sich durch die vielen Abstraktionen der Overhead erheblich. Das ist auch der Grund, warum Microkernels meistens langsamer sind.


Nun ja, mehr dadurch das man evtl. task switches durchfuehren muss, um an etwas dranzukommen. Wenn dein monolithischer Kernel sauber aufgebaut (zumindestens meiner Meinung nach), wird er intern sogar relativ aehnlich aussehen - nur das alles im Ring 0 ist.
*post*

jeb

  • Beiträge: 341
    • Profil anzeigen
    • http://www.jebdev.net
Gespeichert
« Antwort #11 am: 24. July 2005, 10:52 »
Gut. Komme ich zu den Binärformaten:

Worin liegen die Vor-, Nachteile und Unterschiede von ELF und PE?
Warum ist es empfehlenswert, ein Binärformat zu verwenden als einfach alles zu einer rohen Binärdatei zu assemblieren?

SSJ7Gohan

  • Beiträge: 398
    • Profil anzeigen
Gespeichert
« Antwort #12 am: 24. July 2005, 11:28 »
ELF bietet die folgenden Features:
(PE sollte sie auch in etwa bieten, damit kenne ich mich aber nicht aus)
- Relocateables, damit lassen sich z.B. leicht Kernelmodule implementieren
- Shared Libraries
- Informationen über den Einstiegspunkt
- Symboltables der in der Executable verwendeten Symbole
- Aufteilung in die Code, Data, BSS und evt. anderen Sektionen, wichtig für den Bootloader, damit er weiß, wie viel Platz die File im Speicher verbraucht

 

Einloggen