Vorwort

Da ich z. Z. noch immer an meiner eigenen Distrubtion basierend auf einem LFS rumschreibe, bin ich ab und zu auch gezwungen, meine eigenen Kernelmodule zu erstellen. In diesem Tutorial werde ich auf einige Besonderheiten in Bezug auf die Erstellung von Kernelmodulen eingehen. Fuer Anfaenger ist dieses Tutorial weniger geeignet, da ich nicht darauf eingehen werde wie der Kernel aufgebaut ist, was Module fuer einen Sinn haben und wie die Syntax eines Makefile's aussieht; das setze ich als Grundlage vorraus.
Solltet ihr einen Fehler finden, dann schickt mir bitte eine Mail (strcat@gmx.net) und ich werde den Fehler beheben.

Hallo, Welt!

Wie soll es auch anders sein?! Hier kommt das einfachste aller Kernelmodule; naemlich "Hallo, Welt!".

Note
Normalerweise wird der Quelltext ausfuehrlich kommentiert; der Uebersichtlichkeit halber, werde ich die einzelnen Abschnitte nachfolgend ausfuehrlich erklaeren.
/*
 * hallo.c - Hallo, Welt! als Kernelmodule
 */

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

/*
 * hallo_init - der Entry-Point des Moduls
 */
static int hallo_init(void)
{
        printk(KERN_ALERT "hardware stress fractures. Aiee\n");
        return 0;
}

/*
 * hallo_exit - die exit-function
 */
static void hallo_exit(void)
{
        printk(KERN_ALERT "You are screwed!\n");
}

