Die Symboltabelle des Kernels

Wir haben schon gesehen, wie insmod undefinierte Symbole anhand der Tabelle der öffentlichen Kernel-Symbole auflöst. Diese Tabelle enthält die Adressen der globalen Kernel-Elemente — Funktionen und Variablen —, die benötigt werden, um modularisierte Treiber zu programmieren. Die öffentliche Symboltabelle kann in Textform aus der Datei /proc/ksyms ausgelesen werden (natürlich nur, wenn Ihr Kernel das /proc-Dateisystem unterstützt, was er wirklich tun sollte).

Wenn Ihr Modul geladen wird, werden alle deklarierten globalen Symbole in die Symboltabelle des Kernels aufgenommen. Das können Sie anhand der Datei /proc/ksyms oder der Ausgabe des Befehls ksyms überprüfen.

Neue Module können wiederum die Symbole verwenden, die Ihr Modul exportiert; so können Sie neue Module auf anderen Modulen aufsetzen lassen. Auch in den ausgelieferten Kernel-Quellen bauen Module aufeinander auf: Das msdos-Dateisystem benötigt die Symbole, die vom Modul fat exportiert werden, und jedes USB-Input-Gerätemodul baut auf den Modulen usbcore und input auf.

Das Aufeinandersetzen von Modulen ist in komplexen Projekten sehr nützlich. Wenn eine neue Abstraktion in Form eines Gerätetreibers implementiert wird, könnte diese beispielsweise eine Schnittstelle für hardware-spezifische Implementationen bieten. Beispielsweise könnte ein Bildschirmspeicher-Videotreiber Symbole exportieren, die von einem hardware-näheren VGA-Treiber verwendet werden. Jeder Benutzer lädt das Bildschirmspeicher-Modul sowie das passende VGA-Modul für seine vorhandene Hardware. Die Unterstützung von Parallel-Ports und die große Vielfalt von Peripherie-Geräten wird auf ähnliche Weise unterstützt — außerdem das gesamte USB-Kernel-Subsystem. Der Aufbau des Subsystems für den Parallel-Port ist in Abbildung 2-2 zu sehen; die Pfeile stellen die Kommunikation zwischen den Modulen (mit einigen Beispielfunktionen und Datenstrukturen) und mit der Kernel-Programmierschnittstelle dar.

Abbildung 2-2. Aufeinanderstapeln von Treibermodulen für den Parallel-Port

Wenn aufeinandergestapelte Module verwendet werden, ist das Hilfsprogramm modprobe nützlich. modprobe funktioniert ähnlich wie insmod, lädt aber auch alle anderen Module, die das zu ladende Modul zusätzlich benötigt. Ein modprobe-Aufruf kann also mehrere insmod-Aufrufe ersetzen (auch wenn Sie insmod immer noch brauchen, wenn Sie eigene Module aus dem aktuellen Verzeichnis laden wollen, weil modprobe nur den Baum mit den installierten Modulen verwendet).

Die Modularisierung in Ebenen kann auch die Entwicklungszeit verkürzen, weil jede einzelne Ebene vereinfacht wird. Das entspricht der Trennung in Mechanismus und Policy, über die wir in Kapitel 1 gesprochen haben.

Normalerweise implementiert ein Modul seine eigene Funktionalität, ohne überhaupt irgendwelche Symbole exportieren zu müssen. Sie müssen aber immer dann Symbole exportieren, wenn andere Module davon Gebrauch machen können oder sollen. Eventuell müssen Sie auch spezielle Anweisungen einbinden, um das Exportieren aller nicht-statischen Symbole zu vermeiden, da manche (aber nicht alle) Versionen von modutils defaultmäßig alle Symbole exportieren.

Die Header-Dateien des Linux-Kernels stellen eine bequeme Möglichkeit bereit, die Sichtbarkeit Ihrer Symbole zu verwalten, reduzieren damit die Verschmutzung des Namensraums und fördern das Verstecken von Informationen. Der in diesem Abschnitt beschriebene Mechanismus funktioniert ab Kernel-Version 2.1.18; der 2.0-Kernel hatte einen vollständig anderen Mechanismus, der am Ende dieses Kapitels beschrieben wird.

Wenn Ihr Modul überhaupt keine Symbole exportiert, dann können Sie das mit dem folgenden Makroaufruf in Ihrer Quelldatei ausdrücklich ausweisen:


 EXPORT_NO_SYMBOLS;

Das Makro wird zu einer Assembler-Anweisung expandiert und kann an beliebiger Stelle im Modul stehen. Portabler Code sollte es aber in die Initialisierungsfunktion des Moduls (init_module) stellen, weil die in sysdep.h definierte Version dieses Makros nur dort funktioniert.

Wenn Sie dagegen einen Teil der Symbole aus Ihrem Modul exportieren müssen, besteht der erste Schritt darin, das Präprozessor-Makro EXPORT_SYMTAB zu definieren. Dies muß vor dem Einbinden von module.h geschehen. Normalerweise macht man das mit dem Compiler-Schalter –D im Makefile.

Wenn EXPORT_SYMTAB definiert ist, können die einzelnen Symbole mit ein paar Makros exportiert werden:


 EXPORT_SYMBOL (name);
 EXPORT_SYMBOL_NOVERS (name);

> > Beide Versionen des Makros machen ein Symbol außerhalb des Moduls sichtbar, die zweite Version (EXPORT_SYMBOL_NOVERS) exportiert das Symbol ohne Versionsinformationen (siehe Kapitel 11>). Symbole müssen außerhalb jeder Funktion exportiert werden, weil die Makros zu einer Variablendeklaration expandieren. (Wenn es Sie interessiert, können Sie in <linux/module.h> nachlesen, wie das funktioniert, aber das ist zum Programmieren von Modulen nicht notwendig.)