/* * foobar */ static int hallo_init(void) { ..... }
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.
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).
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.
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.
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}).
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
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.
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.
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.
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>.
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.