Kapitel 11. kmod und fortgeschrittene Modularisierung

Inhalt
Module bei Bedarf laden
Kommunikation zwischen Modulen
Versionskontrolle in Modulen
Abwärtskompatibilität
Schnellreferenz

In diesem zweiten Teil des Buches werden wir fortgeschrittenere Themen als bisher besprechen. Wieder fangen wir mit der Modularisierung an.

Die Einführung in die Modularisierung in Kapitel 2 war nur die halbe Wahrheit; der Kernel und das modutils-Paket unterstützen einige fortgeschrittene Funktionen, die komplexer sind als das, was wir bisher benötigt haben, um einen grundlegenden Treiber zum Laufen zu bringen. Dazu gehören das Programm kmod und die Versionsunterstützung in Modulen (womit Sie es vermeiden können, Ihre Module jedesmal neu zu kompilieren, wenn Sie eine neue Kernel-Version einspielen). Außerdem behandeln wir das Ausführen von Hilfsprogrammen im User-Space vom Kernel-Code aus.

Die Implementation des Ladens von Kernel-Modulen bei Bedarf hat sich mit der Zeit deutlich geändert. Dieses Kapitel behandelt wie üblich die Implementation im 2.4-Kernel. Der Beispiel-Code funktioniert aber soweit möglich auch mit 2.0- und 2.2-Kerneln; wir behandeln die Unterschiede am Ende dieses Kapitels.

Module bei Bedarf laden

Linux unterstützt das automatische Laden und Entladen von Modulen. Damit wird folgendes erreicht:

Um dieses Feature auszunutzen, müssen Sie die Unterstützung für den kmod einschalten, wenn Sie Ihren Kernel konfigurieren; Kernel von Distributoren haben diese Fähigkeit normalerweise von Haus aus. Die Fähigkeit, zusätzliche Module bei Bedarf anzufordern, ist besonders nützlich bei Treibern, die voneinander abhängige Module verwenden.

Die Idee hinter kmod ist einfach, aber effektiv. Wenn der Kernel versucht, auf bestimmte Arten von Ressourcen zuzugreifen, diese aber nicht zur Verfügung stehen, ruft er eine spezielle Funktion im kmod-Subsystem auf, anstatt einfach einen Fehler zurückzugeben. Wenn es kmod gelingt, die Ressource durch Laden eines oder mehrerer Module verfügbar zu machen, arbeitet der Kernel wie gewünscht weiter, ansonsten gibt er den Fehler zurück. Fast alle Ressourcen können auf diese Weise angefordert werden: Zeichen- und Block-Treiber, Dateisysteme, Line Disciplines (auf einer seriellen Leitung verwendete Parameter), Netzwerk-Protokolle usw.

Ein Beispiel für einen Treiber, der davon Gebrauch macht, sind die Advanced Linux Sound Architecture (ALSA)-Soundtreiber, die eines Tages die aktuelle Sound-Implementation (Open Sound System, OSS) im Linux-Kernel ersetzen sollen.[1] ALSA ist in viele Teile aufgeteilt. Zuerst wird der Kern-Code geladen, den jedes System braucht. Zusätzliche Bestandteile werden dann je nach vorhandener Hardware (der jeweiligen Soundkarte) und der gewünschten Funktionalität (MIDI-Sequencer, Synthesizer, Mixer, OSS-Kompatibilität) geladen. Ein großes und kompliziertes System kann so also in seine Bestandteile aufgeteilt werden, wobei nur die jeweils benötigten Teile im laufenden System vorhanden sein müssen.

Eine andere gängige Verwendung des automatischen Ladens von Modulen ist das Bauen von Kerneln, die für alle Lagen passen und die von Distributionen verwendet werden. Distributoren wollen, daß ihre Kernel auf so viel Hardware wie möglich läuft. Es ist aber nicht möglich, einfach jeden denkbaren Treiber in den Kernel zu konfigurieren: Der resultierende Kernel wäre zu groß zum Laden (und würde viele System-Ressourcen verschwenden); außerdem fordert man geradezu Konflikte und Verwirrung heraus, wenn man so viele Treiber nach Hardware suchen läßt. Dank des automatischen Ladens kann sich der Kernel selbst an die auf dem jeweiligen System vorgefundene Hardware anpassen.

