Autor Thema: Fragen: OS Design - Kernel, Treiber, Module, Relocational ELF  (Gelesen 14237 mal)

Krendor

  • Beiträge: 19
    • Profil anzeigen
Gespeichert
Hallo Kollegen der Hobbybetriebssystementwicklung,

nach langer Zeit steige ich nun wieder in die Betriebssystementwicklung ein und habe auch schon eine Vorstellung davon, was mein Betriebssystem können soll. Die Featureliste ist schon recht lange, aber nur, weil ich alles im Detail erwähne. Zusammengefasst in folgende Begriffe:
Paging, multithreading, Inter process communication, vga text modus, shell, porting gcc + standard c lib, executing elf files, read cdrom, read/write hdd, FAT12/16/32 und iso9660.
Damit sollte man schon etwas arbeiten können. Später kann man das ganze ja um Sound, netzwerk, acpi, usw. erweitern.

Jetzt langsam zu meiner Frage. Ich will nur sicher gehen, dass ich keinen Denkfehler mache. Ich definiere Interfaces (C++) für die Treiber und die Treiber implementieren diese dann. Z.B. gibt es ein Interface FAT12 oder meinetwegen auch FAT allgemein, und für jedes Filesystem wird dann ein Objekt dieses Interfaces angelegt. Die Treiber laufen also im Ring 0, also im Kernel. Die Treiber liegen als ELF Relocation Datei vor und werden vom Kernel geladen. Es gibt 4 Funktionen, die implementiert werden müssen: GetInfo gibt z.B. zurück, was für ein Treiber das überhaupt ist | getInstance gibt ein Object des Treibers zurück (das sich an das Interface hält), Initialize and Release sind wohl selbsterklärend. GRUB2 lädt nur die Treiber als Module, die für das nachladen weiterer Treiber nötig sind. Der Kernel geht die Module durch, schaut nach was für ein Treiber es ist, löst die Relocations auf, und kann ihn dann nutzen. Für Anwenderprogramme in Ring 3 gibt es dann eine eigene API, die per Interrupt mit dem Kernel kommuniziert.

Habe ich dabei irgendwas übersehen?

Viele Grüße
Krendor

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #1 am: 14. February 2015, 14:44 »
Klingt mehr oder weniger nach den Standardvorgehen für einen modularisierten Monolithen. Ich denke, du bist auf dem richtigen Weg.

Das Treiber-Interface sollte aber natürlich weder FAT12 noch FAT sein, sondern was allgemeines für Dateisysteme, wo du dann später auch z.B. einen ext2-Treiber einfach reinhängen kannst.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

Krendor

  • Beiträge: 19
    • Profil anzeigen
Gespeichert
« Antwort #2 am: 14. February 2015, 16:19 »
Zitat
Das Treiber-Interface sollte aber natürlich weder FAT12 noch FAT sein, sondern was allgemeines für Dateisysteme, wo du dann später auch z.B. einen ext2-Treiber einfach reinhängen kannst.
Da hast du natürlich recht. :D Ich war anscheinend schon bei der Implementierung. Diese kann man bei FAT ja eigentlich zusammenfassen.

Die Funktion GetInfo soll ja unter anderem eine Interface-ID zurückgeben, damit man weiß, welches Interface der Treiber implementiert. (getInstance liefert ja nur einen Zeiger auf ein Objekt zurück) Jetzt muss mir nur noch eine Lösung für folgendes Problem einfallen: Wer und wie erkennt wann, welcher Treiber benötigt wird? Um bei den Dateisystemen zu bleiben: Der ATA Treiber erkennt eine Festplatte, wie erkenne ich, ob ich eine Instanz von FAT, EFS, oder sonstwas brauche? Soll einfach jeder Treiber mal geladen und getestet werden? Soll der ATA Treiber das Dateisystem erkennen sollen? (Dann hätte er wissen, das er nicht haben dürfte. Wird problematisch, wenn es einen Treiber für ein neues Dateisystem gibt. Dann müsste man auch den ATA Treiber anpassen)

