Blockorientierte Gerätetreiber


... [ Seminar Linux und Apache ] ... [ Thema Gerätetreiber unter Linux 2.4 ] ... [ Netzwerktreiber ] ...

Übersicht: Blockorientierte Gerätetreiber


Definition

Wie char devices werden auch die block devices über Knoten im Dateisystem angesprochen. Ein block device kann allerdings ein Dateisystem hosten, wie z.B. eine Platte.
Die Positionierung ist hier möglich.
In den meisten Unixsystemen kann auf block devices nur als Vielfaches eines Blockes zugegriffen werden. Ein Block umfaßt in der Regel ein Kilobyte Daten oder eine andere Potenz von 2, meist 4 oder 8 KByte.
Werden kleinere Datenmengen angefordert, wird intern trotzdem das Lesen eines ganzen Blockes angestossen. Die Applikation bekommt aber nur die angeforderte Datenmenge übergeben.
Linux allerdings erlaubt Applikationen das Lesen und Schreiben wie bei einem character device. Der Transfer beliebig vieler Bytes zu einem bestimmten Zeitpunkt ist zulässig.
Beim Lesen und Schreiben wird überprüft, ob sich der betreffende Datenblock im Hauptspeicher befindet. Wenn nicht, wird der entsprechende Treiber veranlaßt, die Daten vom Hintergrundspeicher zu holen.
Sind beim erneuten (konsekutiven) Zugriff die Daten noch im bereits vorher vom Treiber gelesenen Block vorhanden, braucht überhaupt kein Gerätezugriff (Hardwarezugriff) durchgeführt werden, sondern die Daten können direkt aus dem Buffer der Applikation übergeben werden.
Der Nachteil ist, dass Hintergrundspeicher und Buffercache nicht konsistent zueinander sind. Wenn der Strom ausfällt, sind nicht zwangsläufig alle geänderten Daten auf der Platte.
Im Endeffekt unterscheiden sich character und block devices aber nur durch die Art und Weise, wie die Daten intern durch den Kernel verwaltet werden - und somit im Interface zwischen Kernel und Treiber.
Für den Benutzer ist der Unterschied nicht ersichtlich.
Zusätzlich zu dem Interface, das auch character devices anbieten, gibt es ein blockorientiertes, das für den User oder Applikationen, die über /dev ein Gerät öffnen, unsichtbar bleibt. Es ist aber unerläßlich, um ein Dateisystem mounten zu können.
Bei blockorientierten Geräten handelt es sich vorwiegend um Speichermedien, wie Festplatten oder Bänder. Da auf diese Medien im Regelfall eine Reihe unterschiedlicher Dateien abgelegt werden, muß eine Verwaltungsinformation mit abgelegt werden, die das Wiederfinden einzelner Dateien ermöglicht. Diese Verwaltungsinformation wird auch als "Filesystem" bezeichnet. Standardfilesysteme (z.B. ext2 unter Linux) ermöglichen die hierarchische Ablage von Dateien (in Form von Directories und einfachen Dateien) und bieten darüberhinaus auch Zugriffsschutz.


Programmierung

Das Interface für Blockgerätetreiber unterscheidet sich von dem für Charactertreiber. Das liegt zum einen daran, dass es sozusagen historisch gewachsen ist, zum anderen an dem Bedürfnis nach Geschwindigkeit. Eine langsame Platte ist wesentlich nerviger als ein langsamer Drucker.

Auch Blockgeräte werden über Major-Nummern identifiziert, allerdings unabhängig von denen der Charactergeräte. Es kann durchaus ein Blockgerät und ein Charactergerät geben, die beide die 32 als Major-Nummer benutzen.

Die Funktionen zum Registrieren sind denen der char devices ähnlich:
    #include <linux/fs.h>
    int register_blkdev(unsigned int major,const char *name,struct block_device_operations *bdops);
    int unregister_blkdev(unsigned int major,const char *name);    
Anstelle des Zeigers auf eine file_operations Struktur wird hier jetzt eine Struktur namens block_device_operations benutzt (seit Kernel 2.3.38):
    struct block_device_operations {
    int (*open)(struct inode *inode,struct file *filp);
    int (*release)(struct inode *inode,struct file *filp);
    int (*ioctl)(struct inode *inode,struct file *filp,unsigned command,unsigned long argument);
    int (*check_media_change)(kdev_t dev);
    int (*revalidate)(kdev_t dev);
    };    
open, release und ioctl funkionieren genauso wie bei Charactergeräten. check_media_change prüft, ob sich seit dem letzten Zugriff das Device verändert hat, revalidate reinitialisiert den Status, wenn eine Platte sinch geändert hat. read und write gibt es nicht, weil mit Ausnahme der Raw I/O-Devices keine I/O direkt auf das Gerät ausgeführt wird, sondern über einen Puffer läuft.
Ein Treiber muß also in der Lage sein, Block-I/O zu verarbeiten. Dazu gibt es die Funktion request, die Lese- und Schreibezugriffe verarbeiten kann.
Zu Registrierungszwecken muß der Kernel wissen, wo er request findet, da es nicht in der block_device_operations Struktur enthalten ist. Die Funktion gehört zur Queue der schwebenden I/O-Operationen. Eine solche Queue existiert für jede Major-Nummer, die mit blk_init_queue durch den Treiber initialisiert werden muß.
    #include <linux/blkdev.h>
    blk_init_queue(request_queue_t *queue,request_fn_proc *request);
    blk_cleanup_queue(request_queue_t *queue); 