Module im Kernel anfordern

Sämtlicher Code im Kernel-Space kann das Laden eines Moduls durch Verwendung von kmod anfordern. kmod wurde ursprünglich als separater, freistehender Kernel-Prozeß implementiert, der Anfragen nach zu ladenden Modulen erledigte, ist aber schon vor längerer Zeit so vereinfacht worden, daß er keinen eigenen Prozeß-Kontext mehr braucht. Um kmod zu verwenden, müssen Sie <linux/kmod.> in Ihre Treiber-Quellen einbinden.

Um das Laden eines Moduls anzufordern, rufen Sie request_module auf:


int request_module(const char *module_name);

Der module_name kann entweder der Name einer bestimmten Moduldatei oder der Name einer generischeren Fähigkeit sein; wir schauen uns die Modulnamen im nächsten Abschnitt noch genauer an. Der Rückgabewert von request_module ist entweder 0 oder einer der üblichen Fehler-Codes, wenn etwas schiefgeht.

Beachten Sie, daß request_module synchron ist — die Funktion schläft, bis der Versuch, das Modul zu laden, vollständig abgearbeitet ist. Das bedeutet natürlich auch, daß request_module nicht im Interrupt-Kontext aufgerufen werden darf. Beachten Sie auch, daß ein erfolgreicher Rücksprung aus request_module nicht garantiert, daß die gewünschte Fähigkeit jetzt zur Verfügung steht. Der Rückgabewert zeigt an, daß request_module modprobe erfolgreich ausführen konnte, sagt aber nichts über den Rückgabewert von modprobe selbst aus. Eine Vielzahl von Problemen oder fehlerhaften Konfigurationen kann dazu führen, daß request_module eine erfolgreiche Ausführung meldet, obwohl das gewünschte Modul nicht geladen worden ist.

Die korrekte Anwendung von request_module setzt also normalerweise das zweimalige Abfragen der gewünschten Fähigkeit voraus:


if ( (ptr = look_for_feature()) == NULL) {
    /* wenn das Feature fehlt, einen Anfrage-String erzeugen */
    sprintf(modname, "fmt-for-feature-%i\n", featureid);
    request_module(modname); /* und versuchen, es zu laden */
}
/* Noch einmal nach dem Feature suchen; Fehler melden, falls nicht vorhanden */
if ( (ptr = look_for_feature()) == NULL)
    return -ENODEV;

Die erste Abfrage vermeidet überflüssige Aufrufe von request_module. Wenn das Feature im laufenden Kernel nicht vorhanden ist, wird ein Anfrage-String erzeugt und mit request_module danach gesucht. Die zweite Abfrage stellt sicher, daß das gewünschte Feature verfügbar geworden ist.

Die Seite des User-Space

Das Laden eines Moduls benötigt Hilfe vom User Space, weil es deutlich einfacher ist, die benötigte Konfigurierbarkeit und Flexibilität in diesem Kontext zu implementieren. Wenn der Kernel-Code request_module aufruft, wird ein neuer “Kernel-Thread”-Prozeß gestartet, der ein Hilfsprogramm im User-Space ausführt. Dieses Programm heißt modprobe, wir sind ihm schon weiter vorn in diesem Buch kurz begegnet.

modprobe kann vieles. Im einfachsten Fall ruft es einfach insmod mit dem an request_module übergebenen Modulnamen auf. Kernel-Code wird request_module aber oft mit einem abstrakteren Namen wie scsi_hostadapter aufrufen; modprobe sucht und lädt dann das passende Modul. modprobe kann außerdem mit Modulabhängigkeiten umgehen. Wenn ein angefordertes Modul ein weiteres Modul benötigt, lädt modprobe beide, sofern depmod -a nach der Installation der Module aufgerufen worden ist.[2]

