Wir haben in diesem Kapitel bisher die Programmierschnittstelle der Version 2.4 des Linux-Kernels beschrieben. Leider hat sich diese Schnittstelle im Laufe der Kernel-Entwicklung deutlich verändert. Diese Änderungen sind für sich genommen alle Verbesserungen, bringen aber wieder einmal Schwierigkeiten für diejenigen mit, die Treiber schreiben wollen, die mit verschiedenen Kernel-Versionen kompatibel sind.
Was dieses Kapitel angeht, so gibt es nur wenige sichtbare Unterschiede zwischen den Versionen 2.4 und 2.2. In Version 2.2 wurden allerdings viele der Prototypen der file_operations-Methoden im Vergleich zur Version 2.0 geändert; außerdem wurde der Zugriff auf den User-Space deutlich verändert (und vereinfacht). Der Semaphor-Mechanismus war in Linux 2.0 noch nicht so weit entwickelt. Und schließlich wurde in der 2.1-Entwicklungsserie der Verzeichniseintrag-Cache (dentry cache) eingeführt.
Eine Reihe von Faktoren haben die Änderungen in den file_operations-Methoden vorangetrieben. Die seit langem bestehende Grenze von 2 GByte für die Dateigröße brachte selbst zu Zeiten von Linux 2.0 Probleme mit sich. Daher wurde ab der 2.1-Serie der Typ loff_t, ein 64-Bit-Wert, verwendet, um Dateipositionen und Längen anzugeben. Die Unterstützung großer Dateien war vor der Version 2.4 noch nicht vollständig in den Kernel integriert, aber ein großer Teil der Basisarbeit wurde schon vorher erledigt und mußte von Treiber-Entwicklern berücksichtigt werden.
Eine weitere in der 2.1-Entwicklung eingeführte Änderung war das Hinzufügen des Zeigerarguments f_pos zu den Methoden read und write. Diese Änderung wurde vorgenommen, um die POSIX-Systemaufrufe pread und pwrite zu unterstützen, die explizit den Datei-Offset angeben, von dem Daten gelesen oder zu dem Daten geschrieben werden sollen. Ohne diese Systemaufrufe kann es bei Multithreading-Programmen zu Race Conditions kommen, wenn Daten in Dateien verschoben werden.
Fast alle Methoden in Linux 2.0 erwarteten ein explizites Zeigerargument inode. In der 2.1-Reihe wurde dieser Parameter aus mehreren Methoden entfernt, weil er selten benötigt wurde. Wenn Sie ihn noch brauchen, können Sie ihn immer noch aus dem filp-Argument gewinnen.
Als Ergebnis daraus sahen die Prototypen der häufig verwendeten Methoden in file_operations in 2.0 so aus:
Beachten Sie, daß diese Methode in Linux 2.0 nicht llseek, sondern lseek heißt. Die Namensänderung soll anzeigen, daß Positionierungsoperationen jetzt mit 64-Bit-Offset-Werten vorgenommen werden können.
Wie erwähnt hatten diese Funktionen in Linux 2.0 den inode-Zeiger als Argument, dafür aber kein Positionsargument.
Im 2.0-Kernel konnte die Methode release nicht fehlschlagen und gab deswegen void zurück.
Es hat noch viele weitere Änderungen an der Struktur file_operations gegeben; wir werden diese in den folgenden Kapiteln behandeln, wenn sie für uns relevant werden. Es lohnt sich aber gleichwohl auch jetzt schon, sich anzuschauen, wie portabler Code geschrieben werden kann, der die bisher geschriebenen Änderungen abdeckt. Die Änderungen an diesen Methoden sind umfassend, und es gibt keine einfache, elegante Möglichkeit, sie zu behandeln.
Der Beispiel-Code berücksichtigt diese Änderungen mit kleinen Wrapper-Funktionen, die zwischen der alten und der neuen API “übersetzen”. Diese Wrapper werden nur verwendet, wenn mit 2.0-Header-Dateien kompiliert wird, und ersetzen die “echten” Geräte-Methoden in der Struktur file_operations. Der folgende Code implementiert die Wrapper für den scull-Treiber:
/* *DiefolgendenWrapperbringendenCodemit2.0-KernelnzumLaufen */ #ifdefLINUX_20 intscull_lseek_20(structinode*ino,structfile*f, off_toffset,intwhence) { return(int)scull_llseek(f,offset,whence); } intscull_read_20(structinode*ino,structfile*f,char*buf, intcount) { return(int)scull_read(f,buf,count,&f->f_pos); } intscull_write_20(structinode*ino,structfile*f,constchar*b, intc) { return(int)scull_write(f,b,c,&f->f_pos); } voidscull_release_20(structinode*ino,structfile*f) { scull_release(ino,f); } /*"Echte"Namenin2.0-Namenumdefinieren*/ #definescull_llseekscull_lseek_20 #definescull_readscull_read_20 #definescull_writescull_write_20 #definescull_releasescull_release_20 #definellseeklseek #endif/*LINUX_20*/ |
Das Umdefinieren der Namen auf diese Weise kann auch Struktur-Elemente abdecken, deren Namen sich mit der Zeit geändert haben (wie etwa im Falle der Änderung von lseek in llseek).
Wir müssen wahrscheinlich nicht extra sagen, daß Sie solche Umdefinitionen von Namen mit Vorsicht vornehmen sollten; diese Zeilen sollten vor der Definition der Struktur file_operations, aber nach einer Veränderung dieser Namen stehen.
Es gibt noch zwei weitere Inkompatibilitäten im Zusammenhang mit der Struktur file_operations. So wurde die Methode flush während der 2.1-Entwicklung hinzugefügt. Treiber-Entwickler müssen sich fast nie um diese Methode kümmern, aber ihre reine Anwesenheit mitten in der Struktur kann trotzdem noch zu Problemen führen. Sie können es am besten vermeiden, sich mit der flush-Methode auseinanderzusetzen, wenn Sie die markierte Initialisierungssyntax verwenden, wie wir das in allen Beispieldateien getan haben.
Der andere Unterschied besteht darin, wie ein inode-Zeiger aus der filp-Struktur geholt wird. Während moderne Kernel eine dentry-Struktur (directory entry, Verzeichniseintrag) verwenden, gab es eine solche Struktur im Kernel 2.0 nicht. Daher definiert sysdep.h ein Makro, das für den portablen Zugriff auf inode von einem filp verwendet werden soll.
#ifdefLINUX_20 #defineINODE_FROM_F(filp)((filp)->f_inode) #else #defineINODE_FROM_F(filp)((filp)->f_dentry->d_inode) #endif |
> > In 2.2 und früheren Kerneln gab es von seiten des Linux-Kernels keine Unterstützung für Module bei der Verwaltung des Verwendungszählers. Die Module mußten diese Arbeit selbst erledigen. Dieser Ansatz war fehlerträchtig und machte viel doppelte Arbeit notwendig.
Code, der portabel sein soll, muß aber auch mit dieser alten Vorgehensweise zurechtkommen. Das bedeutet, daß der Verwendungszähler weiterhin inkrementiert werden muß, wenn eine neue Referenz auf das Modul angelegt wird, und daß er dekrementiert werden muß, wenn diese Referenz wieder verschwindet. Portabler Code muß auch das Problem umgehen, daß das Feld owner in der Struktur file_operations in früheren Kernel-Versionen nicht existiert. Das ist am einfachsten möglich, wenn Sie SET_MODULE_OWNER verwenden, anstatt auf das Feld owner zuzugreifen. In sysdep.h stellen wir eine leere Version von SET_FILE_OWNER für Kernel bereit, die nicht über dieses Feld verfügen.
Die Semaphor-Unterstützung war im 2.0-Kernel noch weniger weit entwickelt; ganz allgemein war die Unterstützung für SMP-Systeme zu diesem Zeitpunkt noch recht primitiv. Treiber, die nur für diese Kernel-Version geschrieben wurden, müssen gar keine Semaphore verwenden, weil nur jeweils eine CPU Kernel-Code ausführen konnte. Dennoch mag es hier die Notwendigkeit für Semaphore geben, und es schadet allemal nicht, den vollständigen Schutz späterer Kernel-Versionen zu haben.
Die meisten der in diesem Kapitel behandelten Semaphor-Funktionen existierten auch schon im Kernel 2.0. Die einzige Ausnahme bildet die Funktion sema_init; im Kernel 2.0 mußten Entwickler die Semaphore manuell initialisieren. Die Header-Datei sysdep.h deckt dieses Problem durch Definition einer Version von sema_init ab, die bei der Verwendung eines 2.0-Kernels benutzt wird:
#ifdefLINUX_20 #ifdefMUTEX_LOCKED/*Nur,wennsemaphore.heingebundenist*/ externinlinevoidsema_init(structsemaphore*sem,intval) { sem->count=val; sem->waking=sem->lock=0; sem->wait=NULL; } #endif #endif/*LINUX_20*/ |
Schließlich änderte sich am Anfang der 2.1-Reihe der Zugriff auf den User-Space total. Die neue Schnittstelle hat ein besseres Design und nutzt die Hardware für den sicheren Zugriff auf User Space-Speicher sehr viel besser aus. Aber natürlich hat sich die Schnittstelle geändert. Im 2.0-Kernel sahen die Funktionen für den Speicherzugriff folgendermaßen aus:
voidmemcpy_fromfs(void*to,constvoid*from,unsignedlongcount); voidmemcpy_tofs(void*to,constvoid*from,unsignedlongcount); |
Die Namen dieser Funktionen stammen aus der historischen Verwendung des FS-Segment-Registers auf dem i386. Beachten Sie, daß diese Funktionen keinen Rückgabewert haben; wenn der Benutzer eine ungültige Adresse angibt, dann schlägt das Kopieren der Daten stillschweigend fehl. sysdep.h versteckt die Umbenennung und erlaubt Ihnen das portable Aufrufen von copy_to_user und copy_from_user.