Initialisierung und Beendigung

Wie bereits angedeutet, registriert init_module jede vom Modul bereitgestellte Fähigkeit. Mit “Fähigkeit” meinen wir neue Funktionalitäten, seien es ein ganzer Treiber oder eine neue Software-Abstraktion, die von einer Applikation verwendet werden kann.

Module können viele verschiedene Fähigkeiten registrieren; für jede gibt es eine spezielle Kernel-Funktion. Die an diese Funktionen übergebenen Argumente sind normalerweise ein Zeiger auf eine Datenstruktur, die die neue Fähigkeit beschreibt, und der Name der Fähigkeit. Die Datenstruktur enthält üblicherweise Zeiger auf die Modulfunktionen; auf diese Art und Weise werden Funktionen im Modul aufgerufen.

Die registrierbaren Elemente gehen über die Liste der im vorigen Kapitel genannten Gerätetypen hinaus. Dazu gehören serielle Schnittstellen, diverse Geräte, /proc-Dateien, ausführbare Domänen und Line Disciplines. Viele dieser Elemente gehören nicht direkt zu irgendeiner Hardware, sondern bleiben im Bereich der “Software-Abstraktionen”. Solche Elemente können registriert werden, weil sie sowieso in die Funktionalität des Treibers integriert sind (wie etwa /proc-Dateien oder Line Disciplines).

Es gibt noch weitere Fähigkeiten, die als Zusatzfunktionen für bestimmte Treiber registriert werden, aber deren Verwendung ist so spezifisch, daß es sich nicht lohnt, sie hier zu besprechen. Diese Fähigkeiten verwenden die oben in “” beschriebene Stapelungstechnik. Wenn Sie dieses Thema weiter verfolgen wollen, dann sollten Sie in den Kernel-Quellen nach EXPORT_SYMBOL suchen und sich die von den verschiedenen Treibern angebotenen Einsprungpunkte anschauen. Den meisten Registrierungsfunktionen ist register_ vorangestellt, man kann sie also auch durch Suchen nach register_ in /proc/ksyms finden.

Fehlerbehandlung in init_module

Wenn beim Registrieren von Fähigkeiten ein Fehler auftritt, müssen Sie alle bis dahin durchgeführten Registrierungsaktivitäten rückgängig machen. Ein Fehler kann beispielsweise auftreten, wenn im System nicht mehr genug Speicher vorhanden ist, um Platz für eine neue Datenstruktur zu allozieren, oder weil eine angeforderte Ressource bereits von einem anderen Treiber verwendet wird. Obwohl das eher unwahrscheinlich ist, kann es passieren, und guter Programmcode muß in der Lage sein, damit umgehen zu können.

Linux verwaltet die registrierten Fähigkeiten nicht pro Modul, so daß ein Modul alles selbst rückgängig machen muß, wenn init_module an irgendeiner Stelle fehlschlägt. Wenn Sie jemals nicht alle registrierten Fähigkeiten deregistrieren, gerät der Kernel in einen instabilen Zustand: Sie können Ihre Fähigkeiten nicht noch einmal durch erneutes Laden des Moduls registrieren, weil sie als besetzt (busy) gemeldet werden, und Sie können sie auch nicht deregistrieren, weil Sie dazu den gleichen Zeiger bräuchten, den Sie auch zum Registrieren verwendet haben, und normalerweise nicht in der Lage sind, die Adresse herauszubekommen. Aus solchen Situationen wieder herauszukommen, ist knifflig; oft ist ein Neustart des Systems die einzige Lösung, damit Sie eine neuere Version Ihres Moduls laden können.

Wir würden Ihnen raten, die Fehlerbehandlung mit der goto-Anweisung durchzuführen. Wir hassen es normalerweise, goto zu benutzen, aber in dieser Situation (und nur in dieser Situation) ist das nützlich. Im Kernel wird goto oft wie hier gezeigt verwendet, um Fehler zu behandeln.

Der folgende Beispielcode (mit fiktiven Funktionen zur Registrierung und Deregistrierung) verhält sich sowohl im Erfolgs- als auch im Mißerfolgsfall korrekt:



