Autor Thema: VFS-Konzept  (Gelesen 4204 mal)

oern

  • Beiträge: 44
    • Profil anzeigen
Gespeichert
« am: 13. May 2013, 15:54 »
Hallo,

ich entwickle gerade den Treibermanager / ein VFS für mein OS und wollte es hier mal vorstellen. Kritik und Fälle, in denen das Konzept nicht ausreicht, sind erwünscht. :-)

Der Kernel ist ein modularer Monolith, d.h. die Kerneldatei beinhaltet nur den Prozess (mit Threads)-, Speicher-, Interrupt-, Modul- und Treiber/Dateimanager. Die Treiber werden von der Modulverwaltung entweder als Multibootmodul (für Boottreiber) oder aus einer Datei geladen. Dabei verwende ich ELF-Relocatables (wie der Linuxkernel). Wenn ein Modul geladen wird, wird (ebenfalls wie im Linuxkernel) eine Funktion mod_init aufgerufen, in der der Treiber z.B. Caches anlegen und sich beim Treibermanager anmelden kann. Beim Entfernen wird mod_destroy aufgerufen, die u.a. Aktionen aus mod_init rückgängig macht. Abhängigkeitsverwaltung wird es vorerst nicht geben.

Den grundlegenden Teil des VFS bilden die Treiber (Driver). Ein Treiber wird vom System anhand eines menschenlesbaren Namens identifiziert und kann Fähigkeiten (Capabilities) bereitstellen. Als Fähigkeiten sind erst mal geplant: Speichermedium, Dateisystem, Bus, HID (Human Interface Device) und Video (dass das nicht ewig reicht, ist mir klar, z.B. Netzwerk). Dies hat den Vorteil (gegenüber einem System, das nur Typen unterstützt), dass z.B. ein Treiber für die serielle Schnittstelle ja sowohl HID als auch Video-Gerät ist und man so nicht zwei Treiber zu registrieren muss. Ein Treiber stellt außerdem ein einziges Callback bereit, das als Parameter eine IO-Anfrage bekommt (dazu später mehr). Außerdem hat er eine Liste von Geräten, die von diesem Treiber verwaltet werden.

Ein Gerät wird vom System anhand zweier ID-Nummern identifiziert. Diese müssen nicht unbedingt benutzt werden (0 ist default), und ich denke, dass zwei ausreichen. Bei einem ATA-Treiber kann z.B. die erste für die Platte und die zweite für die Partition stehen, bei Grafikkarten eins für die Karte und eins für den Bildschirm usw. Das Gerät hat auch eine Fähigkeiten-Maske, d.h. ein Gerät, das ein Treiber registriert, muss nicht alle Funktionen unterstützen, die der Treiber bereitstellen kann. Es beinhaltet außerdem den Root-Knoten des Dateibaums des Gerätes.

Ein Knoten wird anhand eines menschenlesbaren Namens identifiziert (d.h. Verzeichniseinträge und Knoten werden nicht getrennt betrachtet). Ein Knoten hat Attribute (z.B. Zugriffzeit, ACLs, Größe) und einen Typ (nur Datei, Symlink oder Verzeichnis).

Um eine IO-Operation mit einem Knoten durchzuführen, muss man das oben genannte Callback des Treibers benutzen und eine IO-Anfrage vorbereiten. Die Anfrage enthält den Knoten, auf den sie sich bezieht, die Funktion, in der das Gerät benutzt wird (entspricht den Capabilities, es darf aber nur ein Bit gesetzt sein), den Typ der Anfrage (Lesen, Schreiben, Attribute lesen, Attribute Schreiben, Erstellen, Entfernen, etc.), Daten und die Größe der Daten.