init erzeugt diese Queue und assoziiert die request-Funktion damit. Das Makro BLK_DEFAULT_QUEUE(major) im ersten Parameter zeigt an, dass die Default-Queue verwendet werden soll.
Das Makro sucht im blk_dev, einem globalen Array, das durch die Major-Nummer indiziert wird.
Daneben existieren weitere globale Arrays mit Treiberinformationen, die in drivers/block/ll_rw_block.c deklariert und erläutert werden.

Alle Blocktreiber sollten den Header <linux/blk.h> includieren. Er definiert viel allgemeinen Code, der in Blocktreibern verwendet wird und stellt Funktionen zum Umgang mit der request-Queue zur Verfügung.
Wie die request-Queue funktioniert, möchte ich aus Zeitgründen nicht weiter erläutern, da das Thema relativ kompliziert ist


Partitionierung

Die meisten Blockgeräte werden nicht 'am Stück' verwendet, sondern partitioniert. Mittels fdisk erstellt man unabhängige 'Pseudogeräte'.
Hier trifft man aber auf ein Problem, weil fdisk Namen erzeugt, die nicht im Fileystem hinterlegt sind und somit vom Treiber die Partitionen nicht zugeordnet werden können.
Für die Partitionierung benötigt man mehrere Minor-Nummern. Die erste wird dem gesamten Device zugewiesen, die anderen den Partitionen.
Wie ein Device partitioniert ist, kann es aus dem Partition Table erfahren.

Um Partitionen zu unterstützen, muß der Treiber <linux/genhd.h> inkludieren und die Struktur gendisk (für 'generic hard disk') deklarieren. Diese Struktur beschreibt das Layout der Disk(s). Der Kernel unterhält eine globale Liste dieser Strukturen, die eingesehen werden kann.
    struct gendisk my_gendisk ={
      major:0,/*Major number assigned later */
      major_name:"pd",/*Name of the major device */
      minor_shift:SPULL_SHIFT,/*Shift to get device number */
      max_p:1 <<SPULL_SHIFT,/*Number of partitions */
      fops:&my_bdops,/*Block dev operations */
      /*everything else is dynamic */
    };
Dazu muß man wissen, dass die signifikanten ersten vier Bits der Minor-Nummer die Unitnummer und die letzten signifikanten vier Bits die Partition repräsentieren. Durch shiften kann man auf sie zugreifen.
Da die Struktur nicht automatisch in die Liste aufgenommen wird, muß man das per Hand im Treiber machen:
    my_gendisk.next =gendisk_head;
    gendisk_head =&my_gendisk;
Die Partitionstabelle wird durch
register_disk(struct gendisk *gd,int drive,unsigned minors,struct block_device_operations *ops,long size);
gelesen.
Ein unregister_disk gibt es nicht!


Raw I/O-Devices

Diese Geräte sind neu im Kernel 2.4. Sie erlauben Applikationen wie DBMS den direkten Zugriff auf Platten, ohne die Kernel-Caches zu benutzen.
Ein Raw-Device kann auch benutzt werden, wenn Daten in kritischen Situationen sofort auf die Platte geschrieben werden sollen, um bei einem Systemfehler den Datenverlust möglichst gering zu halten.
In früheren Versionen war der Support noch nicht brauchbar, weil jedes Blockgerät uch ein Raw-Device benötigte. Jetzt wird ein Pool von Geräteknoten benutzt, die willkürlich mit den Blockgeräten verknüpft werden.


Logical Volume Manager

Das LVM-Subsystem wurde in den Mainstream-Kernel integriert. Es erlaubt die flexiblere Handhabung des Dateisystems, stark angelehnt an die Implementation in HP/UX in Bezug auf die Struktur und die zugehörigen Hilfsprogramme.


RAID-Geräte

Fast das ganze RAID-Subsystem wurde überarbeitet. Die Performanz wurde sowohl auf SMP als auch auf Einprozessor-Systemen verbessert.
Der Code ist robuster geworden und bietet jetzt die Möglichkeit, rekursive RAID-Arrays aufzubauen. Das Mounten eines sets für eine root disk ohne Ramdisk-Image ist möglich.
Das ist vor allem wichtig für Enterprise-User.


Weitere Änderungen im neuen Kernel

Im neuen Kernel wurden alle Blockgerätetreiber neu geschrieben, weil die API aufgeräumt und komplett von der file-API auf Kernel-Level getrennt wurde.
Die Änderungen waren nicht gravierend, aber Module müssen eventuell aktualisiert werden.

Der Support für IDE-Busse wurde verbessert. Die Anzahl der unterstützten IDE-Controller wurde von 4 auf 10 erhöht. Da die meisten Motherboards nur maximal 2 haben, wird das den normalen Desktop-User nicht betreffen.
Der IDE-Treiber wurde geändert und verbessert so den Support für PCI- und PnPIDE-Controller, IDE-Floppies, DVDs und CD-Wechsler.
Außerdem wurden Treiber-Updates eingebaut, die die Bugs in manchen IDE-Chipsätzen beheben sollen und die Features anderer besser unterstützen sollen, wie ATA66.


... [ Seminar Linux und Apache] ... [ Thema Gerätetreiber unter Linux 2.4] ... [ nach oben ] ... [ Netzwerktreiber ] ...