int init_module(void)
{
int err;

    /* Registrierung erwartet einen Zeiger und einen Namen */
    err = register_this(ptr1, "skull");
    if (err) goto fail_this;
    err = register_that(ptr2, "skull");
    if (err) goto fail_that;
    err = register_those(ptr3, "skull");
    if (err) goto fail_those;

    return 0; /* Erfolg */

    fail_those: unregister_that(ptr2, "skull");
    fail_that:  unregister_this(ptr1, "skull");
    fail_this:  return err; /* Fehler weitermelden */
}

Dieser Code versucht, drei (fiktive) Fähigkeiten zu registrieren. Die goto-Anweisung wird dazu verwendet, um im Fehlerfall nur die Fähigkeiten zu deregistrieren, die zuvor erfolgreich registriert worden sind.

Eine andere Möglichkeit, bei der keine haarigen goto-Anweisungen notwendig sind, besteht darin, mitzuverfolgen, was erfolgreich registriert worden ist, und cleanup_module bei jedem Fehler aufzurufen. Die Aufräumfunktion macht dann nur die Schritte rückgängig, die zuvor erfolgreich durchgeführt worden sind. Diese Alternative erfordert aber mehr Code und mehr CPU-Zeit, weswegen goto meistens doch noch das beste Werkzeug ist. Der Rückgabewert (err) von init_module ist ein Fehlercode. Im Linux-Kernel sind Fehlercodes negative Zahlen, die in <linux/errno.h> definiert sind. Wenn Sie eigene Fehlercodes erzeugen wollen, anstatt einfach nur weiterzugeben, was Sie von den anderen Funktionen bekommen, sollten Sie <linux/errno.h> einbinden, um symbolische Werte wie -ENODEV, -ENOMEM usw. verwenden zu können. Es ist guter Programmierstil, passende Fehlercodes zurückzugeben, denn Benutzerprogramme können diese mit perror oder ähnlichen Funktionen in für Menschen lesbare Strings umwandeln. (Es ist allerdings interessant, daß mehrere Versionen von modutils die Fehlermeldung “Device busy” zurückgegeben haben, unabhängig davon, was init_module als Fehler gemeldet hat; dieses Problem ist erst in jüngerer Zeit behoben worden.)

Offensichtlich muß cleanup_module alle von init_module vorgenommenen Registrierungen rückgängig machen; dies geschieht üblicherweise in umgekehrter Reihenfolge:



void cleanup_module(void)
{
    unregister_those(ptr3, "skull");
    unregister_that(ptr2, "skull");
    unregister_this(ptr1, "skull");
    return;
}

Wenn Ihre Initialisierung und Ihr Aufräumen komplexer ist als hier gezeigt, dann kann der goto-Ansatz schwierig zu verwalten sein, weil der gesamte Aufräum-Code in init_modules mit eingeschobenen Labels wiederholt werden muß. Deswegen ist manchmal ein anderes Code-Layout nützlich.

Hierbei minimieren Sie die Duplizierung von Code und halten alles sauber, indem Sie cleanup_module immer dann aus init_module aufrufen, wenn ein Fehler aufgetreten ist. Die Aufräumfunktion muß dann den Zustand jedes Elements überprüfen, bevor es seine Registrierung rückgängig macht. In der einfachsten Form sieht dieser Code so aus:


 struct something *item1;
 struct somethingelse *item2;
 int stuff_ok;

 void cleanup_module(void)
 {
  if (item1)
         release_thing(item1);
  if (item2)
   release_thing2(item2);
  if (stuff_ok)
   unregister_stuff();
  return;
 }

 int init_module(void)
 {
  int err = -ENOMEM;

  item1 = allocate_thing(arguments);
  item2 = allocate_thing2(arguments2);
  if (!item2 || !item2)
   goto fail;
  err = register_stuff(item1, item2);
  if (!err)
   stuff_ok = 1;
  else
   goto fail;
  return 0; /* success */

  fail:
  cleanup_module();
  return err;
 }

Wie hier gezeigt wird, brauchen Sie manchmal externe Schalter, um die erfolgreiche Abarbeitung eines Initialisierungsschrittes zu markieren. Das ist von der Semantik der jeweiligen Registrierungs- oder Allokationsfunktion abhängig. Egal, ob Sie solche Schalter benötigen, skaliert diese Art von Initialisierung besser und ist besser als die zuvor gezeigte Technik.

Verwendungszähler