Vollständige Pfade beginnen mit dem Namen des Treibers. Darauf folgen ggf. in Klammern bis zu zwei ID-Nummern (mit Komma getrennt) und ein Doppelpunkt. Danach folgt (ohne führendes '/', da ja sowieso von der Wurzel ausgegangen wird) der Pfad innerhalb des Gerätes, ganz "normal" mit Slashes abgetrennt. Beispiel: ata(0,1):foo/bar.txt oder vga:modeinfo.
An dem ersten Beispiel sieht man auch, dass Dateisysteme nicht über den Dateisystemtreiber, sondern über den Massenspeichertreiber angesprochen werden, der den zu benutzenden Treiber ermittelt (und natürlich zwischenspeichern darf).
Andere Treibertypen sollen vom Usermode nicht direkt über das VFS angesprochen werden, sondern es soll einheitliche Schnittstellen für die jeweiligen Fähigkeiten geben, z.B. hat ein Grafik/Videogerät standardmäßig eine Datei "modeinfo" in seinem Wurzelverzeichnis, aus der die verfügbaren Modi (vermutlich nur maschinenlesbar) ausgelesen werden können, und "control", in die (vermutlich auch nur maschinenlesbar) sozusagen "male ein Rechteck vom Punkt (a;b) zum Punkt (c;d)" geschrieben werden kann.

Noch weiß ich nicht genau, wie ich das Businterface gestalten soll, da damit sowohl z.B. PCI als auch USB verwaltet werden soll, ebenso wie das HID-Interface da man damit auch sowohl Zeigegeräte als auch Tastaturen etc. verwenden können soll.

Falls jemand bis hier gelesen haben sollte (oder auch sonst), vielen Dank, ich freue mich auf Kritik.
Viele Grüße,

oern

Svenska

  • Beiträge: 1 790
    • Profil anzeigen
Gespeichert
« Antwort #1 am: 13. May 2013, 23:54 »
Hallo,

Der Kernel ist ein modularer Monolith,
Werden Treiber im Kernel- oder im Userspace ausgeführt? :-)

Den grundlegenden Teil des VFS bilden die Treiber (Driver). Ein Treiber wird vom System anhand eines menschenlesbaren Namens identifiziert und kann Fähigkeiten (Capabilities) bereitstellen.
Der Begriff "Fähigkeit" passt nicht, weil du hier Geräteklassen identifizierst. Fähigkeiten wären eher "kann gelesen werden", "kann geschrieben werden" oder "kann Audio ausgeben", "kann Bilder einlesen".

dass z.B. ein Treiber für die serielle Schnittstelle ja sowohl HID als auch Video-Gerät ist und man so nicht zwei Treiber zu registrieren muss
Eine serielle Schnittstelle ist eine serielle Schnittstelle, nicht Eingabegerät und nicht Kamera. Es ist auch nicht zielführend, mehrere Treiber an eine serielle Schnittstelle zu binden, weil da nur ein Gerät dranhängen kann. (Wenn mehrere Geräte dranhängen, kann man z.B. einen Multiplex-Treiber dazwischen schieben, der von der Beschaltung abhängt. Beispielsweise serielle IR-Sender, die eine Rx-Leitung anbieten, weil der Pin noch frei war: Der Multiplexer bindet dann an die serielle Schnittstelle und stellt eine "serielle Schnittstelle" (nur lesen) und eine "IR-Sendediode" (nur schreibend) zur Verfügung.)

Außerdem hat er eine Liste von Geräten, die von diesem Treiber verwaltet werden.
Ich sehe den Treiber als notwendiges Übel, um mit einem Gerät zu kommunizieren. Dann ist es sinnvoller, die Liste der Treiber zu haben, die für die verschiedenen Geräte zuständig sind.

Ein Gerät wird vom System anhand zweier ID-Nummern identifiziert. Diese müssen nicht unbedingt benutzt werden (0 ist default), und ich denke, dass zwei ausreichen. Bei einem ATA-Treiber kann z.B. die erste für die Platte und die zweite für die Partition stehen, bei Grafikkarten eins für die Karte und eins für den Bildschirm usw.
Was machst du mit Subpartitionen (vgl. Minix, BSD, Solaris)? Bei Grafikkarten gibt es ebenfalls mehr Ebenen: Grafikkarte, Framebuffer, CRTC, Encoder, Output. Mit "nur zwei" schränkst du dich da sehr unnötig ein.

Vollständige Pfade beginnen mit dem Namen des Treibers.
Woher weiß ich, welcher Treiber für ein bestimmtes Gerät zuständig ist?