Ich wollte erstmal einfach alle Treiber aus dem Verzeichnis Driver laden. Wahrscheinlich erstelle ich eine Liste pro Interface mit den zugehörigen Treibern. Vielleicht bekommt der Kernel auch eine Funktion Interface getDriverByIID(InterfaceID, index), dann könnte jeder Treiber sich seinen Subtreiber selber erstellen. Aber wie mache ich das bei folgender Struktur? PCI Treiber durchsucht alle PCI Geräte, für jeden gefundenen DiskController erstellt er eine Instanz des zugehörigen Treibers, dieser erstellt pro angeschlossenem ATA/ATAPI Device wieder eine Instanz, diese wiederum pro Dateisystem ebenfalls eine Instanz. Aber am Ende muss es genau eine Instanz des VFS Treibers geben, der dann alle Instanzen der Dateisysteme zusammenführt.

Viele Grüße
Krendor

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #3 am: 15. February 2015, 00:15 »
Wer und wie erkennt wann, welcher Treiber benötigt wird? Um bei den Dateisystemen zu bleiben: Der ATA Treiber erkennt eine Festplatte, wie erkenne ich, ob ich eine Instanz von FAT, EFS, oder sonstwas brauche? Soll einfach jeder Treiber mal geladen und getestet werden?
Im Falle einer Festplatte brauchst du vermutlich erstmal einen Partitions-Layer, der erstmal MBR (ggf. GPT) zerpflückt. Wenn der durchgelaufen ist, dann hast du statt einer Festplatte mehrere voneinander streng zu trennende Blöcke (Partitionen), und du kennst von jeder Partition den Typen (= Dateisystem). Da die Typen nicht immer eindeutig sind (Linux-Partitionen haben alle die MBR-ID 0x83), kannst du dafür einfach eine feste Liste von Dateisystemen nacheinander ausprobieren.

Wenn diese Heuristik fehlschlägt, dann muss der Benutzer eingreifen. Einfach mal alle Dateisystemtreiber auf ein Block Device loszulassen, ist jedenfalls unelegant, weil nicht alle Dateisysteme eindeutige Headerfelder haben. Wenn ein solcher Treiber fälschlicherweise glaubt, er versteht einen Garbage-Header, zerstörst du dir u.U. Partitionen, die du nie hättest anfassen sollen. (Windows ist bekannt dafür, gewissen Linux-Partitionen einen Laufwerksbuchstaben zuzuweisen und bei einem Zugriff zu fragen, ob das komische Ding da formatiert werden soll. Passiert aber nur selten.)

Ich wollte erstmal einfach alle Treiber aus dem Verzeichnis Driver laden. Wahrscheinlich erstelle ich eine Liste pro Interface mit den zugehörigen Treibern.
Je nachdem, was du für Hardware unterstützen möchtest, musst du unterscheiden zwischen erkennbaren und nicht erkennbaren Geräten.

Erkennbare Geräte sind einfacher: Wenn du einen PCI-Bustreiber lädst, dann kann er dir sagen, welche Geräte an ihm angeschlossen sind. Diese Information (und die eindeutige ID) könntest du z.B. in eine Art "Event" verpacken und an den Kernel schicken, damit der dann den entsprechenden Treiber lädt - und zwar auch zur Laufzeit. Die meisten für dich wichtigen PC-Geräte funktionieren so, also PCI, USB, SATA, ISAPnP, Firewire usw. Wenn du das ordentlich hinbekommst, dann hast du auch die Grundlage für Hot-Plugging gelegt.

