Anhang B. Portierungs-Guide

Sie möchten einen Treiber für Kernel 2.4 auf Kernel 2.6 portieren? In diesem Abschnitt erfahren Sie kurz und knapp, womit Sie anfangen und was Sie dabei beachten müssen.

Kompilieren

Um einen Treiber für Kernel 2.6 übersetzen zu können, benötigen Sie als Erstes

  1. Kernelquellen,

  2. eine gültige Kernelkonfiguration und

  3. ein neues Makefile.

Hinweise zu den Kernelquellen und zur Kernelkonfiguration finden Sie in Kernel generieren und installieren. Wie das Makefile aufgebaut sein muss, ist mitsamt einem Beispiel-Makefile im Kapitel Modultreiber außerhalb der Kernelquellen beschrieben.

Die Datei-Erweiterung der auf diese Weise generierten Module lautet nicht mehr ».o«, sondern ».ko«.

Parameterübergabe

In Kernel 2.6 ist das Verfahren, wie einem Modul Parameter übergeben werden, geändert worden. Das Makro MODULE_PARM ist durch die Makros module_param, module_param_array und module_param_string ersetzt worden.

Während in Kernel 2.4 Basis-Standardtypen und Felder mit dem gleichen Makro spezifiziert wurden, gibt es in Kernel 2.6 für Felder eigene Makros.

Die in Kernel 2.4 vorhandenen Basis-Standardtypen (nicht Felder) sind einfach umzusetzen:

Datentyp Version 2.4 Version 2.6
byte (char) MODULE_PARM( bytevar, "b" ); module_param( bytevar, byte, 0 );
short (2 byte) MODULE_PARM( shortvar, "h" ); module_param( shortvar, short, 0 );
integer MODULE_PARM( intvar, "i" ); module_param( intvar, int, 0 );
long MODULE_PARM( longvar, "l" ); module_param( longvar, long, 0 );

Allerdings ist der Datentyp »byte« in Kernel 2.6.3 noch nicht umgesetzt. Hier weicht der Programmierer am besten zunächst auf den Datentyp »short« aus.

Haben Sie in Ihrem Treiber Felder definiert, müssen Sie auf die neuen Makros module_param_array und module_param_string ausweichen.

Im Kernel 2.4 konnte im zweiten Parameter des Makros MODULE_PARM neben dem Datentyp auch die minimale und die maximale Anzahl Feldelemente kodiert werden. In Kernel 2.6 lässt sich für Datentypen nur die maximale Anzahl angeben. Wer auch eine minimale Anzahl Parameter überprüfen will, muss in Kernel 2.6 eigene Datentypen festlegen.

Der Code

int intarray[4];
MODULE_PARM( intarray, "1-4i" );
wird damit in Kernel 2.6 folgendermaßen umgesetzt:
int intarraycount, intarray[4];
module_param_array( intarray, i, intarraycount, 0 );

Modul-Verwaltung

Die Makros MOD_INC_USE_COUNT und MOD_DEC_USE_COUNT existieren nicht mehr. In Kernel 2.6 werden sie normalerweise nicht gebraucht und können gelöscht werden. Kernel 2.6 versucht selbständig mitzuzählen, wie viele Instanzen auf ein Modul zugreifen.

Wer dennoch selbst den Referenzzähler eines Moduls erhöhen oder erniedrigen will, wird stattdessen die Funktionen try_module_get und module_put aufrufen. Als Parameter wird »THIS_MODULE« eingesetzt.

Ressource reservieren

Die Funktionen check_region und check_mem_region gibt es im Kernel 2.6 nicht mehr. Sie werden auch nicht benötigt. Ob der angeforderte Port- oder Speicherbereich frei war, ist ohnehin anhand des Rückgabewertes der Funktionen request_region und request_mem_region ersichtlich.

Treiber, die separat testen, ob eine Ressource frei ist müssen den Code so abändern, dass Test und Reservierung in einem erfolgen.

Interrupt-Service-Routinen

Interrupt-Service-Routinen haben im Kernel 2.6 einen Rückgabewert vom Typ »irqreturn_t« respektive »int«.

Der Rückgabewert ist entweder »IRQ_HANDLED« oder »IRQ_NONE«, abhängig davon, ob die ISR den Interrupt bearbeitet hat oder nicht.

Schlafen legen

Eine wesentliche Änderung zwischen Kernel 2.4 und 2.6 betrifft das Schlafenlegen von Treiberinstanzen oder Kernel-Threads. Die »alten« Funktionen sleep_on und interruptible_sleep_on existieren nicht mehr. Stattdessen muss der Code so umgeschrieben werden, dass die bereits in Kernel 2.4 eingeführten Funktionen wait_event und wait_event_interruptible verwendet werden können.

