Kommunikation zwischen Modulen

Sehr spät in der Pre-2.4.0-Entwicklungsserie haben die Kernel-Entwickler noch eine neue Schnittstelle hinzugefügt, die eine eingeschränkte Kommunikation zwischen Modulen ermöglicht. Diese Schnittstelle erlaubt es Modulen, Strings zu registrieren, die auf interessante Daten verweisen und dann von anderen Modulen abgefragt werden können. Wir schauen uns diese Schnittstelle anhand einer Variante unserer Module master und slave kurz an.

Wir verwenden das gleiche master-Modul, führen aber ein neues slave-Modul namens inter ein. Alles, was inter macht, ist einen String und eine Funktion unter dem Namen ime_string verfügbar zu machen (ime steht dabei für "intermodule example"). Das gesamte Modul sieht folgendermaßen aus:


static char *string = "inter says 'Hello World'";

void ime_function(const char *who)
{
    printk(KERN_INFO "inter: ime_function called by %s\n", who);
}

int ime_init(void)
{
    inter_module_register("ime_string", THIS_MODULE, string);
    inter_module_register("ime_function", THIS_MODULE, ime_function);
    return 0;
}

void ime_cleanup(void)
{
    inter_module_unregister("ime_string");
    inter_module_unregister("ime_function");
}

Dieser Code verwendet die Funktion inter_module_register, die folgenden Prototyp hat:


void inter_module_register(const char *string, struct module *module,
                           const void *data);

string ist der String, den andere Module verwenden, um die Daten zu finden; module ist ein Zeiger auf das Modul, dem die Daten gehören; dies wird fast immer THIS_MODULE sein. data ist schließlich ein Zeiger auf die Daten, die bereitgestellt werden sollen. Beachten Sie die Verwendung eines const-Zeigers für die Daten; es wird hier angenommen, daß er nur zum Lesen exportiert wird. inter_module_register beschwert sich (über printk), wenn der angegebene string bereits registriert ist.

Wenn die Daten nicht mehr gemeinsam genutzt werden müssen, sollte das Modul zum Aufräumen inter_module_unregister aufrufen:


void inter_module_unregister(const char *string);

Es werden zwei Funktionen exportiert, mit denen auf via inter_module_register bereitgestellte Daten zugegriffen werden kann:

const void *inter_module_get(const char *string);

Diese Funktion sucht den angegebenen string und gibt den zugehörigen Datenzeiger zurück. Wenn der String nicht registriert worden ist, wird NULL zurückgegeben.

const void *inter_module_get_request(const char *string, const char *module);

Diese Funktion verhält sich wie inter_module_get, ruft aber zusätzlich request_module mit dem angegebenen module-Namen auf und versucht es dann noch einmal.

Beide Funktionen inkrementieren darüber hinaus auch den Verwendungszähler des Moduls, das die Daten registriert hat. Ein mit inter_module_get oder inter_module_get_request geholter Zeiger bleibt also gültig, bis der explizit freigegeben wird. Zumindest wird das Modul, das den Zeiger erzeugt hat, nicht entladen; es kann natürlich immer noch selbst etwas machen, was den Zeiger ungültig werden läßt.

Wenn Sie den Zeiger nicht mehr benötigen, müssen Sie ihn freigeben, damit der Verwendungszähler des anderen Moduls korrekt dekrementiert wird. Ein einfacher Aufruf von


void inter_module_put(const char *string);

gibt den Zeiger frei, der danach nicht mehr verwendet werden sollte.

In unserem Beispiel-Modul master rufen wir inter_module_get_request auf, damit das Modul inter geladen wird, und holen uns die zwei Zeiger. Der String wird einfach ausgegeben, und der Funktionszeiger wird dazu verwendet, einen Funktionsaufruf von master nach inter auszuführen. Der zusätzliche Code in master sieht folgendermaßen aus:


static const char *ime_string = NULL;
static void master_test_inter();

void master_test_inter()
{
    void (*ime_func)();
    ime_string = inter_module_get_request("ime_string", "inter");
    if (ime_string)
        printk(KERN_INFO "master: got ime_string '%s'\n", ime_string);
    else
        printk(KERN_INFO "master: inter_module_get failed");
    ime_func = inter_module_get("ime_function");
    if (ime_func) {
        (*ime_func)("master");
        inter_module_put("ime_function");
    }
}

void master_cleanup_module(void)
{
    if (ime_string)
        inter_module_put("ime_string");
}

Beachten Sie, daß die Aufrufe von inter_module_put bis zum Abräumen des Moduls zurückgestellt werden. Damit bleibt der Verwendungszähler von inter (mindestens) 1, bis master entladen wird.

> > Es gibt einige weitere wissenswerte Details zur Verwendung der Funktionen zur Kommunikation zwischen Modulen. Zunächst einmal stehen diese auch in Kerneln zur Verfügung, die nicht für ladbare Module konfiguriert worden sind, so daß kein Haufen von #ifdef-Zeilen notwendig ist, um diesen Fall abzufangen. Der von diesen Funktionen aufgespannte Namensraum ist global; die Namen sollten also sorgfältig gewählt werden, um Konflikte zu vermeiden. Schließlich werden gemeinsam genutzte Daten in einer einfachen verketteten Liste gespeichert; die Performance wird in Mitleidenschaft gezogen, wenn viele Abfragen gestartet oder viele Strings gespeichert werden. Diese Funktion ist für eine gelegentliche Verwendung gedacht, nicht als allgemeines Verzeichnis-Subsystem.