An dem ersten Beispiel sieht man auch, dass Dateisysteme nicht über den Dateisystemtreiber, sondern über den Massenspeichertreiber angesprochen werden, der den zu benutzenden Treiber ermittelt (und natürlich zwischenspeichern darf).
Das macht das Layering kaputt:
- Woher weiß der Massenspeichertreiber, welche Dateisystemtreiber es gibt und welche er benutzen darf?
- Warum sollte ein Anwendungsprogramm mit der Festplatte reden wollen, wenn es doch nur Dateien aus dem Dateisystem verarbeiten will?
- Was machst du mit virtuellen Dateisystemen, für die es keine Massenspeicher gibt ("Netzwerkumgebung" als Beispiel)?

Noch weiß ich nicht genau, wie ich das Businterface gestalten soll, da damit sowohl z.B. PCI als auch USB verwaltet werden soll
Im Prinzip musst du zwei grundsätzlich verschiedene Ansätze identisch abbilden können: autokonfigurierende Systeme (PCI, USB, ...) und manuell konfigurierte Systeme (ISA, I²C, serielle/parallele Schnittstelle, PS/2, ...). Letztere erfordern Vorwissen über die angeschlossene Hardware.

Das heißt, du musst einen Treiber manuell an einen Bus anhängen können (z.B. "ne2000 @ ISABUS io 0x280 irq 9"), aber der Bus muss das auch selbst können (z.B. "found 10ec:8029 => ne2000"). Letzteres ist für Hotplugging wichtig, kann aber auch von einem Userspace-Programm erledigt werden. Ansonsten muss der Bustreiber nur die Ressourcenverwaltung machen, je nach Bus gibt es da verschiedene Ressourcen, die auch systemweit sein können (ISA/PCI: io/irq/drq/mmio, I²C: Adresse, seriell: nix).

ebenso wie das HID-Interface da man damit auch sowohl Zeigegeräte als auch Tastaturen etc. verwenden können soll
Das wird schon schwieriger. Am besten ist es vermutlich, wenn du dich am Linux-evdev-System orientierst. Praktisch wirst du wenigstens "Tasten", "relative Achse" (Maus) und "absolute Achse" (Joystick, Touchscreen) und "sonstiges/herstellerspezifisch/Konfiguration" unterstützen wollen. Für genug Langeweile kannst du dir mal einen USB-HID-Descriptor zusammenzimmern. Da steckt unglaublich viel Komplexität hinter.

Ich habe das Gefühl, dass deine Treiber eigentlich "nur" ein Verzeichnis bereitstellen, in dem dann verschiedene treiberspezifische Dateien auftauchen, ähnlich wie bei /sys (oder teils noch /proc) unter Linux. Mit dem Unterschied, dass es bei dir keine allgemeine Wurzel gibt, sondern jedes Gerät eine eigene Wurzel hat. Statt vom VFS die Struktur vorzugeben, würde ich eher den Treibern die Möglichkeit geben, Dateien (und Ordner) in dieser Ordnerstruktur anzulegen und die Zugriffe auf diese Dateien als einzelne Callbacks zu definieren. Sonst hast du in jedem Treiber eine einzelne Funktion mit einem großen switch/case drin, was unhandlich ist. Mache den Kernel lieber etwas komplizierter und die Treiber einfacher.

Welche Dateien es gibt und wie diese benutzt werden, sollte per Konvention festgelegt werden; eine Info-Datei mit den wichtigsten Informationen ("Driver: ata", "Interface-Version: 1", "Device: hdd0", "Device: hdd1") ist auch sehr praktisch. Versionierte Schnittstellen sind anfangs vielleicht übertrieben, aber sicherlich auch keine schlechte Idee.

Vielleicht hilft es. :-)

Gruß,
Svenska

oern

  • Beiträge: 44
    • Profil anzeigen
Gespeichert
« Antwort #2 am: 14. May 2013, 18:41 »
Hallo,

vielen Dank erstmal für die Antwort!  :-)

Der Kernel ist ein modularer Monolith,
Werden Treiber im Kernel- oder im Userspace ausgeführt? :-)
Ich dachte, das einzige Kriterium für einen monolithischen Kernel sei, dass Treiber im Kernelmodus laufen? Bei mir jedenfalls schon.