Nicht erkennbare Geräte kannst du nicht erkennen. Du kannst nur auf gut Glück einen Treiber auf eine bestimmte Hardware-Adresse laden und schauen, ob der Treiber dort seine Hardware findet. Das heißt, du brauchst irgendwo eine Liste von bekannten Standardadressen, wo du die dafür vorgesehenen Treiber einfach mal lädst und guckst, was passiert. Das betrifft alle Geräte, die entweder historisch gewachsen oder extrem simpel sind, also per ISA, I²C, SPI oder ähnlich beschränkten Bussystemen angeschlossen sind. Manche modernen Geräte kannst du wegen Kompatiblität auch so behandeln (USB-Tastatur, PCI-VGA). Beispiele sind die PS/2-, COM- und LPT-Schnittstellen, IDE-Festplatten (aber nicht unbedingt der dazugehörige IDE-Controller), viele Mainboardgeräte (z.B. Flashbausteine, Temperatursensoren), oder der DDC/EDID-Baustein in Bildschirmen.

Wenn du alle Geräte als "nicht erkennbar" behandelst - was auch gut funktioniert, zumindest für statische Geräte - dann musst du aber bedenken, dass du die Treiber in einer gewissen Reihenfolge laden musst, und dass du gewisse Treiber u.U. auch mehrfach laden musst (z.B. PCI-PCI-Bridges).

PCI Treiber durchsucht alle PCI Geräte, für jeden gefundenen DiskController erstellt er eine Instanz des zugehörigen Treibers, dieser erstellt pro angeschlossenem ATA/ATAPI Device wieder eine Instanz, diese wiederum pro Dateisystem ebenfalls eine Instanz. Aber am Ende muss es genau eine Instanz des VFS Treibers geben, der dann alle Instanzen der Dateisysteme zusammenführt.
Exakt. Entweder, alle aktiven Dateisysteme müssen sich im einzigen VFS-Treiber anmelden ("Mounten"), oder jedes Dateisystem stellt sein eigenes VFS-Interface bereit ("Laufwerksbuchstaben").

Krendor

  • Beiträge: 19
    • Profil anzeigen
Gespeichert
« Antwort #4 am: 15. February 2015, 11:10 »
Zitat
oder jedes Dateisystem stellt sein eigenes VFS-Interface bereit ("Laufwerksbuchstaben").
Die Idee gefällt mir, weil ich sowieso auf Laufwerksbuchstaben hinaus wollte. Aber dise Buchstaben müssen ja auch verwaltet werden. (Damit es keine doppelten gibt) Also werde ich doch ein zentrales VFS benötigen... Das kann ich dann bei Bedarf auch schnell zu einem Linux-Style VFS umprogrammieren.  :-D Da fällt mir ein: Das VFS kann sich ja einfach alle Instanzen der Dateisystem Treiber geben lassen. 8-)

Zitat
dass du die Treiber in einer gewissen Reihenfolge laden musst, und dass du gewisse Treiber u.U. auch mehrfach laden musst (z.B. PCI-PCI-Bridges).
Das mit den PCI-PCI-Bridges verstehe ich noch nicht so ganz. Ich weiß, dass sie 2 Busse miteinander verbinden, aber sind die nicht schon vom BIOS initialisiert? Ist nicht alles vom BIOS initialisiert was PCI betrifft?
Allerdings muss ich Treiber nicht mehrfach laden; Ich erstelle nur eine weitere Instanz vom Treiber. (c++) ;)

Ich denke, ich programmiere erst mal den kernel fertig. Der muss noch die physiche und virtuelle Speicherverwaltung bekommen. Danach fange ich einfach mal mit den Interfaces an und merke ja sofort, wenn sich Probleme ergeben und. Danach kommt Multithreading und die Treiber. Und dann als erstes eine Shell...

Edit: Vielleicht sollte ich einfach alle Treiber in den Kernel integrieren. Das ist ja nur ein Hobbyprojekt und so groß wird das sicher auch nicht.
« Letzte Änderung: 15. February 2015, 12:54 von Krendor »

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #5 am: 15. February 2015, 16:47 »
Zitat
oder jedes Dateisystem stellt sein eigenes VFS-Interface bereit ("Laufwerksbuchstaben").
Die Idee gefällt mir, weil ich sowieso auf Laufwerksbuchstaben hinaus wollte. Aber dise Buchstaben müssen ja auch verwaltet werden. (Damit es keine doppelten gibt) Also werde ich doch ein zentrales VFS benötigen...
Ja, aber das kann man auch einfacher haben: Ein Array aus 26 Einträgen, wo jeweils ein Handle auf eine Dateisystems-Instanz abgelegt wird, und zwei Funktionen GibMirLaufwerksbuchstaben() und IchGebLaufwerksbuchstabenZurueck().

