Questo articolo vuole essere una guida pratica al porting dei moduli. Per chi volesse approfondire
consiglio di leggere una serie di articoli su
http://lwn.net/Articles/driver-porting/, scritti da Jonathan Corbet,
autore insieme a Alessandro Rubini del libro
Linux Device Drivers, 2nd Edition
edito dalla O'Reilly.
Un'occhiata al source tree del kernel 2.6.0 può essere molto utile per approfondire ulteriormente.
Il Makefile
Partiamo con le modifiche da apportare al Makefile per funzionare con kbuild.
Il nuovo sistema di build del kernel semplifica un po' la scrittura dei Makefile, forzando inoltre una
maggiore uniformità del codice fra gli sviluppatori:
CONFIG_MODULE=m
obj-$(CONFIG_MODULE) := module.o
Le righe sopra riportate sono sufficienti a compilare un modulo di un solo file. Se si vuole che il modulo sia "linkato"
staticamente al kernel, occorre sostituire
m
con
y
nella dichiarazione di
CONFIG_MODULE
.
Sorgenti Multipli
Se il proprio modulo è composto da più file sorgente, supponiamo
"module1.c"
,
"module2.c"
,
"module3.c"
, allora
occorre far sapere a kbuild di quali file è composto:
CONFIG_MODULE=m
obj-$(CONFIG_MODULE) := module.o
module-objs := module1.o module2.o module3.o
Da notare, in terza riga, il simbolo
module-objs
: la sintassi è
<nomemodulo>-objs := <elenco file>
.
Un esempio tratto direttamente dal kernel può essere utile per chiarire, quindi riporto un estratto dal Makefile per il driver della scheda wifi Aironet Arlan 655:
obj-$(CONFIG_ARLAN) := arlan.o
arlan-objs := arlan-main.o arlan-proc.o
dove CONFIG_ARLAN sarà valutato
y
o
m
, a seconda che si scelga di compilare il supporto della scheda come dinamico o built-in.
Sottodirectory
Se i sorgenti del proprio modulo sono organizzati in sottodirectory, allora occorre dire a kbuild di richiamare i Makefile ricorsivamente
nelle sottodirectory:
obj-$(CONFIG_MODULE) := module/
In questo modo verranno compilate prima tutte le sottodirectory e solo al termine verrà "linkato" il modulo vero e proprio.
Flag di compilazione
Si possono definire flag usando le variabili
EXTRA_CFLAGS
,
EXTRA_LDFLAGS
,
EXTRA_AFLAGS
rispettivamente come opzioni per il compilatore (di norma gcc), il linker o l'assembler .
Kbuild aggiungerà queste flag al relativo comando
applicandole a tutto il Makefile.
ifdef DEBUG
EXTRA_CFLAGS += -DDEBUG_HIGH
endif
Per applicare delle flag ai singoli file sorgente, si possono usare le flag
CFLAGS_$@
e
AFLAGS_$@
.
CFLAGS_aha152x.o = -DAHA152X_STAT -DAUTOCONF
CFLAGS_gdth.o = -DDEBUG_GDTH=2 -D__SERIAL__ -D__COM2__ \
-DGDTH_STATISTICS
CFLAGS_seagate.o = -DARBITRATE -DPARITY -DSEAGATE_USE_ASM
Compilare i moduli
Per compilare e "linkare" il tutto è sufficiente lanciare:
$ make -C /path/to/source SUBDIRS=$PWD modules
dove
"/path/to/source"
è il percorso alla root directory del kernel target (ad esempio
"/usr/src/linux-2.6.0/"
).
Alla fine del processo di compilazione si otterrà un file con estensione
.o
e uno con estensione
.ko
.
Il modulo da caricare è quello con estensione
.ko
, a differenza dei moduli per kernel fino al 2.4.
Infatti gli sviluppatori del kernel 2.6 hanno scelto di differenziare gli oggetti
(kernel objects, dalle cui iniziali l'estensione
.ko
) dai normali file oggetto (estensione
.o
).
Per caricare il modulo è sufficiente lanciare:
mentre per rimuoverlo dal kernel:
I sorgenti
Ottenuto il Makefile, prima di poter compilare il modulo, bisogna modificare il codice e portarlo alle nuove specifiche
del kernel 2.6.
Innanzitutto, la consueta riga
non è più necessaria perché sarà kbuild a generarla.
Un'altra piccola modifica, risalente già alla release del kernel 2.4.10, è la dichiarazione di licenza del modulo:
Sono previste varie licenze, tutte segnalate nel file
"include/linux/module.h"
dei sorgenti del kernel. È importante
definire la licenza dei moduli in quanto, se proprietaria (
MODULE_LICENSE("Proprietary")
), non ci sarà supporto dalla comunità di sviluppatori del
kernel, e il modulo non avrà accesso ai simboli esportati con la macro
EXPORT_SYMBOL_GPL()
.
Per un elenco completo dei simboli esportati con questa macro consiglio di usare
cscope
oppure un grep ricorsivo
sul kernel source tree
$ grep -e EXPORT_SYMBOL_GPL -h -R linux-2.6.0/
supponendo che
"linux-2.6.0/"
sia la dir root del kernel tree.
Un'altra modifica a cui probabilmente si è abituati, se recentemente si è sviluppato per il kernel 2.4, è la dichiarazione
delle funzioni di inizializzazione e cleanup usando
module_init()
e
module_exit()
definite in
"include/linux/init.h"
.
Prendiamo il solito modulo di esempio:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
static int mymodule_init(void)
{
printk(KERN_ALERT "Mymodule loaded!\n");
return 0;
}
static void mymodule_exit(void)
{
printk(KERN_ALERT "Mymodule unloaded!\n");
}
module_init(mymodule_init);
module_exit(mymodule_exit);
MODULE_LICENSE("GPL");
Ora non è più sufficiente scrivere le funzioni di inizializzazione e cleanup nominandole
init_module
e
cleanup_module
, ma è obbligatorio
passare i puntatori alle funzioni come parametro di
module_init
e
module_exit
.
A questo punto si è liberi di scegliere il nome delle funzioni...
Passare parametri al modulo
La vecchia macro
MODULE_PARM
va in pensione. Al suo posto subentra
module_param(name, type, perm)
, definita in
"include/linux/moduleparam.h"
.
Il parametro
name
è ovviamente il nome, mentre
type
è il tipo di parametro definito, e può essere del tipo
byte
,
short
,
ushort
,
int
,
uint
,
long
,
ulong
,
charp
,
bool
,
invbool
.
perm
indica i permessi del file associato al parametro nel nuovo sysfs (
"/sys"
): zero indica che il parametro non è visibile, gli altri valori
indicano permessi di lettura e scrittura, e vengono definiti con i classici valori ottali (tipo
0644
) o con le macro standard riportate di seguito
(tratte da
"include/linux/stat.h"
):
#define S_IRUSR 00400 // user read
#define S_IWUSR 00200 // user write
#define S_IRGRP 00040 // group read
#define S_IWGRP 00020 // group write
#define S_IROTH 00004 // other read
#define S_IWOTH 00002 // other write
Ne sono state definite poi altre per comodità, che sono semplici combinazioni delle precedenti:
#ifdef __KERNEL__
#define S_IRUGO (S_IRUSR|S_IRGRP|S_IROTH) // all read
#define S_IWUGO (S_IWUSR|S_IWGRP|S_IWOTH) // all write
#endif
Occorre ricordare che i permessi consentiti sono lettura e scrittura. L'esecuzione non avrebbe senso.
Poiché siamo agli albori del sysfs, consiglio di fare esperimenti.
Se è necessario registrare il valore di un parametro in una variabile con un nome diverso dal nome del parametro, si può usare la seguente variante della
module_param()
:
module_param_name(name, value, type, perm)
dove
name
è il nome del parametro visibile all'esterno e
value
è il nome della variabile interna al proprio modulo.
Questo può essere comodo per i programmatori nell'assegnare nomi significativi alle variabili interne,
e per l'utente del modulo, per il quale i programmatori possono associare ai parametri esterni nomi più significativi per un utente "profano".
Per comodità, l'uso delle stringhe è stato semplificato ed ora è possibile avere una stringa copiata direttamente all'interno di un
array di
char
usando la funzione:
module_param_string(name, string, len, perm)
string
è un puntatore ad un array di char preallocato di lunghezza
len
.
Ad esempio:
char sa[10];
module_param_string(stringa, sa, 10, 0);
si ottiene, così, un parametro
stringa
, il cui valore, se definito, verrà registrato nell'array puntato da
sa
, per un massimo di 10 caratteri.
Parametri multipli
I parametri multipli, forniti al modulo come liste di valori separati da virgole, vengono dichiarati usando:
module_param_array(name, type, num, perm)
num
viene settato al numero di parametri passati al parametro
name
.
Ad esempio:
int count[10];
int n;
module_param_array(count, int, n, 0);
eseguendo:
$ insmod ./hello.ko count=1,2,3,4,5
fornirà il valore 5 alla variabile
n
. Se invece fornissimo un numero di parametri maggiore della lunghezza dell'array dichiarato, la
funzione scarterà quelli in eccesso.
Passare parametri ai moduli a boot time
È possibile passare parametri ai moduli direttamente dalla stringa di boot del kernel: è sufficiente scrivere il nome del modulo, seguito da un punto,
dal nome del parametro, dal segno uguale e infine dal valore.
Ad esempio, se usate GRUB:
kernel /boot/bzImage <...> mymodule.count=1,2,3,4,5
Alias di un modulo
È possibile definire un nome alternativo con cui identificare un modulo usando la funzione:
MODULE_ALIAS("nome-alternativo");
Contatore d'uso
La gestione del contatore d'uso nel kernel 2.4, che usava macro come
MOD_INC_USE_COUNT
, era scomoda e soggetta ad errori.
Per questo motivo, nel kernel 2.6 è stata fatta una scelta differente, portando all'esterno del modulo la gestione del contatore.
Un frammento di codice che debba usare le risorse messe a disposizione dal modulo deve prima chiamare la funzione
int try_module_get(&module);
un valore di ritorno uguale a zero significa che la funzione ha avuto esito negativo e le risorse del modulo non dovrebbero essere usate.
Questo potrebbe accadere quando il modulo sta eseguendo le funzioni di inizializzazione o cleanup.
Per comunicare al modulo che non si ha più bisogno dei suoi servizi è sufficiente chiamare
dove
&module
è l'indirizzo alla istanza della
struct module
che descrive il modulo di cui si ha bisogno.
Occorre ricordare che è possibile usare
THIS_MODULE
per riferirsi al modulo da cui si sta chiamando la
try_module_get()
o la
module_put()
.
Esportare i simboli
Nel nuovo kernel 2.6, solo i simboli esplicitamente esportati sono visibili, a differenza della serie 2.4 dove, se non diversamente indicato, venivano
tacitamente esportati tutti i simboli presenti nel modulo.
Per esportare un simbolo è sufficiente usare la macro
EXPORT_SYMBOL(symbol-to-export)