Den grundlegenden Teil des VFS bilden die Treiber (Driver). Ein Treiber wird vom System anhand eines menschenlesbaren Namens identifiziert und kann Fähigkeiten (Capabilities) bereitstellen.
Der Begriff "Fähigkeit" passt nicht, weil du hier Geräteklassen identifizierst. Fähigkeiten wären eher "kann gelesen werden", "kann geschrieben werden" oder "kann Audio ausgeben", "kann Bilder einlesen".
OK, ich suchte nur nach irgendeinem Wort, das in etwa beschreibt was ich meine. Ich sehe jetzt auch ein, dass Geräteklassen ein besserer Begriff ist.

dass z.B. ein Treiber für die serielle Schnittstelle ja sowohl HID als auch Video-Gerät ist und man so nicht zwei Treiber zu registrieren muss
Eine serielle Schnittstelle ist eine serielle Schnittstelle, nicht Eingabegerät und nicht Kamera. Es ist auch nicht zielführend, mehrere Treiber an eine serielle Schnittstelle zu binden, weil da nur ein Gerät dranhängen kann. (Wenn mehrere Geräte dranhängen, kann man z.B. einen Multiplex-Treiber dazwischen schieben, der von der Beschaltung abhängt. Beispielsweise serielle IR-Sender, die eine Rx-Leitung anbieten, weil der Pin noch frei war: Der Multiplexer bindet dann an die serielle Schnittstelle und stellt eine "serielle Schnittstelle" (nur lesen) und eine "IR-Sendediode" (nur schreibend) zur Verfügung.)
Heißt das, die serielle Schnittstelle wäre eher der Bus-Klasse zuzuordnen?

Außerdem hat er eine Liste von Geräten, die von diesem Treiber verwaltet werden.
Ich sehe den Treiber als notwendiges Übel, um mit einem Gerät zu kommunizieren. Dann ist es sinnvoller, die Liste der Treiber zu haben, die für die verschiedenen Geräte zuständig sind.
Könnte man natürlich auch machen, ich dachte, dass das einfacher ist, um z.B. Geräte zu erkennen (da dafür der Treiber zuständig ist) oder beim Entfernen des Treibers alle Geräte, die über ihn verwaltet werden, mit zu entfernen. Kommt meiner Meinung nach mehr oder weniger aufs selbe hinaus.

Ein Gerät wird vom System anhand zweier ID-Nummern identifiziert. Diese müssen nicht unbedingt benutzt werden (0 ist default), und ich denke, dass zwei ausreichen. Bei einem ATA-Treiber kann z.B. die erste für die Platte und die zweite für die Partition stehen, bei Grafikkarten eins für die Karte und eins für den Bildschirm usw.
Was machst du mit Subpartitionen (vgl. Minix, BSD, Solaris)? Bei Grafikkarten gibt es ebenfalls mehr Ebenen: Grafikkarte, Framebuffer, CRTC, Encoder, Output. Mit "nur zwei" schränkst du dich da sehr unnötig ein.
Dass das nicht genug ist habe ich schon gewusst, lieber wäre mir ein Array bzw. eine Liste. Jedenfalls will ich die Geräte eigentlich nicht als Dateien im Baum des Treibers verwalten (oder was auch immer ich stattdessen verwenden sollte).

Vollständige Pfade beginnen mit dem Namen des Treibers.
Woher weiß ich, welcher Treiber für ein bestimmtes Gerät zuständig ist?
Das genaue Gegenteil (abgesehen vom Unixkonzept) wären meiner Meinung nach die Laufwerksbuchstaben in Windows. Dort hat der Anwender mehr oder weniger keine Kontrolle darüber, welcher Buchstabe letztlendlich für welches Gerät steht. Ich halte es für besser, wenn der Anwender explizit angibt, was er haben will, was auch den Namen des Treibers mit einschließt. Einen "eindimensionalen" Dateibaum, d.h. es gibt nur eine Wurzel, und Mounten möchte ich eigentlich nicht haben.

