7.7. Intermodul-Kommunikation

Funktionen und Daten eines Treibers lassen sich durchaus auch von einem anderen Treiber nutzen. Hinweise, wann dies sinnvoll und nützlich, oder aber auch zu vermeiden ist, finden Sie in Kapitel 9, Über das Schreiben eines guten, performanten Treibers. Vollkommen unproblematisch und transparent ist diese Nutzung, wenn der Treiber als Built-in-Treiber realisiert ist. Wie bei jedem normalen C-Programm, welches aus mehreren Modulen besteht, können hier Funktionen und Daten deklariert und danach verwendet werden bzw. es können Funktionen und Daten definiert und anschließend von anderen Kernelkomponenten aufgerufen werden.

Auch Modultreiber können Funktionen und Daten anderer Kernelkomponenten nach entsprechenden Deklarationen – die sich ja meist in Header-Dateien finden – verwenden. Nur wenn ein als Modul realisierter Treiber einem anderen als Modul realisierten Treiber Funktionen oder Daten zur Verfügung stellen möchte, muss er per Makro das zugehörige Symbol im Quellcode explizit exportieren (siehe Beispiele Modulübergreifende Symbole (Teil 1, Definition) und Modulübergreifende Symbole (Teil 2, Deklaration)).

Beispiel 7-16. Modulübergreifende Symbole (Teil 1, Definition)

...
static char textbuf[25] = "Hallo Treiber";
...
void ton_an( u16 tonwert )
{
    s8 save;
    ...
}
...
EXPORT_SYMBOL( textbuf );
EXPORT_SYMBOL_GPL( ton_an );

Beispiel 7-17. Modulübergreifende Symbole (Teil 2, Deklaration)

...
MODULE_LICENSE("GPL");

extern void ton_an( u16 tonwert );
extern char textbuf[];

static int __init buf_init(void)
{
    ton_an( 330 );
    printk( "stacked: \"%s\"\n", textbuf );
    return 0;
}
...

Über die Wahl des Makros, entweder EXPORT_SYMBOL oder EXPORT_SYMBOL_GPL, kann der Treiberentwickler entscheiden, ob er allen übrigen Treibern oder nur Treibern, die unter einer GPL oder BSD-Lizenz stehen, Funktionen oder/und Daten zur Verfügung stellt. Wollen Treiber, die unter einer proprietären Lizenz stehen, auf Funktionen zugreifen, die nur GPL oder BSD lizenzierten Treibern zur Verfügung gestellt werden, meldet der Betriebssystemkern beim Laden, dass Symbole nicht aufgelöst werden konnten:

Error inserting 'stacked.ko': -1 Unknown symbol in module

Auch ein Built-in-Treiber kann über das Makro EXPORT_SYMBOL_GPL den Aufruf von Funktionen und den Zugriff auf Daten einschränken.

In dem vorgestellten Schema ist die Reihenfolge, in der die Module geladen werden, entscheidend. Ein Modul lässt sich nur dann erfolgreich laden, wenn alle Funktionen und Daten, auf die es zugreifen möchte, im Kernel bereits definiert sind. In beinahe allen Fällen ist eine hierarchische Abhängigkeit zwischen den Modulen gegeben.

Doch auch der seltene Fall einer flachen Abhängigkeit, bei der ein vorher geladenes Modul auf Funktionen und Daten zugreift, die ein später geladenes Modul zur Verfügung stellt, wird von Linux unterstützt. Hierzu dienen die Funktionen bzw. Makros symbol_get, symbol_put und symbol_put_addr. Diese stehen allerdings nur GPL bzw. BSD lizenzierten Treibern zur Verfügung.

Diese Funktionen zum Exportieren und Importieren von Symbolen während der Laufzeit dürfen auch im Interrupt-Kontext aufgerufen werden. Ein Spinlock sorgt dafür, dass es zu keiner Race Condition kommt.

Auch bei dieser Variante exportiert ein Treiber seine Funktionen über das Makro EXPORT_SYMBOL_GPL. Der importierende Treiber ruft dann während der Laufzeit symbol_get auf. Wird der Treiber wieder entladen oder aber das Symbol einfach nicht mehr gebraucht, muss ein symbol_put erfolgen. In diesem Fall wird der usecount des Moduls, welches das Symbol zur Verfügung stellt, wieder dekrementiert. Wird das korrespondierende symbol_put vergessen, kann das Modul ansonsten nicht entladen werden.

Der Parameter zum Makro symbol_get ist der Name der Variablen selbst. Das Makro wandelt den übergebenen Namen zum geeigneten String um, über den die Adresse zum Symbol geholt werden kann. Der Rückgabewert des Makros bzw. der darunter liegenden Funktion ist die Adresse des Symbols. Folglich muss der Treiber einen entsprechenden Pointer zur Verfügung stellen (Beispiel Importieren eines Symbols).

Beispiel 7-18. Importieren eines Symbols

extern void ton_an( u16 tonwert );                         (1)
void (*ton_an_fnctn)(u16 var );                            (2)
...
    ton_an_fnctn = symbol_get( ton_an );                   (3)
...                                                        (4)
    if( ton_an_fnctn )                                     (5)
        symbol_put( ton_an );