Zitat
dass du die Treiber in einer gewissen Reihenfolge laden musst, und dass du gewisse Treiber u.U. auch mehrfach laden musst (z.B. PCI-PCI-Bridges).
Das mit den PCI-PCI-Bridges verstehe ich noch nicht so ganz. Ich weiß, dass sie 2 Busse miteinander verbinden, aber sind die nicht schon vom BIOS initialisiert?
Eine PCI-PCI-Bridge schenkt dir einen neuen PCI-Bus, nachdem du sie initialisiert hast. Aber was ich meinte, war allgemeiner. Wenn du z.B. ein Notebook mit CardBus-Schnittstelle hast, dann ist diese Schnittstelle ein "PCI-Gerät". Und wenn du den Treiber für dieses Gerät lädst, dann taucht ein neuer PCI-Bus auf, mit der evtl. eingesteckten Karte drin. Entweder, dein Kernel erzeugt dann ein Event "neue Hardware gefunden", oder du musst alle deine PCI-Treiber neu laden, damit sie vielleicht jetzt etwas finden, was sie vorher nicht gefunden haben.

Gut, das sind alles ziemliche Spezialfälle, die du nicht unterstützen musst, aber vorher drüber nachdenken schadet sicherlich nicht. ;-)

Ist nicht alles vom BIOS initialisiert was PCI betrifft?
Das weiß ich nicht. Ich kann mir aber gut vorstellen, dass manche BIOSse nur das initialisieren, was sie zum booten brauchen.

Edit: Vielleicht sollte ich einfach alle Treiber in den Kernel integrieren. Das ist ja nur ein Hobbyprojekt und so groß wird das sicher auch nicht.
Dann brauchst du dir über meine Hinweise auch keine großen Gedanken machen. ;-)

Krendor

  • Beiträge: 19
    • Profil anzeigen
Gespeichert
« Antwort #6 am: 17. February 2015, 13:10 »
Zitat
Das weiß ich nicht. Ich kann mir aber gut vorstellen, dass manche BIOSse nur das initialisieren, was sie zum booten brauchen.
Soll ich also am besten alle Geräte neu initialisieren? Das stelle ich mir nur im Falle von I/O Ports schwer vor, da ich nicht weiß, welche Ports schon vom System benutzt werden...

Zitat
Eine PCI-PCI-Bridge schenkt dir einen neuen PCI-Bus, ...
Tja, da hapert es wohl noch mit meinem Verständnis. Auf PCI greife ich doch über 2 Register zu. (1 mal Adresse und 1 mal Daten) Das Adressregister ist aufgeteilt in Bus, Gerät und Funktion. Damit habe ich doch schon mehrere Busse. Wofür dann noch Bridges? Entweder kann ich auf die anderen Busse nur zugreifen, wenn die Bridge richtig angelegt ist, oder die Bridge selber stellt nochmal 2 Ports (Daten und Adresse) zur Verfügung. Das wird im Wiki leider nicht so richtig ersichtlich.

Ich dachte in diesem Fall auch eher, dass ich einen Treiber PCI habe, der alle Geräte durchsucht und dann Instanzen von PCIDevice anlegt. PCIDevice wiederum scannt das Gerät und lädt den entsprechenden Treiber nach. (Disk Controller, VGA, usw.)  In dem Falle hat der Treiber PCI einfach eine Methode "rescan", die erneut nach Geräten sucht, aber nur für neue Geräte die Treiber lädt.
(Wenn ich vom Treiber laden rede, dann meine ich, dass der Treiber nur geladen wird, wenn er noch nicht geladen ist, und dass dann eine neue Instanz von der Klasse angelegt wird.)

