Abwärtskompatibilität

Der Linux-Kernel ist ein sich bewegendes Ziel; viele Dinge ändern sich während der Entwicklung neuer Features. Die Schnittstelle, die wir in diesem Kapitel beschrieben haben, ist diejenige, die Sie im 2.4-Kernel vorfinden werden; wenn Ihr Code auch mit älteren Versionen laufen soll, müssen Sie einige Änderungen vornehmen.

Dies ist der erste von vielen Abschnitten mit dem Titel "Abwärtskompatibilität" in diesem Buch. Am Ende jedes Kapitels werden wir die Dinge behandeln, die sich seit der Kernel-Version 2.0 verändert haben, und beschreiben, was Sie tun müssen, damit Ihr Code portabel wird.

Zunächst einmal wurde das Makro KERNEL_VERSION mit der Version 2.1.90 eingeführt. Die Header-Datei sysdep.h enthält einen Ersatz für die Kernel, die das benötigen.

Änderungen in der Ressourcen-Verwaltung

Die neue Ressourcen-Verwaltung bringt einige Portabilitätsprobleme mit sich, wenn Sie einen Treiber schreiben wollen, der mit Kernel-Versionen vor 2.4 funktioniert. In diesem Abschnitt beschreiben wir, auf welche Probleme Sie stoßen werden und wie die Header-Datei sysdep.h versucht, die Probleme vor Ihnen zu verbergen.

Die offensichtlichste Änderung im neuen Code zur Ressourcen-Verwaltung ist das Hinzufügen von request_mem_region und verwandten Funktionen. Ihre Rolle beschränkt sich auf den Zugriff auf die I/O-Speicher-Datenbank, ohne bestimmte Operationen auf irgendwelcher Hardware durchzuführen. In älteren Kerneln sollten Sie also diese Funktionen einfach nicht aufrufen. Das geht leicht mit der Header-Datei sysdep.h, die diese Funktionen einfach als Makros definiert, die in Kerneln vor der Version 2.4 0 zurückgeben.

Ein weiterer Unterschied zwischen 2.4 und früheren Kernel-Versionen besteht in den Prototypen von request_region und zugehörigen Funktionen selbst.

Kernel vor 2.4 haben sowohl request_region als auch release_region als Funktionen deklariert, die void zurückgeben, (und erzwangen damit den vorhergehenden Aufruf von check_region). Die neue Implementation enthält jetzt Funktionen, die einen Zeiger-Wert zurückgeben, so daß Fehler gemeldet werden können (was check_region ziemlich nutzlos macht). Der Zeiger-Wert selbst kann in Treiber-Code für nichts als einen Test auf NULL verwendet werden; dieser Wert bedeutet, daß die Anforderung fehlgeschlagen ist.

Wenn Sie sich ein paar Zeilen Code in Ihren Treibern sparen wollen und Ihnen die Abwärtskompatibilität egal ist, dann können Sie auch die neuen Funktionsaufrufe ausnutzen und die Verwendung von check_region in Ihrem Code vermeiden. check_region ist jetzt mit Hilfe von request_region implementiert, gibt den I/O-Bereich frei und meldet eine erfolgreiche Ausführung, wenn die Anfrage erfüllt werden konnte; der zusätzliche Aufwand ist vernachlässigbar, weil keine dieser Funktionen jemals aus zeitkritischem Code aufgerufen wird.

Wenn Sie lieber portablen Code haben möchten, dann können Sie es beim weiter oben vorgeschlagenen Code belassen und die Rückgabewerte von request_region und release_region ignorieren. sysdep.h deklariert die beiden Funktionen sowieso als Makros, die 0 (Erfolg) zurückgeben, so daß Sie sowohl portabel sein als auch den Rückgabewert jeder aufgerufenen Funktion abfragen können.

Der letzte Unterschied in der I/O-Registry zwischen Version 2.4 und früheren Kernel-Versionen besteht in den Datentypen, die für die Argumente start und len verwendet werden. Während neuere Kernel immer unsigned long verwenden, benutzen ältere Kernel kürzere Typen. Diese Änderung hat aber keinen Einfluß auf die Portabilität des Treibers.

Kompilieren auf Multiprozessor-Systemen

In der Version 2.0 des Kernels wurde die Konfigurationsoption CONFIG_SMP nicht verwendet, um Kernel auf SMP-Systemen zu bauen; statt dessen wurde eine globale Zuweisung im Haupt-Makefile des Kernels benutzt. Beachten Sie, daß Module, die für einen SMP-Rechner kompiliert worden sind, nicht in einem Einzelprozessor-Kernel laufen und umgekehrt; es ist also wichtig, daß hier nichts durcheinandergerät.

Der Beispiel-Code zu diesem Buch kümmert sich automatisch um SMP in den Makefiles, so daß der oben gezeigte Code nicht in jedes Modul kopiert werden muß. Allerdings unterstützen wir kein SMP in der Kernel-Version 2.0. Dies sollte aber kein Problem sein, weil die Multiprozessor-Unterstützung in Linux 2.0 ohnehin nicht so besonders stabil war; alle, die SMP-Systeme verwenden, sollten 2.2 oder 2.4 benutzen. Version 2.0 wird in diesem Buch behandelt, weil das immer noch die Plattform der Wahl (insbesondere in der MMU-freien Implementation) für kleine eingebettete Systeme ist. Solche Systeme haben aber nicht mehrere Prozessoren.