Das System zählt, wie oft jedes Modul verwendet wird, um bestimmen zu können, wann ein Modul gefahrlos entfernt werden kann. Das System benötigt diese Information, denn ein Modul kann nicht entladen werden, wenn es in Benutzung ist. Beispielsweise können Sie nicht einen Dateisystemtyp entladen, während noch ein Dateisystem dieses Typs gemountet ist, und kein Zeichen-Gerät entfernen, während ein Prozeß ein solches noch benutzt; ansonsten bekommen Sie einen Segmentation Fault oder eine Kernel-Panic, wenn wilde Zeiger dereferenziert werden.

In modernen Kerneln kann das System automatisch den Verwendungszähler mit einem Mechanismus pflegen, den Sie im nächsten Kapitel kennenlernen werden. Manchmal müssen Sie das aber trotzdem noch manuell machen, etwa bei Code, der auch portabel auf ältere Kernel sein soll. Der Verwendungszähler wird von drei Makros verwaltet:

MOD_INC_USE_COUNT

Zählt den Zähler für das aktuelle Modul um eins hoch.

MOD_DEC_USE_COUNT

Zählt den Zähler um eins herunter.

MOD_IN_USE

Ergibt true, wenn der Zähler nicht null ist.

Diese Makros sind in <linux/module.h> definiert und arbeiten auf internen Datenstrukturen, auf die Programmierer nicht direkt zugreifen sollten. Übrigens sind die Interna der Modul-Verwaltung während der 2.1-Entwicklung sehr verändert und für 2.1.18 vollständig neu geschrieben worden, die Verwendung dieser Makros hat sich aber nicht geändert.

Beachten Sie, daß Sie nicht MOD_IN_USE in cleanup_module überprüfen müssen, da der Systemaufruf sys_delete_module (definiert in kernel/module.c) das automatisch macht, bevor er die Funktion cleanup_module aufruft.

Die korrekte Verwaltung des Modul-Verwendungszähler ist für die Stabilität des Systems absolut notwendig. Denken Sie daran, daß der Kernel zu jeder Zeit versuchen darf, ein Modul zu entladen. Es ist ein häufig gemachter Programmierfehler, eine Reihe von Operationen zu beginnen (etwa als Antwort auf eine open-Anforderung) und den Verwendungszähler am Ende heraufzusetzen. Wenn der Kernel das Modul unterwegs entlädt, ist das eine Garantie für einsetzendes Chaos. Um solche Probleme zu vermeiden, sollten Sie MOD_INC_USE_COUNT aufrufen, bevor Sie irgend etwas anderes im Modul machen.

Wenn der Verwendungszähler durcheinander kommt, können Sie ein Modul nicht mehr entladen. Das kann während der Entwicklung eines Moduls durchaus passieren, denken Sie also daran. Wenn beispielsweise ein Prozeß beendet wird, weil Ihr Treiber auf einen NULL-Zeiger zugegriffen hat, dann wird der Treiber das Gerät nicht schließen und der Verwendungszähler nicht auf 0 zurückgehen können. Eine mögliche Lösung für dieses Problem besteht darin, den Verwendungszähler während des Debuggens völlig abzustellen, indem Sie sowohl MOD_INC_USE_COUNT als auch MOD_DEC_USE_COUNT auf leere Operationen umdefinieren. Sie können auch noch eine andere Methode verwenden, um den Zähler auf 0 zu zwingen (wie das geht, erfahren Sie in “the Section called Das Argument von ioctl benutzen in Kapitel 5” in Kapitel 5). In einem fertigen Modul sollten aber nie diese Sicherheitsüberprüfungen abgeschaltet werden. Beim Debuggen helfen solche brachialen Methoden mitunter, Zeit zu sparen, und sind daher akzeptabel.

Der aktuelle Wert des Verwendungszählers steht im dritten Feld jedes Eintrags in /proc/modules. Diese Datei zeigt, welche Module gerade geladen sind, und enthält einen Eintrag pro Modul. Die einzelnen Felder sind der Name des Moduls, die Größe des Speichers in Byte, den das Modul verwendet, und der aktuelle Wert des Verwendungszählers. Eine typische /proc/modules- Datei kann etwa so aussehen:

parport_pc    7604 1 (autoclean)
lp      4800 0 (unused)
parport     8084 1 [parport_probe parport_pc lp]
lockd     33256 1 (autoclean)
sunrpc     56612 1 (autoclean) [lockd]
ds      6252 1
i82365     22304 1
pcmcia_core   41280 0 [ds i82365]