(1)
Hier steht die Deklaration der Funktion (ton_an), die durch ein anderes Modul exportiert wird.
(2)
Die Adresse der zu importierenden Funktion soll in dieser Variablen abgespeichert werden.
(3)
Hier wird die Adresse der zu importierenden Funktion geholt. Falls die Adresse noch nicht bekannt ist, gibt symbol_get »NULL« zurück.
(4)
Die importierte Funktion kann jetzt verwendet werden.
(5)
Mit der Funktion wird dem Kernel mitgeteilt, dass die Funktion von diesem Modul nicht weiter benötigt wird. Damit kann das Modul, welches die Funktion exportiert, entladen werden, falls kein anderes Modul auf die Funktion oder ein anderes Symbol zugreift.

Auch das Makro symbol_put bekommt einfach den Namen der Variablen übergeben, der dann zu einem String gewandelt wird und der Funktion __symbol_put übergeben wird. Anstelle von symbol_put kann auch symbol_put_addr verwendet werden. Anstatt des Symbols als solchem wird hier die Adresse des Symbols übergeben:

    symbol_put_addr( ton_an_function ); // alternativ zu symbol_put( ton_an );

Die folgenden beiden Codefragmente zeigen die Nutzung der Funktionen symbol_get und symbol_put. Die wichtigsten Schritte, die der Programmierer durchführen muss, sind:

  1. Deklaration der zu importierenden Funktionen bzw. Daten.

  2. Definition von Variablen, die die Adressen der Funktionen oder Daten aufnehmen.

  3. Wenn gesichert ist, dass das Modul, welches Funktionen oder Daten zur Verfügung stellt, geladen ist, können die Adressen vom Kernel angefordert werden.

  4. Muss nicht mehr auf die Funktionen oder Daten zugegriffen werden, wird dem Kernel mitgeteilt, dass das Modul freigegeben werden kann.

Das Modul Export einer Funktion und einer Variablen (icm.c) stellt eine Funktion (ton_an) und eine Variable (texbuf) zur Verfügung. Das Modul Import einer Funktion und einer Variablen (stacked.c) greift auf die exportierte Funktion bzw. Variable zu. Am besten testet man die Funktionalität, indem man beide Module generiert und dann zunächst nur Modul stacked.ko lädt. Im Syslog muss daraufhin die Meldung erscheinen, dass die Symbole nicht aufgelöst werden konnten. Danach entfernt man das Modul stacked.ko wieder und lädt zunächst das Modul icm.ko und danach das Modul stacked.ko erneut. Jetzt konnten die Symbole aufgelöst werden. Da das Modul icm.ko auf dem Speaker-Treiber beruht (siehe Beispiel Zugriff auf Portadressen am Beispiel PC-Speaker) und das Modul stacked.ko auf selbigen jetzt zugreifen kann, ertönt ein Ton, der beim Entladen des Moduls stacked.ko wieder abgeschaltet wird.

Beispiel 7-19. Export einer Funktion und einer Variablen (icm.c)

#include <linux/module.h>
#include <linux/init.h>
#include <asm/io.h>

MODULE_LICENSE("GPL");

static char *textbuf = "Hallo Treiber";

void ton_an( u16 tonwert )
{
    s8 save;

    if( tonwert ) {
        tonwert = CLOCK_TICK_RATE/tonwert;
        printk("ton_an( 0x%x )\n", tonwert );
        outb( 0xb6, 0x43 );
        outb_p(tonwert & 0xff, 0x42);
        outb((tonwert>>8) & 0xff, 0x42);
        save = inb( 0x61 );
        outb( save | 0x03, 0x61 );
    } else {
        outb(inb_p(0x61) & 0xFC, 0x61);
    }
}

static int __init buf_init(void)
{
    printk(textbuf);
    return 0;
}

static void __exit buf_exit(void)
{
    outb(inb_p(0x61) & 0xFC, 0x61);
}

module_init( buf_init );
module_exit( buf_exit );
EXPORT_SYMBOL_GPL( ton_an );
EXPORT_SYMBOL_GPL( textbuf );

Beispiel 7-20. Import einer Funktion und einer Variablen (stacked.c)

#include <linux/module.h>
#include <linux/init.h>

MODULE_LICENSE("GPL");

extern void ton_an( u16 tonwert );
extern char *textbuf;

static void (*ton_an_function)(u16 var );
static char **textbufptr;

static int __init buf_init(void)
{
    ton_an_function = symbol_get( ton_an );
    if( ton_an_function )
        ton_an_function( 330 );
    else
        printk("can't find address of symbol \"ton_an\"\n");
    textbufptr = symbol_get( textbuf );
    if( textbufptr )
        printk("content of 0x%p: \"%s\"\n", textbufptr, *textbufptr );
    else
        printk("can't find address of symbol \"textbufptr\"\n");
    return 0;
}

static void __exit buf_exit(void)
{
    if( ton_an_function ) {
        ton_an_function( 0 );
        symbol_put( ton_an );
        //symbol_put_addr(ton_an_function); // alternativ zu symbol_put
    }
    if( textbufptr )
        symbol_put( textbuf );
}

module_init( buf_init );
module_exit( buf_exit );


Lizenz