Die ioctl-Methode

Wie Zeichen-Geräte können auch Block-Geräte mit dem Systemaufruf ioctl gesteuert werden. Der einzige Unterschied zwischen Block- und Zeichen-Geräten ist, daß es eine Reihe von gemeinsamen ioctl-Befehlen in Block-Geräten gibt, deren Unterstützung von den meisten Treibern erwartet wird.

Block-Treiber bedienen üblicherweise die folgenden in <linux/fs.h> definierten Befehle:

BLKGETSIZE

Gibt die Größe des aktuellen Gerätes in Sektoren zurück. Der Wert von arg ist ein Zeiger auf eine long-Variable und sollte verwendet werden, um die Größe in den User-Space zu kopieren. Dieser ioctl-Befehl wird beispielsweise verwendet, um an mkfs die Größe des zu erzeugenden Dateisystems zu übermitteln.

BLKFLSBUF

Dieser Befehl steht wörtlich für “flush buffers”. Die Implementation dieses Befehls ist für alle Geräte gleich und wird später beim Beispiel-Code für die gesamte ioctl-Methode gezeigt.

BLKRRPART

Partitionstabelle neu einlesen. Dieser Befehl ist nur bei partitionierbaren Geräten, die später in “the Section called Partitionierbare Geräte” eingeführt werden, sinnvoll.

BLKRAGET, BLKRASET

Wird verwendet, um den aktuellen Vorauslesewert des Gerätes zu holen oder zu setzen. Für GET sollte dieser Wert mit dem in arg an ioctl übergebenen Zeiger als long im User-Space gespeichert werden. Für SET wird der neue Wert als Argument übergeben.

BLKFRAGET, BLKFRASET

Holt oder setzt den Vorauslesewert auf Dateisystem-Ebene für dieses Gerät (also den Wert, der in max_readahead gespeichert ist).

BLKROSET, BLKROGET

Diese Befehle werden verwendet, um den Nur-Lesen-Schalter des Geräts zu ändern bzw. abzufragen.

BLKSECTGET, BLKSECTSET

Diese Befehle holen oder setzen die maximale Anzahl von Sektoren pro Anfrage (die in max_sectors gespeichert ist).

BLKSSZGET

Gibt die Sektorengröße dieses Block-Geräts in der Integer-Variablen zurück, auf die ein Zeiger übergeben wird. Die Größe stammt direkt aus dem Array hardsect_size.

BLKPG

Der Befehl BLKPG erlaubt es User-Mode-Programmen, Partitionen hinzuzufügen und zu entfernen. Er wird von blk_ioctl (siehe unten) implementiert; keiner der Treiber im Haupt-Kernel hat eine eigene Implementation.

BLKELVGET, BLKELVSET

Diese Befehle erlauben eine gewisse Steuerung des Fahrstuhl-Algorithmus zur Sortierung der Anfragen. Wie BLKPG wird auch dieser Befehl von keinem Treiber direkt implementiert.

HDIO_GETGEO

Wird in <linux/hdreg.h> definiert und benutzt, um die Festplattengeometrie zu ermitteln. Die Geometrie sollte in einer struct hd_geometry-Struktur in den User-Space kopiert werden. Diese Struktur ist ebenfalls in hdreg.h deklariert. sbull zeigt, wie dieser Befehl allgemein implementiert wird.

Der Befehl HDIO_GETGEO ist der am häufigsten verwendete aus einer Reihe von HDIO_-Befehlen, die alle in <linux/hdreg.h> definiert sind. Interessierte Leser können in ide.c und hd.c mehr Informationen über diese Befehle finden.

Fast alle diese ioctl-Befehle werden für alle Block-Geräte auf die gleiche Art und Weise implementiert. Im 2.4-Kernel gibt es eine Funktion namens blk_ioctl, die die gemeinsamen Befehle implementiert. Sie ist in <linux/blkpg.h> implementiert. Oftmals muß ein Treiber nur BLKGETSIZE und HDIO_GETGEO selbst implementieren und kann die anderen Befehle problemlos an blk_ioctl zur Verarbeitung übergeben.

Das Gerät sbull unterstützt nur die obigen allgemeinen Befehle, weil die Implementation der gerätespezifischen Befehle genauso wie bei bei den Zeichen-Geräten wäre. Die ioctl-Implementation für sbull sieht wie folgt aus:


 
int sbull_ioctl (struct inode *inode, struct file *filp,
                 unsigned int cmd, unsigned long arg)
{
    int err;
    long size;
    struct hd_geometry geo;

    PDEBUG("ioctl 0x%x 0x%lx\n", cmd, arg);
    switch(cmd) {

      case BLKGETSIZE:
        /* Gibt die Geraetegroeße in Sektoren zurueck */
        if (!arg) return -EINVAL; /* NULL-Zeiger: ungueltig */
        err = ! access_ok (VERIFY_WRITE, arg, sizeof(long));
        if (err) return -EFAULT;
        size = blksize*sbull_sizes[MINOR(inode->i_rdev)]
                / sbull_hardsects[MINOR(inode->i_rdev)];
        if (copy_to_user((long *) arg, &size, sizeof (long)))
            return -EFAULT;
        return 0;

      case BLKRRPART: /* Partitionstabelle neu einlesen: koennen wir nicht */
        return -ENOTTY;

      case HDIO_GETGEO:
        /*
         * Geometrie holen: Weil wir ein virtuelles Geraet
         * implementieren, muessen wir uns plausible Werte ueberlegen.
         * Wir geben 16 Sektoren und vier Koepfe an und berechnen
         * die entsprechende Anzahl von Zylindern. Die Daten sollen in
         * Sektor vier starten.
         */
        err = ! access_ok(VERIFY_WRITE, arg, sizeof(geo));
        if (err) return -EFAULT;
        size = sbull_size * blksize / sbull_hardsect;
        geo.cylinders = (size & ˜0x3f) >> 6;
        geo.heads = 4;
        geo.sectors = 16;
        geo.start = 4;
        if (copy_to_user((void *) arg, &geo, sizeof(geo)))
            return -EFAULT;
        return 0;

      default:
        /*
         * ioctls, die wir nicht verstehen, lassen wir von der
         * Block-Schicht abarbeiten.
         */
        return blk_ioctl(inode->i_rdev, cmd, arg);
    }

    return -ENOTTY; /* unbekannter Befehl */
}

Die PDEBUG-Anweisung am Anfang der Funktion haben wir im Code gelassen, so daß Sie beim Kompilieren des Moduls das Debuggen einschalten können, um zu sehen, welche ioctl-Befehle am Gerät aufgerufen werden.