Kernel-Threads

Der Rückgabewert der Funktion kernel_thread ist in Kernel 2.6 vom Typ »int« und nicht mehr vom Typ »long«.

Darüber hinaus ist die Funktion daemonize geändert worden. Die Funktion hat jetzt Parameter, die den Namen des zugehörigen Kernel-Threads bestimmen. Damit entfällt der in Kernel 2.4 obligatorische Aufruf der Funktion strncpy zum Kopieren des Namens.

Der Kernel-Event-Daemon (keventd) existiert nicht mehr. Stattdessen soll die Event-Workqueue verwendet werden. Entsprechend ist die Funktion schedule_task durch die Funktion schedule_work auszutauschen.

Der Code aus 2.4

struct tq_struct mytask;
...
    mytask.routine=my_function;
    mytask.data   = NULL;
    ...
    schedule_task( &mytask );
    ...
wird in 2.6 umgesetzt zu
static DECLARE_WORK(work, my_function, NULL );
...
    if( schedule_work( &work )==0 ) {
        ...// Fehler
    }

Task-Queues und Bottom-Halves

Task-Queues und Bottom-Halves gibt es nicht mehr. Programmierer nutzten in der Regel die drei Task-Queues »tq_immediate«, »tq_timer« und »tq_scheduler«. Die Task-Queue »tq_immediate« und damit auch Bottom-Halves können durch ein Tasklet ersetzt werden. Die Task-Queue »tq_timer« kann durch ein Timer-Objekt ausgetauscht werden, wobei hier der Zeitpunkt, zu dem die Funktion des Timer-Objekts aufgerufen wird, auf einer x86-Plattform 10 Jiffies betragen sollte (der Timer wird unter Kernel 2.6 zehnmal häufiger aufgerufen als früher). Für die Ersetzung der »tq_scheduler« ist beispielsweise die Event-Workqueue geeignet.

Code in Kernel 2.4:

static struct tq_struct bhtask;
...
    INIT_TQUEUE( &bhtask, my_function, NULL );
    ...
    queue_task( &bhtask, &tq_immediate );
    mark_bh( IMMEDIATE_BH );

wird beispielsweise ersetzt durch

DECLARE_TASKLET( tltask, my_function, 0L );
...
    tasklet_schedule( &tltask );

Intermodul-Kommunikation

Die Funktionen inter_module_register, inter_module_unregister, inter_module_get, inter_module_get_request und inter_module_put existieren in Kernel 2.6 nicht mehr.

Zur Kommunikation zwischen Modulen müssen jetzt die Funktionen symbol_get, symbol_put und symbol_put_addr verwendet werden. Die Aufrufe von inter_module_register und inter_module_unregister können gelöscht werden, stattdessen werden die Variablen und Funktionen mit Hilfe von EXPORT_SYMBOL und EXPORT_SYMBOL_GPL exportiert.

Das importierende Modul deklariert zu importierende Variablen im Kopf des Moduls und tauscht die Funktion inter_module_get mit der Funktion symbol_get aus, wobei die Namen von Funktionen und Variablen direkt – und nicht mehr wie früher als String – angegeben werden.

Kernel 2.4:

static void (*vom_anderen_modul)();
...
    vom_anderen_modul = inter_module_get("funktions_name")
wird zu:
extern void funktions_name;
static void (*vom_anderen_modul)();
...
    vom_anderen_modul = symbol_get( funktions_name );

Die Funktion inter_module_get_request muss aufgesplittet werden: in das Laden des Moduls (siehe request_module) und in die Zuweisung der Symboladresse.

Device-Filesystem

Das Device-Filesystem ist in Kernel 2.6 kräftig aufgeräumt worden. Die Funktionen devfs_register_chrdev, devfs_register_blkdev und devfs_register existieren nicht mehr. Die Funktionen sind durch die wesentlich übersichtlicheren Funktionen devfs_mk_cdev und devfs_mk_bdev ersetzt worden.

Kritische Abschnitte schützen

Eine der wichtigsten Änderungen zwischen Kernel 2.4 und Kernel 2.6 ist die Einführung der Unterbrechbarkeit von Kernelfunktionen (Preemption). Mit dem damit verbundenen neuen Unterbrechungsmodell hat sich die Zahl möglicher kritischer Abschnitte wesentlich erhöht. Der Entwickler muss sich sehr sorgfältig seinen Treibercode ansehen. Globale Variablen müssen in Kernel 2.6 möglicherweise über ein Spinlock gesichert werden.