An dem ersten Beispiel sieht man auch, dass Dateisysteme nicht über den Dateisystemtreiber, sondern über den Massenspeichertreiber angesprochen werden, der den zu benutzenden Treiber ermittelt (und natürlich zwischenspeichern darf).
Das macht das Layering kaputt:
- Woher weiß der Massenspeichertreiber, welche Dateisystemtreiber es gibt und welche er benutzen darf?
- Warum sollte ein Anwendungsprogramm mit der Festplatte reden wollen, wenn es doch nur Dateien aus dem Dateisystem verarbeiten will?
- Was machst du mit virtuellen Dateisystemen, für die es keine Massenspeicher gibt ("Netzwerkumgebung" als Beispiel)?
Also das Herausfinden des FS-Treibers wäre glaube ich kein großes Problem, da holt sich der Storage-Treiber "einfach" beim ersten Zugriff eine Liste der Treiber der Dateisystemklasse und geht die durch. 2. und 3. sind in der Tat Probleme. Umgekehrt ist die Frage wie das Layering stattfinden soll. Die Pfad-Pipes von tyndur finde ich ehrlich gesagt nicht besonders schön (auch wenn sie ja ihren Zweck erfüllen), denn dann müsste ja der FS-Treiber wissen, von wo er lesen soll.

Die einfachste, aber unschönste Variante wäre wahrscheinlich, für alle Geräteklassen eigene Callbacks und Interfaces zu benutzen. Das wollte ich mit diesem VFS aber ja möglichst vermeiden. Aber die Treiberschnittstelle ist (meiner Meinung nach) eh der zentrale Teil eines Betriebssystems und soll schön werden, daher lohnt es sich auch, mehr Arbeit hineinzustecken.

Gruß,
oern

Svenska

  • Beiträge: 1 790
    • Profil anzeigen
Gespeichert
« Antwort #3 am: 14. May 2013, 23:33 »
Hallo,

Heißt das, die serielle Schnittstelle wäre eher der Bus-Klasse zuzuordnen?
Nein, eine serielle Schnittstelle ist der Klasse "serielle Schnittstelle" zuzuordnen und nichts anderem. Ein Großteil der Dinge, die man damit macht, sind sehr speziell für serielle Schnittstellen und haben keine Entsprechung in anderen Bussystemen (Baudrate, Datenformat, Handshaking, ...). Ähnliches gilt für andere Bussysteme auch.

Ich habe das Gefühl, dass du die Abstraktion "character-device"/"block-device" nicht magst und stattdessen lieber Klassen wie "video-device", "hid-device" und so weiter einführen willst. Der Weisheit letzter Schluss dürfte das nicht sein, weil du am Ende die Unterschiede in den Geräten trotzdem irgendwo abbilden können musst. Wenn du irgendwo einen ioctl()-Ersatz mit hardwarespezifischen Funktionen bereitstellen musst, kannst du dir die Geräteklassen auch schenken.

Die andere Frage ist, wer einen Treiber eigentlich benutzen möchte. Wenn an der seriellen Schnittstelle eine Maus hängt, möchtest du sie als manuell konfigurierten Bus mit einem Eingabegerät dran betrachten. Ist daran aber ein Mikrocontroller oder ein Terminal(-emulator) angeschlossen, ist diese Sichtweise unpraktisch. Du kannst das darum nicht über einen Kamm scheren.

