7.3. Device-Filesystem

Über Gerätedatei und Major-Nummer findet die Applikation zum Treiber. Die Applikation greift auf die Gerätedatei zu, die mit einer Major-Nummer assoziiert ist. Über diese Major-Nummer hat sich der Treiber beim Kernel angemeldet. Zwischen Treiber und Applikation werden also gleich zwei Identifikatoren verwendet, nämlich die Gerätedatei und die Major-Nummer, wo eigentlich ein Merkmal ausreichen würde. Das führt zu der Überlegung, die Major-Nummer einzusparen; erst recht, da sie mit 8 Bit Breite in der Vergangenheit ein schmerzlich limitierender Faktor beim Zugriff auf Geräte war.

Abbildung 7-8. Erstellung der Gerätedatei durch den Anwender oder durch den Treiber

Aus dieser Überlegung heraus wurde das Device-Filesystem (devfs) geschaffen. Beim Device-Filesystem werden die Gerätedateien nicht mehr durch ein Programm auf Applikationsebene (mknod) erzeugt, sondern direkt durch den Treiber (siehe Abbildung Erstellung der Gerätedatei durch den Anwender oder durch den Treiber). Die Beziehung zwischen Treiber und Gerätedatei wäre automatisch – ohne Verwendung einer Major-Nummer – hergestellt. Außerdem entfiele das Anlegen der Gerätedatei durch eine Applikation. Das spart Ressourcen, da nur noch solche Gerätedateien erzeugt würden, deren Treiber auch im System geladen sind.

Das Device-Filesystem hat allerdings auch Nachteile. Da es mehr Code benötigt als bei klassischer Implementierung der Major- und Minor-Nummer, leidet die Skalierbarkeit. Darüber hinaus sind Attribute der Gerätedateien – zum Beispiel die Zugriffsrechte – durchaus systemspezifisch; sprich, ein Treiber kann die Zugriffsrechte auf eine bestimmte Datei nicht kennen. Es kommt noch hinzu, dass diese Zugriffsrechte auch wechseln können: darf ein Sounddevice (/dev/dsp) von jedem User verwendet werden, könnte ein remote eingeloggter User das Mikrofon einschalten und alles mithören, was die Person, die vor dem Rechner sitzt, spricht oder telefoniert. Auf der anderen Seite soll bestimmten Usern Zugriff auf das Sounddevice ermöglicht werden, damit diese es nutzen. Richtig wäre wahrscheinlich, demjenigen, der gerade physikalisch vor dem Rechner sitzt, die Zugriffsrechte zu erteilen. Derartige Strategien können einem Treiber jedoch nicht bekannt sein.

Insbesondere wegen der Skalierbarkeit ist das Device-Filesystem bisher immer nur als Option angesehen worden – als eine Komponente, die neben der traditionellen Major-Nummer angeboten wird. Es kommt noch hinzu:

  1. Die Implementierung des Device-Filesystems wird innerhalb der Entwicklergemeinde als nicht besonders geglückt angesehen.

  2. Angeblich existieren im Code noch Race Conditions, die zu Datenverlusten führen können.

  3. Die Gerätedateien haben im Device-Filesystem neue Namen bekommen.

  4. Nicht alle Treiber unterstützen das Device-Filesystem.

  5. In manchen Fällen wird die Gerätedatei benötigt, um den zugehörigen Treiber auszuwählen. Wenn die Gerätedatei allerdings durch den Treiber erst erstellt wird, funktioniert das Prinzip hier nicht.

  6. Die Methode, um Zugriffsrechte zu ändern bzw. geänderte Zugriffsrechte zu protokollieren, wird innerhalb der Entwicklergemeinde ebenfalls kritisiert.

Aus diesen Gründen hat das Device-Filesystem keine weite Verbreitung in der Linux-Welt gefunden. Mehr noch, im Kernel 2.6 ist es bereits als »deprecated«, als nicht mehr unterstützt gekennzeichnet. Nichtsdestotrotz ist die aus Kernel 2.4 bekannte Implementierung in Kernel 2.6 überarbeitet worden. Dabei wurden sowohl Schnittstellen geändert als auch Funktionalitäten weggeworfen. Eine Entkopplung der Major- und Minor-Nummer von der Gerätedatei findet beispielsweise jetzt nicht mehr statt.

Ein Treiber ist leicht zu modifizieren, so dass er das Device-Filesystem unterstützt. Zunächst wird die Header-Datei <linux/devfs_fs_kernel.h> inkludiert. In der Treiberinitialisierungsfunktion (module_init) meldet sich der Treiber in gewohnter Weise beim IO-Subsystem an (register_chrdev bzw. register_blkdev). Bei Erfolg meldet er sich anschließend beim Device-Filesystem an. Dazu ruft er im Fall eines zeichenorientierten Gerätes die Funktion devfs_mk_cdev, im Fall eines blockorientierten Gerätes die Funktion devfs_mk_bdev auf (siehe Beispiel Anmeldung beim Device-Filesystem).

Beispiel 7-11. Anmeldung beim Device-Filesystem

#include <linux/devfs_fs_kernel.h>
...
static int __init treiber_init(void)
{
    ...
    if(register_chrdev(240, "Treibername", &fops) == 0) {
        if( devfs_mk_cdev( MKDEV(240,0), S_IFCHR | S_IRUGO | S_IWUGO, "speaker%d", 0 )) ) {
            printk( KERN_ERR "Integration ins Device-Filesystem " "fehlgeschlagen.\n");
    }
    ...
}