Das Hilfsprogramm modprobe wird durch die Datei /etc/modules.conf konfiguriert.[3] Die Man-Page zu modules.conf enthält eine vollständige Liste aller Einträge, die in dieser Datei stehen dürfen. Die gängigsten Einträge sind:

path[misc]=directory

Diese Anweisung teilt modprobe mit, daß die diversen Module im Unterverzeichnis misc unter dem angegebenen Verzeichnis directory zu finden sind. Weitere Pfade, die Sie eventuell setzen möchten, sind boot, der auf ein Verzeichnis von Modulen verweist, die beim Booten geladen werden sollen, sowie toplevel, der ein Toplevel-Verzeichnis angibt, unter dem ein Baum mit Modul-Unterverzeichnissen zu finden ist. Vermutlich wollen Sie auch eine separate keep-Anweisung haben.

keep

Normalerweise führt eine path-Anweisung dazu, daß modprobe alle andere Pfade (einschließlich der Defaults) verwirft, die es eventuell kennt. Durch Verwenden von keep vor path fügt modprobe die neuen Pfade zur Liste hinzu, anstatt sie zu löschen.

alias alias_name real_name

Veranlaßt modprobe, das Modul real_name zu laden, wenn es um das Laden von alias_name gebeten wird. Der Aliasname bezeichnet normalerweise eine bestimmte Fähigkeit wie scsi_hostadapter, eth0 oder sound. So werden generische Anforderungen ("ein Treiber für die erste Ethernet-Karte") auf bestimmte Module abgebildet. Alias-Zeilen werden normalerweise während der Systeminstallation erzeugt; wenn einmal ermittelt worden ist, welche Hardware in einem bestimmten System vorhanden ist, werden die passenden alias-Einträge erzeugt, damit die richtigen Treiber geladen werden.

options [-k] module opts

Definiert einen Satz von Optionen (opts) für das angegebene Modul module, die beim Laden verwendet werden. Wenn das Flag -k angegeben wird, wird das Modul durch ein modprobe -r nicht automatisch entfernt.

pre-install module command, post-install module command, pre-remove module command, post-remove module command

Die ersten beiden Anweisungen geben einen Befehl command an, der entweder vor oder nach der Installation des angegebenen Moduls ausgeführt werden soll; die letzten beiden Anweisungen führen den Befehl vor oder nach dem Entfernen des Moduls aus. Diese Anweisungen sind nützlich, wenn eine zusätzliche Verarbeitung im User-Space notwendig ist oder wenn ein notwendiger Daemon-Prozeß gestartet werden soll. Der Befehl sollte mit seinem vollständigen Pfad angegeben werden, um mögliche Probleme zu vermeiden.

Beachten Sie, daß das Modul mit modprobe entfernt werden muß, damit die Anweisungen zum Entfernen ausgeführt werden; dies geschieht nicht, wenn das Modul mit rmmod entfernt wird oder das System (absichtlich oder unabsichtlich) heruntergefahren wird.

modprobe unterstützt deutlich mehr Anweisungen als hier genannt sind, die anderen werden aber nur in komplizierten Situationen benötigt.

Eine typische /etc/modules.conf-Datei sieht so aus:


alias scsi_hostadapter aic7xxx
alias eth0 eepro100
pre-install pcmcia_core /etc/rc.d/init.d/pcmcia start
options short irq=1
alias sound es1370

Diese Datei teilt modprobe mit, welche Treiber zu laden sind, damit das SCSI-System, das Ethernet und die Soundkarten funktionieren. Es stellt außerdem sicher, daß, wenn die PCMCIA-Treiber geladen werden, ein Startup-Skript ausgeführt wird, das den Card Services-Daemon startet. Schließlich wird eine Option angegeben, die an den short-Treiber übergeben wird.

Sicherheitsfragen beim Laden von Modulen

Das Laden eines Moduls in den Kernel hat offensichtliche Auswirkungen auf die Sicherheit, weil der geladene Code mit höchsten Rechten läuft. Aus diesem Grund muß man bei der Arbeit mit dem Modul-Ladesystem sorgfältig vorgehen.

