Herausnehmbare Geräte

Als wir über Zeichen-Treiber gesprochen haben, haben wir die letzten beiden Datei-Operationen in der block_device_operations-Struktur ignoriert, weil sie nur für herausnehmbare Block-Geräte existieren. Jetzt ist es an der Zeit, sich damit zu beschäftigen. sbull ist eigentlich kein herausnehmbares Gerät, tut aber so und implementiert daher diese Methoden.

Die Operationen, über die wir hier reden, sind check_media_change und revalidate. Erstere wird verwendet, um herauszufinden, ob sich das Gerät seit dem letzten Zugriff geändert hat, letztere reinitialisiert den Zustand des Geräts nach einem Wechsel.

Bei sbull wird der Datenbereich des Gerätes eine halbe Minute, nachdem der Verwendungszähler auf 0 gefallen ist, freigegeben. Wenn das Gerät lange genug ausgehängt (oder geschlossen) ist, dann reicht das als Simulation eines Diskettenwechsels, und der nächste Zugriff auf das Gerät alloziert einen neuen Speicherbereich.

Diese “verzögerte Freigabe” wird mit einem Kernel-Timer implementiert.

check_media_change

Diese Funktion hat als einziges Argument ein kdev_t, das das Gerät identifiziert. Der Rückgabewert ist 1, wenn das Medium gewechselt wurde, und 0 sonst. Ein Block-Treiber, der keine herausnehmbaren Geräte unterstützt, muß diese Funktion nicht implementieren, wenn er fops->check_media_change auf NULL setzt.

Wenn es sich um ein Gerät mit herausnehmbaren Medien handelt, aber nicht festgestellt werden kann, ob das Medium gewechselt wurde, dann ist es immer eine sichere Sache, 1 zurückzugeben. Das ist das Verhalten der IDE-Treiber, wenn sie es mit herausnehmbaren Medien zu tun haben.

Die Implementation in sbull gibt 1 zurück, wenn das Gerät durch den Ablauf des Timers bereits aus dem Speicher entfernt worden ist, und 0, wenn die Daten noch gültig sind. Wenn das Debugging eingeschaltet ist, wird auch eine Nachricht in die Systemprotokolle ausgegeben; der Benutzer kann also feststellen, wann die Methode vom Kernel aufgerufen worden ist.


 
int sbull_check_change(kdev_t i_rdev)
{
    int minor = MINOR(i_rdev);
    Sbull_Dev *dev = sbull_devices + minor;

    PDEBUG("check_change for dev %i\n",minor);
    if (dev->data)
        return 0; /* noch gueltig */
    return 1; /* nicht mehr gueltig */
}


Revalidierung

Die Überprüfungsfunktion wird aufgerufen, wenn ein Medienwechsel festgestellt wurde. Außerdem wird sie auch von den diversen stat-Systemaufrufen der Kernel-Version 2.1 aufgerufen. Der Rückgabewert wird derzeit nicht benutzt. Um sicherzugehen, sollte 0 im Erfolgsfall und ein negativer Wert im Fehlerfall zurückgegeben werden.

Die von revalidate durchgeführte Operation ist gerätespezifisch, aber normalerweise aktualisiert die Funktion die interne Statusinformation anhand des neuen Mediums.

In sbull versucht revalidate einen neuen Datenbereich zu allozieren, wenn es noch keinen gibt.


 
int sbull_revalidate(kdev_t i_rdev)
{
    Sbull_Dev *dev = sbull_devices + MINOR(i_rdev);

    PDEBUG("revalidate for dev %i\n",MINOR(i_rdev));
    if (dev->data)
        return 0;
    dev->data = vmalloc(dev->size);
    if (!dev->data)
        return -ENOMEM;
    return 0;
}


Zusätzliche Vorsichtsmaßnahmen

Treiber für herausnehmbare Geräte sollten auch überprüfen, ob es einen Medienwechsel gegeben hat, wenn das Gerät geöffnet wird. Der Kernel stellt dafür eine Funktion bereit:


int check_disk_change(kdev_t dev);

Der Rückgabewert ist von Null verschieden, wenn ein Medienwechsel gefunden wurde. Der Kernel ruft check_disk_change automatisch beim mount, aber nicht beim open auf.

Manche Programme greifen aber direkt auf die Daten zu, ohne das Gerät vorher einzuhängen. Dazu gehören beispielsweise fsck, mcopy und fdisk. Wenn der Treiber Statusinformationen über herausnehmbare Geräte im Speicher hält, sollte er check_disk_change aufrufen, wenn das Gerät zum erstenmal geöffnet wird. Die Kernel-Funktion ruft die normalen Treiber-Methoden (check_media_change und revalidate) auf, so daß in open selbst nichts Besonderes implementiert werden muß.

Hier die Implementation von open in sbull, die sich auch um Medienwechsel kümmert:


 
int sbull_open (struct inode *inode, struct file *filp)
{
    Sbull_Dev *dev; /* Geraeteinformation */
    int num = MINOR(inode->i_rdev);

    if (num >= sbull_devs) return -ENODEV;
    dev = sbull_devices + num;

    spin_lock(&dev->lock);
    /* revalidieren beim ersten open, Fehler, wenn keine Daten da sind */
    if (!dev->usage) {
        check_disk_change(inode->i_rdev);
        if (!dev->data)
        {
            spin_unlock (&dev->lock);
            return -ENOMEM;
        }
    }
    dev->usage++;
    spin_unlock(&dev->lock);
    MOD_INC_USE_COUNT;
    return 0;          /* Erfolg */
}

Im Treiber muß für Medienwechsel nichts weiter implementiert werden. Die Daten sind ohnehin beschädigt, wenn ein Medium gewechselt wird, während noch ein Prozeß das Gerät geöffnet hat. Ein Treiber kann solche Datenbeschädigung nur dann verhindern, wenn anhand des Verwendungszählers das Herausnehmen der Medien kontrolliert werden kann. In diesem Fall können open und close die Sperre entsprechend einschalten bzw. freigeben.