Symbole in Linux 2.0 exportieren

Der Mechanismus zum Exportieren von Symbolen in Linux 2.0 wurde um eine Funktion namens register_symtab herum entwickelt. Ein Linux 2.0-Modul baut eine Tabelle mit allen zu exportierenden Symbolen auf und ruft dann aus seiner Initialisierungsfunktion heraus register_symtab auf. Nur in dieser expliziten Symboltabelle aufgeführte Symbole wurden an den Kernel exportiert. Wenn die Funktion dagegen gar nicht aufgerufen wurde, wurden alle globalen Symbole exportiert.

Wenn Ihr Modul keine Symbole exportieren muß und Sie nicht alles als static deklarieren wollen, dann verstecken Sie einfach die globalen Symbole durch Hinzufügen der gleich gezeigten Zeile zu init_module. Dieser Aufruf von register_symtab überschreibt einfach die Default-Symboltabelle des Moduls mit einer leeren Tabelle:


 register_symtab(NULL);

Genau so ist EXPORT_NO_SYMBOLS in sysdep.h definiert, wenn für die Version 2.0 kompiliert wird. Aus diesem Grund muß EXPORT_NO_SYMBOLS auch in init_module stehen, um unter Linux 2.0 korrekt zu funktionieren.

Wenn Sie aber Symbole aus Ihrem Modul exportieren müssen, dann müssen Sie eine Symboltabellenstruktur erzeugen, die diese Symbole beschreibt. Das Füllen einer solchen Struktur für Linux 2.0 ist eine knifflige Aufgabe, aber die Kernel-Entwickler haben Header-Dateien geschrieben, die die Sache vereinfachen. Die folgenden Code-Zeilen zeigen, wie eine Symboltabelle mittels der Header-Dateien von Linux 2.0 deklariert und exportiert wird:


static struct symbol_table skull_syms = {

#include <linux/symtab_begin.h>
  X(skull_fn1),
  X(skull_fn2),
  X(skull_variable),
#include <linux/symtab_end.h>
        };

 register_symtab(&skull_syms);

Das Schreiben portablen Codes, der die Sichtbarkeit von Symbolen steuert, erfordert spezielle Maßnahmen des Treiber-Programmierers. Dies ist ein Fall, in dem es nicht reicht, einige Kompatibilitätsmakros zu definieren; statt dessen gilt es, eine nicht zu vernachlässigende Menge von bedingtem Präprozessor-Code zu schreiben. Die Konzepte aber sind einfach. Zunächst müssen die zu verwendende Kernel-Version ermittelt und einige Symbole entsprechend definiert werden. Wir haben uns entschieden, in sysdep.h ein Makro namens REGISTER_SYMTAB() zu definieren, das in Version 2.2 und neuer zu nichts expandiert und in Version 2.0 zu register_symtab. Außerdem wird __USE_OLD_SYMTAB__ definiert, wenn alter Code verwendet werden muß.

Durch Verwendung dieses Codes kann ein Modul, das Symbole exportiert, dies jetzt portabel tun. Im Beispiel-Code finden Sie ein Modul namens misc-modules/export.c, das nichts tut, außer ein Symbol zu exportieren. Dieses Modul, das in “the Section called Versionskontrolle in Modulen in Kapitel 11>” in Kapitel 11> genauer besprochen wird, enthält die folgenden Zeilen, um das Symbol portabel zu exportieren:


#ifdef _ _USE_OLD_SYMTAB_ _
 static struct symbol_table export_syms = {
  #include <linux/symtab_begin.h>
  X(export_function),
  #include <linux/symtab_end.h>
 };
#else
 EXPORT_SYMBOL(export_function);
#endif

int export_init(void)
{
 REGISTER_SYMTAB(&export_syms);
 return 0;
}

Wenn _ _USE_OLD_SYMTAB_ _ gesetzt ist (es sich also um einen 2.0-Kernel handelt), dann wird die symbol_table-Struktur wie benötigt definiert; ansonsten wird EXPORT_SYMBOL verwendet, um das Symbol direkt zu exportieren. In init_module wird dann REGISTER_SYMTAB aufgerufen, was auf allen anderen Kerneln als 2.0 zu nichts expandiert.

Modul-Konfigurationsparameter

> > > > MODULE_PARM wurde mit der Kernel-Version 2.1.18 eingeführt. Im 2.0-Kernel wurden keine Parameter explizit deklariert. Statt dessen konnte insmod den Wert beliebiger Variablen im Modul ändern. Dieses Verfahren hatte den Nachteil, dem Benutzer Zugriff auf Variablen zu geben, die nicht für solchen Zugriff vorgesehen waren; außerdem war keine Typenüberprüfung der Parameter möglich. MODULE_PARM macht Modul-Parameter viel sauberer und sicherer, macht aber auch Linux 2.2-Module inkompatibel zu 2.0-Kerneln.

> Wenn die Kompatibilität mit 2.0 für Sie ein Problem ist, dann kann ein einfacher Präprozessor-Test verwendet werden, um die diversen MODULE_-Makros so zu definieren, daß sie nichts tun. Die Header-Datei sysdep.h im Beispiel-Code definiert diese Makros bei Bedarf.