Beim Editieren der Datei modules.conf sollten Sie immer daran denken, daß jeder, der Kernel-Module laden kann, die vollständige Kontrolle über das System hat. Deswegen sollten alle Verzeichnisse, die zum Ladepfad hinzugefügt werden, sorgfältig geschützt werden, desgleichen die Datei modules.conf selbst.

Beachten Sie, daß sich insmod normalerweise weigert, Module zu laden, die nicht dem Benutzer root gehören; dieses Verhalten ist eine Vorsichtsmaßnahme gegen Angreifer, die sich Schreibrechte auf ein Modulverzeichnis verschaffen. Sie können diese Abfrage mit einer Option von insmod (oder einer Zeile in modules.conf) abschalten, das vermindert aber die Sicherheit Ihres Systems.

Sie sollten nicht vergessen, daß der Modulnamen-Parameter, den Sie an request_module übergeben, am Ende in der Kommandozeile von modprobe landet. Wenn dieser Modulname von irgendeinem User-Space-Programm kommt, muß er sehr sorgfältig überprüft werden, bevor er an request_module übergeben wird. Denken Sie beispielsweise an einen Systemaufruf, der Netzwerk-Schnittstellen konfiguriert. Als Antwort auf einen Aufruf von ifconfig fordert dieser Systemaufruf request_module auf, den Treiber der (vom Benutzer angegebenen) Schnittstelle zu laden. Ein feindlich gesinnter Benutzer könnte einen fiktiven Schnittstellennamen wählen, der modprobe veranlaßt, etwas Unpassendes zu tun. Dies ist eine echte Verletzlichkeit, die spät im 2.4.0-test-Entwicklungszyklus entdeckt wurde; die schlimmsten Probleme sind beseitigt worden, aber das System ist immer noch anfällig für böswillige Modulnamen.

Ein Beispiel für das Laden von Modulen

Schauen wir uns jetzt an, wie das Laden von Modulen bei Bedarf in der Praxis funktioniert. Dazu verwenden wir die beiden Module master und slave, die Sie im Verzeichnis misc-modules in den Quelldateien des O'Reilly-FTP-Servers finden.

Um diesen Test-Code auszuführen, ohne die Module im Default-Suchpfad für Module zu installieren, können Sie etwa folgende Zeilen zu Ihrer /etc/modules.conf hinzufügen:


keep
path[misc]=˜rubini/driverBook/src/misc-modules

Das Slave-Modul erfüllt keine Aufgabe; es verbraucht nur Platz, bis es entfernt wird. Das Master-Modul sieht dagegen folgendermaßen aus:


#include <linux/kmod.h>
#include "sysdep.h"


int master_init_module(void)
{
    int r[2]; /* Ergebnisse */

    r[0]=request_module("slave");
    r[1]=request_module("nonexistent");
    printk(KERN_INFO "master: loading results are %i, %i\n", r[0],r[1]);
    return 0; /* Erfolg */
}

void master_cleanup_module(void)
{ }

Beim Laden versucht master, zwei Module zu laden: das Modul slave sowie eines, das nicht existiert. Die printk-Meldungen gelangen in Ihr System-Protokoll und möglicherweise auf die Konsole. Folgendes passiert auf einem System mit kmod-Unterstützung, wenn der Daemon aktiv ist und die Befehle auf der Text-Konsole eingegeben werden:

morgana.root# depmod -a
morgana.root# insmod ./master.o
master: loading results are 0, 0
morgana.root# cat /proc/modules
slave                    248   0  (autoclean)
master                   740   0  (unused)
es1370                 34832   1

Sowohl der Rückgabewert aus request_module als auch die Datei /proc/modules (beschrieben in ``the Section called Initialisierung und Beendigung in Kapitel 2'' in Kapitel 2) zeigen, daß das Modul slave korrekt geladen worden ist. Beachten Sie aber, daß auch der Versuch, nonexistent zu laden, einen erfolgreichen Rückgabewert zeigt. Weil modprobe ausgeführt wurde, meldet request_module eine erfolgreiche Ausführung, unabhängig davon, was in modprobe passiert ist.