Blockgeräte-Treiber

In Kernel 2.6 ist das zentrale Objekt, welches ein Blockgerät repräsentiert, nicht mehr die Tabelle mit den Treiber-Einsprungspunkten (struct block_device_operations), sondern vielmehr die struct gendisk. Die Tabelle mit den Treiber-Einsprungspunkten wird in die struct gendisk eingehängt. Die Übergabe des Objektes an das Blockgeräte-Subsystem findet damit auch nicht mehr in der Funktion register_blkdev, sondern in der Funktion add_disk statt. Weiter noch: In Kernel 2.6 muss die Funktion register_blkdev nicht mehr aufgerufen werden. Nur wer sich dynamisch eine Minor-Nummer zuteilen lassen und unter /proc/devices gelistet werden möchte, verwendet die Funktion. Diese benötigt im Übrigen auch nur noch zwei, und nicht mehr drei Parameter.

Die struct gendisk nimmt auch die Kenndaten des Gerätes (der Disk) auf. Die dafür früher vorhandenen globalen Variablen existieren nicht mehr.

Auch beim Datenaustausch hat es wesentliche Veränderungen gegeben. Statt einer globalen Request-Queue bringt jedes Gerät (Disk) seine eigene Request-Queue und folglich auch sein eigenes Lock mit. Das Lock io_request_lock gibt es damit nicht mehr.

Der Zugriff auf die Requests erfolgt nicht mehr über das Makro CURRENT, sondern über elv_next_request. Die aus Kernel 2.4 bekannten Buffer-Heads sind in Kernel 2.6 durch die BIO-Blöcke mit ihren eigenen Zugriffsfunktionen abgelöst worden.

USB

Zum Registrieren eines USB-Treibers beim USB-Subsystem wird zwar weiterhin die Funktion usb_register aufgerufen, allerdings hat sich die dabei übergebene struct usb_driver geändert. So nutzen USB-Treiber nicht mehr automatisch die Major-Nummer des USB-Subsystems. Die Treiber, die die Major-Nummer ohnehin nicht genutzt haben, müssen keine struct file_operations definieren.

Treiber, die die Major-Nummer mitverwenden wollen, müssen unter Kernel 2.6 eine struct usb_class_driver ausfüllen. In diese wird auch ein Zeiger auf die struct file_operations eingetragen. Die struct usb_class_driver wird dem USB-Subsystem durch Aufruf der Funktion usb_register übergeben. Anders als in Kernel 2.4 erstellt Kernel 2.6 – falls das Device-Filesystem aktiviert ist – automatisch eine Gerätedatei. Der Treiber gibt die ihm zugewiesene Minor-Nummer durch Aufruf der Funktion usb_deregister_dev wieder frei.

Die Funktion usb_submit_urb hat in Kernel 2.6 einen zusätzlichen Parameter: die Flags, die die Reservierung von Speicher steuern. Um das »alte« Verhalten zu haben, kann hier einfach GFP_KERNEL eingesetzt werden.

Ebenfalls geändert wurde die probe-Funktion. In Kernel 2.4 sah der Prototyp der Funktion folgendermaßen aus:

    void *usb_driver_probe( struct usb_device *dev, unsigned intf, const
        struct usb_device_id *id );
In Kernel 2.6 lautet er:
    void *usb_driver_probe( struct usb_interface *intf, const struct
        usb_device_id *id );

Wie ersichtlich, bekommt die Funktion direkt das Interface übergeben.

Die Unterschiede im Bereich USB sind detaillierter unter [Kroah2003a] zu finden.

Sonstiges

Die Header-Datei <linux/modversions.h> existiert nicht mehr. Falls Sie diese in Ihrem Treiber inkludieren, können Sie das zugehörige Include löschen.

Die Funktion verify_area existiert im Kernel 2.6 nicht mehr. Im Code kann anstelle von verify_area die Funktion access_ok verwendet werden. Parameter und Rückgabewert sind identisch.

Das Makro EXPORT_SYMBOL_NOVERS ist im Kernel 2.6 quasi nicht mehr existent. Es kann durch EXPORT_SYMBOL ersetzt werden.

Die Funktionen sti und cli sollen unter Kernel 2.6 nicht mehr verwendet werden. Stattdessen kann der Programmierer auf local_irq_disable und local_irq_enable zurückgreifen. Allerdings sollte die Interruptsperre generell nicht mehr benutzt werden. Vielmehr sollte der Programmierer im Treiber ein Spinlock einführen, mit dem er den zugehörigen kritischen Abschnitt schützt.


Lizenz