Zitat
Dann brauchst du dir über meine Hinweise auch keine großen Gedanken machen.
Hab ich auch erst gedacht, aber dann ist mir eingefallen, dass ich trotzdem Treiberklassen brauche und mir somit über alles klar werden muss. :-D Außerdem will ich ja lernen... :wink:

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #7 am: 18. February 2015, 13:40 »
Zitat
Das weiß ich nicht. Ich kann mir aber gut vorstellen, dass manche BIOSse nur das initialisieren, was sie zum booten brauchen.
Soll ich also am besten alle Geräte neu initialisieren? Das stelle ich mir nur im Falle von I/O Ports schwer vor, da ich nicht weiß, welche Ports schon vom System benutzt werden...
Initialisiere einfach alles, was du brauchst, und ignoriere den Rest. Damit fährst du für den Anfang gut genug.

Was die I/O-Ports angeht: Das ist genau das, was ich weiter oben beschrieben habe. Einfache (ISA-)Geräte kannst du nicht identifizieren, sondern nur auf gut Glück ein paar Ports lesen/schreiben und schauen, ob das, was da passiert, ungefähr so aussieht wie das, was dein gesuchtes Gerät dort tun würde. Deswegen gibt es da nichts zu initialisieren.

Das Adressregister ist aufgeteilt in Bus, Gerät und Funktion. Damit habe ich doch schon mehrere Busse. Wofür dann noch Bridges?
Bridges sind ein Hardware-Feature. Die kriegst du, weil die Hardware so gebaut ist, und vermutlich kannst du sie auch erstmal ignorieren.

Ich wollte damit nur sagen, dass du manche Geräte erst sehen/ansprechen kannst, wenn du bestimmte andere Geräte funktionierend in deinen Kernel integriert hast.

In dem Falle hat der Treiber PCI einfach eine Methode "rescan", die erneut nach Geräten sucht, aber nur für neue Geräte die Treiber lädt.
Ja, klingt gut.

Krendor

  • Beiträge: 19
    • Profil anzeigen
Gespeichert
« Antwort #8 am: 18. February 2015, 15:44 »
Zitat
Ich wollte damit nur sagen, dass du manche Geräte erst sehen/ansprechen kannst, wenn du bestimmte andere Geräte funktionierend in deinen Kernel integriert hast.
Dessen bin ich mir bewusst. Aber trotzdem danke. :-)

Zitat
Was die I/O-Ports angeht: Das ist genau das, was ich weiter oben beschrieben habe. Einfache (ISA-)Geräte kannst du nicht identifizieren, sondern nur auf gut Glück ein paar Ports lesen/schreiben und schauen, ob das, was da passiert, ungefähr so aussieht wie das, was dein gesuchtes Gerät dort tun würde. Deswegen gibt es da nichts zu initialisieren.
Aber ein PCI Gerät kann in den BARs auch nach IO-Ports verlangen. Das wird durch eine 1 in Bit 0 im BAR angezeigt. ein Massenspeichercontroller für ATA oder SATA würde da z.B. die standard Ports 0x1F0 bis 0x1F7 haben.

Edit: Eine Frage noch zu den Descriptoren: Muss ich lgdt, lidt, usw. physische oder virtuelle Adressen nach der Aktivierung von Paging übergeben?
« Letzte Änderung: 19. February 2015, 11:49 von Krendor »

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #9 am: 19. February 2015, 13:53 »
Aber ein PCI Gerät kann in den BARs auch nach IO-Ports verlangen. Das wird durch eine 1 in Bit 0 im BAR angezeigt. ein Massenspeichercontroller für ATA oder SATA würde da z.B. die standard Ports 0x1F0 bis 0x1F7 haben.
Ja, bei solchen Geräten weißt du dann genau, dass an den betreffenden I/O-Ports ein Gerät hängt und brauchst nicht rumzuprobieren. ;-)

 

Einloggen