Hier sind mehrere Module zu sehen. Unter anderem sind die Parallel-Port-Module gestapelt geladen worden, wie das oben beschrieben wurde. Der Hinweis (autoclean) gibt an, daß diese Module von kmod oder kerneld (siehe Kapitel 11) verwaltet werden; (unused) bedeutet eben das: Das Modul wird nicht benutzt. Es gibt auch noch andere Markierungen. In Linux 2.0 wurde das zweite Feld (die Größe) in Seiten und nicht in Bytes angegeben (auf den meisten Plattformen sind 4 KByte eine Seite).

Entladen

Ein Modul wird mit dem Befehl rmmod entladen. Das Entladen ist deutlich einfacher als das Laden, weil nicht gelinkt werden muß. Der Befehl ruft den Systemaufruf delete_module auf, der wiederum cleanup_module im Modul aufruft, wenn der Verwendungszähler auf null gefallen ist, oder gibt ansonsten eine Fehlermeldung zurück.

Die Implementation von cleanup_modules ist dafür zuständig, jedes Element zu deregistrieren, das das Modul vorher registriert hat. Nur die exportierten Symbole werden automatisch entfernt.

Explizite Initialisierungs- und Aufräumfunktionen

Wie wir bereits gesehen haben, ruft der Kernel init_module auf, um ein frisch geladenes Modul zu initialisieren, sowie cleanup_module unmittelbar vor dem Entfernen des Moduls. In modernen Kerneln haben diese Funktionen aber oft andere Namen. Ab Kernel 2.3.13 gibt es eine Möglichkeit, die Initialisierungs- und Aufräumroutinen explizit zu benennen; dies ist auch der bevorzugte Programmierstil.

Schauen wir uns ein Beispiel an. Wenn Ihr Modul seine Initialisierungsroutine my_init (anstelle von init_module) und die Aufräumroutine my_cleanup nennt, dann würden Sie dies mit den folgenden zwei Zeilen (normalerweise am Ende der Quelldatei) angeben:


 module_init(my_init);
 module_exit(my_cleanup);

Beachten Sie, daß Ihr Code <linux/init.h> einbinden muß, um module_init und module_exit verwenden zu können.

Der Vorteil dabei besteht darin, daß jede Initialisierungs- und Aufräumfunktion im Kernel einen eigenen Namen haben kann, was beim Debuggen hilft. Diese Funktionen machen auch denjenigen das Leben leichter, die Treiber schreiben, die entweder als Modul arbeiten oder direkt in den Kernel eingebaut werden. Die Verwendung von module_init und module_exit ist aber nicht notwendig, wenn Ihre Initialisierungs- und Aufräumfunktionen die alten Namen benutzen. Das einzige, was diese beiden Makros bei Modulen machen, ist init_module und cleanup_module als neue Namen für die angegebenen Funktionen zu definieren.

Wenn Sie sich durch die Kernel-Quellen graben (ab Version 2.2), dann werden Sie wahrscheinlich eine etwas andere Form der Deklaration der Modul-Initialisierungs- und Aufräumfunktionen vorfinden, die etwa so aussieht:


 static int _ _init my_init(void)
 {
  ....
 }

 static void _ _exit my_cleanup(void)
 {
  ....
 }

Das Attribut _ _init sorgt — wenn es wie hier gezeigt verwendet wird — dafür, daß die Initialisierungsfunktion verworfen und der Speicher zurückgewonnen wird, nachdem die Initialisierung abgeschlossen ist. Das funktioniert aber nur bei eingebauten Treibern und hat keine Wirkung auf Module. _ _exit sorgt als Gegenstück dafür, daß die angegebene Funktion nicht mit aufgenommen wird, wenn der Treiber nicht als Modul gebaut wird; auch dies hat in Modulen keine Wirkung.

Die Verwendung von _ _init (und _ _initdata bei Datenelementen) kann die Menge des vom Kernel verwendeten Speichers reduzieren. Es ist kein Problem, die Initialisierungsfunktionen von Modulen mit _ _init zu markieren; es nützt aber derzeit auch nichts. Die Verwaltung der Initialisierungsabschnitte ist für Module noch nicht implementiert; möglicherweise kommt das aber in Zukunft noch.