Der erste Parameter der Funktion ist vom Typ struct dev_t. Er besteht aus einer Kombination von Major- und Minornumber. Über das Makro MKDEV wird mit den Parametern Major- und Minornumber ein entsprechender Wert gebildet. Der zweite Parameter der Funktion devfs_mk_cdev bzw. devfs_mk_bdev spezifiziert die Zugriffsrechte. Hier ist darauf zu achten, dass bei einem zeichenorientierten Gerät S_IFCHR und im Fall eines blockorientierten Gerätes S_IFBLK mitgesetzt wird, damit auch wirklich die passenden Gerätedateien angelegt werden können. Das Erstellen der Einträge im Device-Filesystem ist nur erfolgreich, wenn dieses Bit für den Dateityp gesetzt ist. Ansonsten geben die beiden Funktionen einen negativen Fehlerwert zurück. Der dritte Parameter (hier "speaker%d") ist ein Formatstring, wie er bei der Funktion printf üblich ist. Die dazu notwendigen Parameter folgen direkt.

Vor dem Entladen eines Treibers müssen die Einträge im Device-Filesystem wieder rückgängig gemacht werden. Kein Problem: devfs_remove mit dem Gerätedateinamen als Parameter aufrufen und das war's. Der Gerätedateinamen wird übrigens – wie beim Erzeugen – wieder als Formatstring mit zugehörigen Parametern übergeben:

static void __exit treiber_exit(void)
{
    ...
    devfs_remove( "speaker%d",0 );
    unregister_chrdev(240,"Treibername");
}

Das Anlegen von Gerätedateien in einem Unterverzeichnis ist ebenfalls sehr einfach. Vor den Dateinamen wird einfach der zugehörige Pfad gesetzt:

    devfs_mk_cdev( MKDEV(240,0), S_IFCHR | S_IRUGO | S_IWUGO, "ABC/DEF/speaker%d", 0 ) 

In obigem Beispiel legt das Device-Filesystem zunächst das Verzeichnis »ABC«, darunter das Verzeichnis »DEF« und anschließend die Gerätedatei »speaker0« an.

Das Aufräumen – sprich Löschen – von Gerätedateien in Unterverzeichnissen ist jedoch etwas komplizierter. Jeder Ordner muss nämlich einzeln gelöscht werden:

    devfs_remove( "ABC/DEF/speaker0" );
    devfs_remove( "ABC/DEF" );
    devfs_remove( "ABC" );

Vereinfacht wird die Situation, falls alle Gerätedateien mitsamt Verzeichnis gelöscht werden sollen. Hier reicht es aus, den Ordner zu löschen. Alle darunter abgelegten Dateien werden mitgelöscht.

Für das Erzeugen eines Unterverzeichnisses steht zudem die Funktion devfs_mk_dir zur Verfügung. Diese Funktion ist jedoch optional. Ihre Verwendung ist nicht notwendig, falls die Verzeichnisnamen als Teil des Geräte-Dateinamens angegeben wurden. Des Weiteren gibt es noch die Funktion devfs_mk_symlink, mit deren Hilfe symbolische Links (Symlinks) zu Gerätedateien erstellt werden können. Mit der Zeile

    devfs_mk_symlink( "ABC/TonAusgabe", "../speaker0" );

wird ein Symlink im Verzeichnis »ABC« mit dem Namen »TonAusgabe« angelegt, der auf die Gerätedatei »speaker0« zeigt. Symlinks werden wie die Gerätedateien selbst durch Aufruf der Funktion devfs_remove wieder gelöscht. Allerdings darf das Aufräumen nur dann geschehen, wenn der Symlink auch wirklich angelegt wurde. Es ist daher auf korrekte Auswertung des Rückgabewertes der Funktion zu achten. Programmtechnisch einfach wird das Problem gelöst, indem das Erzeugen und Löschen über Pointer realisiert wird (siehe Beispiel Löschen von Symlinks im Device-Filesystem).

Beispiel 7-12. Löschen von Symlinks im Device-Filesystem

...
static char *ton_ausgabe_ptr = "ABC/TonAusgabe";
...
static int __init treiber_init(void)
{
        if( devfs_mk_symlink( ton_ausgabe_ptr, "../speaker0" ) ) {
            ton_ausgabe_ptr = NULL;
        }
...
static void __exit treiber_exit(void)
{
    ...
    if( ton_ausgabe_ptr )
        devfs_remove( ton_ausgabe_ptr );
...

Die Prototypen der Device-Filesystem-Funktionen sind im Übrigen so gestaltet, dass ein Treiber die Funktionen unabhängig von der Kernelkonfiguration in jedem Fall verwenden kann. Ist in der Kernelkonfiguration das Device-Filesystem nicht ausgewählt, werden die Funktionen durch den Wert »0« ersetzt, so dass im Beispiel der Fehlerfall nie eintritt (die Bedingung ist nie erfüllt).

Wer wie die Entwickler eingebetteter Systeme allerdings auf schlanken Code achtet, sollte die fraglichen Bereiche dennoch über die Definition #ifdef CONFIG_DEVFS_FS kapseln. Damit wird überhaupt kein Code erzeugt, falls die Option deaktiviert ist.

Beispiel 7-13. Device-Filesystem für ein eingebettetes System

...
#ifdef CONFIG_DEVFS_FS
    if(devfs_mk_cdev( MKDEV(240,0),S_IFCHR|S_IRUGO|S_IWUGO,speaker_ptr ))
        speaker_ptr = NULL;
    else if( devfs_mk_symlink( ton_ausgabe_ptr, "../speaker0" ) )
        ton_ausgabe_ptr = NULL;
#endif
...


Lizenz