Das darauffolgende Entfernen von master führt zu folgendem Ergebnis:

morgana.root# rmmod master
morgana.root# cat /proc/modules
slave                    248   0  (autoclean)
es1370                 34832   1

Das Modul slave bleibt im Kernel zurück, bis das nächste Aufräumen von Modulen erfolgt (was auf modernen Systemen auch nie der Fall sein könnte).

User Mode-Hilfsprogramme ausführen

Wie wir bereits gesehen haben, führt die Funktion request_module ein Programm im User Mode (also als separaten Prozeß, in einem unprivilegierten Prozessormodus und im User-Space) aus, um seine Aufgabe zu erledigen. In der 2.3-Entwicklungsserie machten die Kernel-Entwickler die Fähigkeit, ein User Mode-Hilfsprogramm auszuführen, auch für den Rest des Kernels verfügbar. Sollte Ihr Treiber ein User Mode-Programm ausführen müssen, um seine Aufgabe durchzuführen, geschieht das mit diesem Mechanismus. Weil dies ein Bestandteil der kmod-Implementation ist, schauen wir uns diese hier an. Bei Interesse sollten Sie auch kernel/kmod.c anschauen; das ist nicht besonders viel Code, zeigt aber nett, wie man User Mode-Hilfsprogramme verwendet.

Das Interface zum Ausführen von Hilfsprogrammen ist recht einfach. Ab Kernel 2.4.0-test9 gibt es eine Funktion namens call_usermodehelper; diese wird vor allem vom Hot Plug-Subsystem (für USB-Geräte und ähnliche) ausgeführt, um das Laden von Modulen sowie Konfigurationsaufgaben beim Anschließen eines neuen Geräts zu unterstützen. Der Prototyp lautet:


int call_usermodehelper(char *path, char **argv, char **envp);

Die Argumente werden Ihnen bekannt vorkommen: Sie sind der Name des ausführbaren Programms, die an dieses zu übergebenden Argumente (argv[0] ist konventionsgemäß der Name des Programms selbst) sowie die Werte eventueller Umgebungsvariablen. Beide Arrays müssen mit NULL-Werten terminiert werden, genau wie beim Systemaufruf execve. call_usermodehelper schläft, bis das Programm gestartet ist, und gibt dann den Status dieser Operation zurück.

In diesem Modus ausgeführte Hilfsprogramme werden übrigens als Kinder eines Kernel-Threads namens keventd ausgeführt. Eine wichtige Folge dieses Designs ist, daß es für Ihren Code keine Möglichkeit gibt festzustellen, ob das Hilfsprogramm seine Ausführung beendet hat oder welchen Rückgabewert es hatte. Die Ausführung von Hilfsprogrammen ist daher auch ein wenig Glückssache.

Wir möchten hier noch darauf hinweisen, daß es nur wenige gute Gründe für User Mode-Hilfsprogramme gibt. Meistens ist es besser, ein Skript zu schreiben, das während der Installation des Moduls ausgeführt wird und die gesamte Arbeit während des Ladens des Moduls erledigt, anstatt Aufrufe von User Mode-Programmen im Kernel-Code zu verdrahten. So etwas sollte nach Möglichkeit dem Benutzer überlassen werden.

Fußnoten

[1]

Die ALSA-Treiber finden Sie unter www.alsa-project.org.

[2]

Die meisten Distributionen führen depmod -a automatisch beim Booten aus, so daß Sie sich darüber keine Gedanken machen müssen, sofern Sie nicht Ihr neues Modul nach dem Rebooten installiert haben. Nähere Details finden Sie in der Dokumentation zu modprobe.

[3]

Auf älteren Systemen heißt diese Datei oft /etc/conf.modules. Dieser Name funktioniert immer noch, sollte aber nicht mehr verwendet werden.