module_init(hallo_init);
module_exit(hallo_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anyone <anyone@foo.invalid>");
MODULE_DESCRIPTION("Ein einfaches Beispiel");

Das ist die einfachste Variante eines Kernelmoduls. hallo_init() wird als Entry-Point des Moduls ueber module_init() registriert. Sie wird durch den Kernel ausgefuehrt, wenn das Modul geladen wird. Der Call zu module_init() ist eigentlih kein Function-Call, sondern ein Makro, dass seinen einzigen Parameter als Initialisierungsfunktion fuer das Modul zuweist. Alle init-Funktionen muessen folgende Form haben:

int my_init(void);

Da init-Funktionen ueblicherweise nicht durch externen Code aufgerufen werden, muessen sie die Funktion nicht exportieren und sie kann deswegen als static markiert werden.
init-Funktionen liefern einen int zurueck. Wenn die Initialisierung (oder was auch immer die init-Funktion durchfuehren will) erfolgreich war, gibt die Funktion null zurueck (im Fehlerfall nicht-null).
Diese init-Funktion gibt nur eine Nachricht aus und null zurueck. init-Funktionen in richtigen Modulen registrieren Ressourcen, belegen Datenstrukturen, .. Auch wenn diese Datei statisch in der Kernel-Image kompiliert werden wuerde, wuerde die init-Funktion erhalten bleiben und beim Booten des Kernels ausgefuehrt werden.

Die hallo_exit() - Funktion ist als Exit-Point des Moduls ueber module_init() registriert. Der Kernel ruft hallo_exit() auf, wenn das Modul aus dem Speicher entfernt wird. Exit-Funktionen raeumen die benutzten Ressourcen auf und stellen sicher, dass die Hardware sich in einem konsistenten Zustand befindet bevor sie zurueckkehren. Nach der Rueckkehr der exit-Funktion wird das Modul entladen.
Exit-Funktionen mussen folgende Form haben:

void my_exit(void);

Wenn diese Datei in der statische Kernel-Image kompiliert werden wuerde, wuerde die exit-Funktion nicht mit eingeschlossen und wuerde auch nie aufgerufen werden (da, weil es kein Modul mehr gibt, der COde auch nicht aus dem Speicher entfernt werden wuerde).
MODULE_AUTHOR und MODULE_DESCRIPTION dienen lediglich informativen Zwecken; anders jedoch das MODULE_LICENSE()-Makro. Das ist nicht nur ein "Dann geben wir halt die Lizenz an", sondern wird vom Kernel auch abgearbeitet! In diesem Makro steht die Copyright-Lizenz fuer diese Date; wenn ein "Nicht-GPL-Modul" in den Speicher geladen wird, setzt der Kernel ein Tainted-Flag. Dieses Flag ist fuer Informationszwecke gedacht, aber viele Kernel-Entwickler kuemmern sich nicht mehr so intensiv um einen Bug-Report wenn das Tainted-Flag in dem Oops gesetzt ist. Ausserdem koennen "Nicht-GPL-Module" keine "GPL-only-Symbole" aufrufen (dazu aber spaeter mehr).

Das Erzeugen von Modulen

Module zu erzeugen ist mit Linux 2.6.x dank dem kbuild-System sehr einfach geworden. Beim Bauen von Module muss man sich zuerst entscheiden, wo der Sourceocde des Moduls ausgelagert werden soll. Man kann den Modul-Source dem Kernel-Proper-Source hinzufuegen (entweder als Patch oder wenn man irgendwann mal den Code per Merge zum offiziellen Tree hinzufuegt). Da das bei unserem Modul aber eher weniger geschehen wird, kann man den Source auch ausserhalb des Source-Trees verwalten und kompilieren.

Innerhalb des Source-Trees

Im Idealfall ist das Modul ein offizieller Teil von Linux und lebt somit im Kernel-Source-Tree; das Project in den Kernel-Proper zu bekommen, erfordert am Anfang mehr Aufwand, ist aber normalerweise die Loesung, die man bevorzugen sollte.

Zuerst muss man sich entscheiden, wo im Kernel-Source-Tree das Modul exisiteren wird. Treiber werden in Unterverzeichnissen des drivers/ - Directorys im root des Kernel-Source-Trees gespeichert. Das ist intern weiter nach Klasse, Typ und evtl. spezifischen Treibern unterteilt. Character-Devices befinden sich in drivers/char/, Block-Devices liegen in drivers/block/ und USB-Devices sind in drivers/usb/ zu finden. Diese Regel muss aber nicht zwingend zutreffend sein, da einige USB-Devices Character-Devices sind.

Nehmen wir mal an, unser glorreiches "Hallo, Welt!" - Modul ist ein Character-Device und wir wollen es unter drivers/char/ speichern. In diesem Verzeichnis sind etliche C-Source-Dateien und eine Hand voll anderer Verzeichnisse. Treiber, die nur aus einem oder zwei Source-Files bestehen, koennten diese einfach an dieser Stelle speichern. Treiber mit mehreren Source-Files und weiteren Zusaetzen sollten ein neues Unterverzeichnis anlgen.
Als Beispiel dazu erzeugen wir ein Unterverzeichnis mit dem Namen world in drivers/char/.

Wenn das erledigt ist, muss man noch eine Zeile zu dem Makefile in drivers/char/ hinzufuegen; dazu editieren wir die drivers/char/Makefile und fuegen folgende Zeile hinzu:

obj-m += world/

Damit wird das Build-System veranlasst, auch in das world/-Verzeichnis zu welchseln, wenn die Module kompiliert werden. Wahrscheinlicher ist aber, dass das Kompilieren des Treibers durch eine spezifische Konfigurationsoption ausgeloest wird; vielleicht CONFIG_HALLO. In diesem Fall sollte man folgende Zeile im Makefile anfuegen:

obj-${CONFIG_HALLO} += world/

Zu guter letzt wird in drivers/char/world/ ein neues Makefile mit folgender Zeile erstellt:

obj-m += hallo.o

Das Build-System begibt sich jetzt in world/ und erzeugt das Modul hallo.ko aus hallo.c.
Der Treiber wird vermutlich durch eine Konfigurationsoption ausgeloest; von daher kann man folgendes benutzen:

obj-${CONFIG_HALLO} += hallo.o

Wenn der glorreiche "Hallo, Welt!" - Treiber komplizierter werden und so stark anwachsen sollte, dass mehr als ein Source-File benoetigt wird, stellt das kein Problem dar. Wenn zum Beispiel zu dem hallo.c noch ein byebye.c hinzukommen sollte, wird einfach folgendes in der Makefile eingetragen:

obj-${CONFIG_HALLO} += hallo.o +
hallo-objs := hallo.o bybye.o

Anschliessend werden hallo.c und byebye.c in hallo.ko uebersetzt und gelinkt.
Wenn man zudem noch eigene Compile-Flags waehrend des Build-Prozesses der Datei an den Kompiler uebergeben werden muessen, reicht es, wenn man folgendes in das Makefile eintraegt:

EXTRA_FLAGS += -DDEBUG

Wenn man sich dazu entschliesst, die Source-Datei(en) in drivers/char/ zu speichern, aber kein neues Unterverzeichnis anlegen will, muss man die oben genannten Zeile nicht in die drivers/char/world/Makefile, sondern logischerweise in die drivers/char/Makefile eintragen.
Um zu kompilieren, startet man den Kernel-Build-Prozess wie immer; wenn die Uebersetzung des Moduls von einer Konfigurationsoption abhaengen sollte (wie CONFIG_HALLO), sollte man nur sicherstelltn, dass diese aktiviert ist, bevor man den Build startet.

Externes Erzeugen

Wenn man das Modul ausserhalb des Kernel-Source-Trees zu bauen und zu verwalten, muss man mit folgendem Eintrag ein Makefile im eigenen Source-Tree erzeugen:

obj-m := hallo.o

Das kompiliert hallo.c in hallo.ko; wenn die Sourcen ueber mehrere Dateien verteilt sein sollten, dann reichen folgende zwei Zeilen:

obj-m := hallo.o
hallo-objs := hallo.o bybye.o

Damit wird hallo.c und bybye.c in hallo.ko kompiliert.
Der grosse Unterschied beim externen Uebersetzen ist der Build-Prozess. Da sich jetzt unser Modul ausserhalb des Kernel-Trees befindet, muss man make(1) erklaeren, wie es die Kernel-Source-Files und das Basis-Makefile findet. Das ist aber leichter als es sich anhoert; dazu reicht es naemlich folgendes auszufuehren: make -C /my/kernel/tree SUBDIRS=$PWD modules

Hierbei ist /my/kernel/tree der Ort, an dem man den konfigurierten Kernel-Source-Tree gespeichert hat. Die Arbeitskopie des Kernel-Source-Tree sollte man nicht in /usr/src/linux, sondern an einer anderen Stelle speichern (z. B. im eigenem ${HOME}).

Installation von Modulen

Kompilierte Module werden in /lib/modules/<version>/kernel/ installiert. Bei einem Kernel mit der Version 2.6.15 wuerde das uebersetzte Hallo-Modul folglich in /lib/modules/2.6.15/kernel/drivers/char/hallo.ko landen, wenn es direkt in drivers/char/ gelagert wurde.
Das folgende Build-Kommando wird benutzt, um die kompilierten Module an der richtigen Stelle zu installieren: make modules_install

Erzeugen der Module Dependencies (Modul-Abhaengigkeiten)

Die Linux-Modul-Utils koennen mit Abhaengigkeiten umgehen. Das bedeutet folgendes:
Wenn das Modul foo vom Modul bar abhaengt, wird, wenn man das Modul foo laedt, das Modul bar automatisch geladen. Diese Dependency-Information muss erzeugt werden. Die meisten Linux-Distributionen erzeugen das Mapping automatisch und aktualisieren es bei jedem Bootvorgang. Um die Modul-Dependency-Information zu erzeugen, laesst man als root einfach depmod ablaufen.
Um ein schnelles Update durchzufuehren, also nur die Informationen fuer Module zu erzeugen, die neuer als die vorhandenen Dependency-Informationen sind, kann man depmod -A ausfuehren. Die Modul-Dependency-Information wird in der Datei /lib/modules/<version>/modules.dep gespeichert.

Module laden

Der einfachste Weg um ein Modul zu laden, ist das Programm insmod(1). Dieses Utility ist sehr einfach. Es bittet den Kernel einfach, das angegebene Modul zu laden. insmod fuehrt keine Aufloesung von Abhaengigkeiten oder weitergehende Ueberpruefungen auf Fehler durch. Die Benutzung von insmod ist simpel; dazu muss man lediglich als root das Kommando insmod module ausfuehren, wobei module der Name des Moduls ist, dass man laden will. In unserem Fall also insmod hallo`.
Genauso einfach wie es ist, ein Modul zu laden, kann man es auch wieder entfernen. Dazu existiert das Kommando rmmod(1); als root rmmod hallo ausgefuehrt, wuerde das Modul hallo wieder entfernen.

Diese beiden Utilites sind jedoch nur Notloesungen; man sollte im Umgang auf Module eher auf das Programm modprobe zurueckgreifen. Das bietet die Aufloesung von Abhaengigkeiten, intelligente Ueberpruefung auf Fehler und Reporting und weitere Features und Optionen an. Um ein Module mit modprobe in den Kernel zu laden, ruft man als root modprobe module [ module parameter ] auf, wobei module der Name des zu ladenden Moduls ist. Falls weitere Argumente auf den Namen folgen, werden diese als Parameter an das Modul beim laden uebergeben (spaeter gehe ich noch genauer auf modprobe ein).

modprobe versucht nicht nur das angegebene Modul zu laden, sondern auch alle Module, von denen es abhaengig ist. Aus diesem Grund sollte modprobe das bevorzugte Programm sein, wenn man Kernel-Module laden will. modprobe kann auch benutzt werden, um Module wieder aus dem Kernel zu entfernen. Hierbei muss man als root modprobe -r module(s) eingeben, wobei modules ein oder mehrere Module bezeichnet, die entfernt werden sollen. Im Gegensatz zu rmmod entfernt 'modprobe; auch alle Module, von denen das angegebene Modul abhaengt, wenn diese sonst nicht benutzt werden.

Verwaltung der Konfigurationsoptionen

Im Abschnitt Das Erzeugen von Modulen wurde "nur" erklaert, wie man das Modul kompiliert wird, wenn die CONFIG_HALLO-Konfigurationsoption gesetzt worden ist und genau diese Konfigurationsoptionen sehen wir uns jetzt mal genauer an (dazu bleiben wir bei dem Hallo-Modul.

Danke dem neuen kbuild-System im 2.6er Kernel ist das hinzufuegen von neuen Konfigurationsoptionen mehr als einfach. Dazu muss man lediglich einen Eintrag zur Datei Kconfig hinzufuegen, die fuer das Ueberleben im Kernel-Source-Tree verantwortlich ist. Fuer Treiber ist das normalerweise genau das Verzeichnis, in dem sich der Source befindet. Wenn das Hallo-Modul in drivers/char/ zu finden ist, dann waere das die Datei drivers/char/Kconfig.
Wenn ein neues Subdirectory erzeugt wurde und dort ein neues Kconfig-File benoetigt wird, kann man es von einem bestehenden Kconfig-File aus "sourcen". Das geschieht mit einem Eintrag

source "drivers/char/world/Kconfig"

in einer bereits vorhandenen Kconfig-Datei wie z. B. drivers/char/Kconfig. Die Eintraege in der Kconfig sind sehr einfach strukturiert. Fuer unser Modul wuerde er in etwa so aussehen:

menu "ATA/ATAPI/MFM/RLL support"

config HALLO
       tristate "Hallo, Welt! support"
       default n
       help
         If you say Y here, support for Hallo, Welt will be compiled
         into the kernel and accessible via device node. Yout can also
         say M here and the driver will be built as a module namend
         hallo.ko.

         If unsure, say N

Die erste Zeile definiert, unter welchem (Unter)Abschnitt unser Treiber zu finden ist (in diesem Fall unter Device Drivers -> ATA/ATAPI/MFM/RLL support).
In der zweiten Zeile wird angegeben, fuer welche Konfigurationsoption der Eintrag steht. Hierbei muss man beachten, dass das Praefix CONFIG_ angenommen und nicht geschrieben wird!
Die dritte Zeile deklariert das die Option ein Tristate ist. Das bedeutet, dass sie in den Kernel eingebaut werden (Y), als Modul erzeugt werden (M) oder gar nicht kompiliert werden (N) kann. Um die Option Modul (M) zu deaktivieren - weil Hallo ein Feature und kein Modul darstellt - kann mandie Direktive bool anstelle von tristate verwenden. Der Text in den Anfuehrungszeichen ist der Name dieser Option in den verschiedenen Konfigurations-Utilities (menuconfig, xconfig, ..).
Die vierte Zeile spezifiziert die Defaulteinstellung: aus.
Die help-Direktive gibt an, dass der verbleibende Text der Text ist, der auf Anforderung der Hilfe ausgegeben wird. Die verschiedenen Konfigurations-Utilities koennen diesen Text auf Anforderung darstellen. Da dieser Text fuer Benutzer und Entwickler gedacht ist, die ihre eigenen Kernel erzeugen, kann er kurz und buendig sein.

Es gibt auch noch andere Optionen. Die depends-Direktive spezifiziert Optionen, die gesetzt sein muessen, bevor diese Option gesetzt werden kann. Wenn die Abhaengigkeiten nicht erfuellt sind, ist diese Option deaktiviert. Wenn man beispielsweise die Direktive

depends on BYEBYE

zu dem Config-Entry hinzufuegt, dann kann das Modul nicht aktiviert werden, bis das CONFIG_BYEBYE - Modul aktiviert ist.

Die select-Direktive funktioniert wie depends, schaltet aber die angegebene Option automatisch ein, wenn unsere Option angewaehlt wird. Das ist zwar einerseits komfortabel, sollte aber nicht so haeufig wie depends genutzt werden, da sie andere Konfigurationsoptionen automatisch aktiviert. Die Benutzung ist so simpel wie die von depends

select BYEBYE

Die Konfigurationsoption CONFIG_BYEBYE wird automatisch aktiviert, wenn CONFIG_HALLO aktiviert wird.
Sowohl fuer select als auch fuer depends kann man mehrere Optionen ueber && anfordern. Bei depends kann man auch angeben, dass eine Option nicht aktiviert sein darf, indem man ein Ausrufezeichen voranstellt. Zum Beispiel gibt

depends on FOO_BAR && !BYEBYE

an, dass der Treiber von CONFIG_FOO_BAR abhaengt und diese also gesetzt sein muessen und dass CONFIG_BYEBYE nicht gesetzt sein darf.

Die tristate- und bool - Optionen koennen von der Direktive if gefolgt werden, die die gesamte Option von einer anderen Konfigurationsoption abhaengig macht. Wenn die Bedingung nicht erfuellt ist, wird die Konfigurationsoption nicht nur deaktiviert, sondern taucht erst gar nicht in den Konfigurations-Utilities auf. Zum Beispiel weist die Direktive

bool "Extreme Boredom" if HOLIDAY

das Konfigurationssystem an, diese Option nur anzuzeigen, wenn CONFIG_HOLIDAY gesetzt ist. Augenscheinlich ist der Extreme Boredom nur verfuegbar, wenn CONFIG_HOLIDAY gesetzt ist.

Die if-Direktive kann auch nach der default-Direktive verwandt werden. Sie forciert den Default nur, wenn die Bedingung erfuellt ist.
Das Konfigurationssystem exportiert mehrere Meta-Optionen, um die Konfiguration zu erleichtern. Die Option CONFIG_EMBEDDED wird nur aktiviert, wenn der Benutzer angegeben hat, dass er die Optionen sehen will, die es erlauben, Key-Features auszuschalten (um auf einem Ebedded-System kostbaren Speicher zu sparen). Die Option CONFIG_BROKEN_ON_SMP wird benutzt, um anzugeben, dass ein Treiber nicht SMP-save ist. Diese Option wird normalerweise nicht gesetzt, damit der Benutzer explizit bestaetigen muss, dass er ueber das Problem Bescheid weiss. Neue Treiber sollten dieses Flag natuerlich nicht benutzen. Schliesslich wird die Option CONFIG_EXPERIMENTAL dafuer genutzt, Optionen zu markieren, die experimentell sind. Die Defaulteinstellung ist aus; auch wiederum nur deshalb, um den Benutzer dazu zu zwingen, explizit zu bestaetigen, dass er weiss, dass er durch Aktivieren dieses Treiber ein Risiko eingeht.

Modul-Parameter

Der Linux-Kernel stellt ein einfaches Framework zur Verfuegung, dass es Treibern erlaubt, Parameter zu deklarieren, die vom Benutzer entweder beim Booten oder beim Laden eines Moduls angegeben werden koennen und dann diese Parameter in den Treibern als globale Variablen zu nutzen. Diese Modul-Parameter werden auch in sysfs angezeigt. Daher ist das Erzeugen und Verwalten der Module-Parameter relativ einfach.
Ein Modul-Parameter wird ueber das Macro module_param() definiert.

module_param(name, type, perm);

Dabei ist name sowohl der Name des Parameters, der fuer den Benutzer sichtbar ist, als auch der Name der Variablen, die den Parameter in unserem Modul speichert.
Das type-Argument enthaelt den Datentyp des Parameters; das kann ein byte, short, ushort, int, unit, long, ulong'ΒΈ 'charp, bool oder invbool sein. Diese Typen sind entsprechend ein Byte, ein Short Integer, ein Unsigned Short Integer, ein Integer, ein Unsigned Integer, ein Long Integer, ein Unsigned Long Integer, ein Pointer auf ein char, ein Boolean und ein Boolean, dessen Wert invers zur Angabe des Benutzers ist.
Das byte-Type wird in einem einzelenen char gespeichert und die Boolean-Typen werden in Variablen vom Typ int gespeichert. Die uebrigen Variablen werden in den entsprechenden C-Typen abgespeichert.
Schliesslich gibt das perm-Argument die Zugriffsberechtigungen des korrespondierenden Files in sysfs an. Die Zugriffsberechtigungen (Permissions) koennen in dem ueblichen oktalen Format angegeben werden, beispielsweise 0644 (der Eigentuemer kann lesen und schreiben, die Gruppe kann lesen und jeder andere (others) kann ebenfalls lesen), oder durch (ORing) der S_Ifoo-Defines, zum Beispiel S_IRUGO | S_IWUSR (jeder kan lesen, der User kan auch schreiben). Ein Wert von null deaktiviert den sysfs-Eintrag vollstaendig.

Das Makro deklariert nicht die Variable fuer uns; dass muss man selbst erledigen, bevor das Makro aufgerufen wird. Von daher siehe eine typische Benutzung in etwa wie folgt aus

/* module paraeter controlling */
static int allow_foobar = 1;            /* Default to on  */
module_param(allow_foobar, bool, 0644); /* a Boolean Type */

Das waere im aeussersten Geltungsbereich (scope) in unserem Modul-Source-File. Ergo: allow_foobar ist global.

Es ist moeglich, dass der interne Name der Variable sich von dem Namen des externen Parameters unterscheidet. Das kann man wie folgt mittels module_param_named() erreicht werden:

module_param_named(name, variable, type, perm);

Dabei ist name der Name des extern sichtbaren Parameters und variable der Name der internen globalen Variablen; zum Beispiel

static unsigned int max_text = DEFAULT_MAX_LINE_TEST;
module_param_named(maximum_line_test, max_test, int, 0);

Normalerweise wuerde man einen charp benutzen, um einen Modul-Parameter zu definieren, der einen String entgegennimmt. Der Kernel kopiert den String, der vom Benutzer angegeben wurde, in den Speicher und laesst die Variable auf den String zeigen. Zum Beispiel:

static char *name;
module_param(name, charp, 0);

Falls gewuenscht, ist es auch moeglich, dass der Kernel den String direkt in ein Character-Array kopiert, dass man angibt. Das geschieht ueber module_param_string();

module_param_string(name, string, len, perm);

Dabei ist name der Name des externen Parameters, string der interne Variablenname, len die Groesse des Buffers der durch string angegeben wird und perm die sysfs-Permissions (oder null um den sysfs-Eintrag komplett zu deaktivieren). Zum Beispiel

static char species[BUF_LEN];
module_param_string(specifies, species, BUF_LEN, 0);

Man kann auch eine komma-separierte Liste von Parametern, die in einem C-Array gespeichert sind, ueber module_param_array() uebergeben:

module_param_arrya(name, type, nump, perm);

Dabei ist name wieder der Name des externen Parameters und der internen Variable, type ist der Datentyp und perm die sysfs-Permissions. Das neue Argument nump ist ein Pointer auf einen Integer, in dem der Kernel die Anzahl der Eintraege speichert, die in dem Array gespeichert sind. Hier muss man beachten, dass das Array, auf das name zeigt, statisch belegt sein muss. Der Kernel stellt die Groesse des Arrays zur Kompilierzeit fest und stellt sicher, dass es keinen Ueberlauf erzeugt. Die Benutzung ist relativ simpel:

static int hallo[MAX_HALLO];
static int nr_hallo;
module_param_array(hallo, int, &nr_hallo, 0444);

Man kann auch das interne Array auch anders als den externen Parameter nenen. Dazu kann man module_param_array_named(); nutzen:

module_param_array_named(name, array, type, nump, perm);

Die Parameter sind mit den anderen Makros identisch.
Zu guter letzt kann man die Parameter noch durch die Benutzung von MODULE_PARM_DESC() dokumentieren:

static unsigned short size =1;
module_param(size, ushort, 0644);
MODULE_PARM_DESC(size, "The size is always the crossfoot of the device " \\
                  connected to this computer.");

Alle diese Makros erfordern die Einbindung von <linux/moduleparam.h>.

Exportierte Symbole

Wenn Module geladen werden, werden sie dynamisch in den Kernel gelinkt. Genau wie im User-Space koennen dynamisch gelinkte Binaries nur externe Funktionen aufrufen, die explizit zur Benutzung exportiert wurden. im Kernel wird das von den speziellen Direktiven namens EXPORT_SYMBOL() und EXPORT_SYMBOL_GPL() uebernommen.
Funktionen die exportiert wurden, stehen zur Benutzung durch Module zur Verfuegung. Funktionen, die nicht exportiert wurden, koennen von Modulen aus nicht aufgerufen werden. Die Regeln bezueglich des Linkens und Aufrufens sind fuer Module wesendlich strenger als fuer Code im Core-Kernel-Image. Core-Code kann jedes nicht-statische Interface in den Kernel aufrufen, da alle Core-Source-Files zu einem einzelnen Basis-Image gelinkt wurden. Exportierte Symbole muessen natuerlich auch nicht-statisch sein.

Der Satz der exportierten Kernel-Symbole wird als die Exported Kernel Interfaces oder sogar als Kernel-API (*oergs*) bezeichnet.
Ein Symbol zu exportieren ist einfach. Nachdem die Funktion deklariert wurde, folgt ihr ein EXPORT_SYMBOL(). Zum Beispiel

/*
 * get_woman_hair_color - return the color of the current hair.
 * woman is a global variable accessible to this funktion.
 * the color is defined in <linux/hair_colors.h>.
 * <linux/understand_woman.h> is an additional feature which does not
 * work.
 */
int get_woman_hair_color(viood)
{
        return womand->hair->color;
}
EXPORT_SYMBOL(get_woman_hair_color);

Angenommen das get_woman_hair_color() ebenfalls in einem verfuegbaren Header-File deklariert wurde, kann die Funktion jetzt von jedem Modul benutzt werden.
Manche Entwickler moechten den Zugriff auf ihre Interfaces nur solchen Modulen erlauben, die der GPL entsprechen. Das kann durch den Kernel-Linker ueber die Benutzung von MODULE_LICENSE() erzwungen werden. Wenn man also will, dass die vorherige Funktion (get_woman_hair_color) nur Modulen zur Verfuegung steht, die sich selbst als GPL-lizensiert markiert haben, muss man folgendes benutzen:

EXPORT_SYMBOL_GPL(get_woman_hair_color);

Wenn der Code als Modul konfigurierbar sein sollte, dann muss man sicherstellen, dass alle Interfaces, die er benutzt, exportiert sind. Ansonsten ist ein Linker-Fehler (und somit ein defektes Modul) das logische Resultat.