Wenn du mal einen BSD-Kernel startest, siehst du ganz viele spezielle Bussysteme, die wie ein Baum verschaltet sind. Vereinfacht findest du da z.B. so eine Struktur:
Wurzel
- CPU
- ISA-Bus
--- PS/2 Keyboard-Controller (stellt PS/2-Bus bereit)
----- PS/2 Tastatur
----- PS/2 Maus
--- COM, LPT, ...
- PCI-Bus
--- IDE-Controller (stellt ATA-Bus bereit)
----- ATA-Festplatte
----- ATAPI-Laufwerk
--- Netzwerkkarte (stellt PHY-Bus bereit)
----- PHY (da gibt's verschiedene)
--- Soundkarte (stellt Soundkarten-Bus bereit)
----- Audioausgang
----- Audioeingang
----- MIDI-Schnittstelle
----- Gameport
----- Modem


Jedenfalls will ich die Geräte eigentlich nicht als Dateien im Baum des Treibers verwalten (oder was auch immer ich stattdessen verwenden sollte).
Irgendwo möchtest du diese Baumstruktur haben, damit jeder Treiber seine privaten Dinge exportieren kann. Zusätzlich gibt es dann noch Konventionen, welche Zugriffselemente für einen bestimmten Gerätetyp (da kommen dann die Klassen wieder ins Spiel) erwartet werden können. Ob diese Elemente nun in einem Dateisystem auftauchen oder nicht, ist ein Detail - du möchtest sie aber in den Userspace exportieren können.

Vollständige Pfade beginnen mit dem Namen des Treibers.
Woher weiß ich, welcher Treiber für ein bestimmtes Gerät zuständig ist?
Ich halte es für besser, wenn der Anwender explizit angibt, was er haben will, was auch den Namen des Treibers mit einschließt.
Ich würde einen Treiber, wenn ich ihn schon explizit angeben muss, als "Treibername + Instanznummer" identifizieren.

Der "atabus"-Treiber muss ja prinzipiell nur zwei Geräte unterstützen (master+slave) und pro Kanal läuft dann eine Instanz dieses Treibers. Wenn also das DVD-Laufwerk auf dem zweiten Kanal den Treiber abstürzen lässt, ist die Festplatte am ersten Kanal noch da, außerdem werden die Treiber dann einfacher.

Wenn du das in einem Dateisystem implementierst (ähnlich wie /dev), dann könntest du einfach symbolische Links von allgemeinen Begriffen auf die konkrete Hardware setzen ("/dev/drive0 -> /dev/ata0/drive0/data") und hättest so das beste aus beiden Welten. Deinem Dateimanager ist es relativ egal, welcher Treiber nun für dein "Laufwerk C:" zuständig ist (dem Benutzer erst recht, der will seine Daten) - einem Firmware-Update-Tool für den SATA-Controller nicht.

Einen "eindimensionalen" Dateibaum, d.h. es gibt nur eine Wurzel, und Mounten möchte ich eigentlich nicht haben.
Für die gesamte Treiberverwaltung wäre eine zentrale Wurzel schon angemessen. Es muss ja trotzdem nicht die systemweit einzige Wurzel sein. Andererseits mounten sowohl Windows, Linux als auch MacOS die Dateisysteme in einen großen Baum (und Windows erzeugt dann Aliase auf die Laufwerksbuchstaben). Wenn du die Wurzel nicht in den Userspace exportierst, dann sehen die Unterknoten für den Benutzer disjunkt aus, du kannst es aber trotzdem im Kernel noch einfach traversieren, wenn das mal nötig sein sollte.

Umgekehrt ist die Frage wie das Layering stattfinden soll.
Du könntest die Hotplug-Funktionalität missbrauchen. Wenn du ein USB-Gerät einsteckst, taucht am USB-Bus ein neues Gerät auf. Irgendwo (bevorzugt im Userspace) läuft dann ein Programm, welches auf solche Ereignisse reagiert und eine Instanz des passenden Treibers für dieses spezielle Gerät lädt, also alle Gerätetreiber im System kennt. Das kann man auch für Blockdevices machen, wobei du dann pro Blockdevice eine Instanz eines Dateisystems hast, die fix mit diesem Blockdevice verbunden ist. Statt "disk0" greifst du dann eben auf "vfat0" zu und verletzt so die Ebenen nicht.

Die einfachste, aber unschönste Variante wäre wahrscheinlich, für alle Geräteklassen eigene Callbacks und Interfaces zu benutzen.
Praktisch hast entweder die Geräteklasse "Gerät" mit dem Universalcallback ioctl() (eine Maintenance- und Kompatiblitätshölle) oder du hast viele Geräteklassen mit jeweils eigener API.

Das Ergebnis ist so ziemlich das gleiche, weil verschiedene Geräte einfach verschieden sind. Du kannst dir ja mal systemd anschauen und das, was Lennart Poettering dazu schreibt (warum er dbus im Linux-Kernel haben möchte, was er gegen OSS hat und diverse andere Rants). Begründen tut er es, auch wenn ich manche Sachen anders sehe.

Gruß,
Svenska

 

Einloggen