Articoli Manifesto Tools Links Canali Libri Contatti ?
Linux / Kernel

Porting dei moduli al kernel Linux 2.6

Abstract
Linus ha appena rilasciato la prima release stabile del kernel 2.6 e le modifiche apportate sono parecchie. Per gli autori di moduli sono due le principali novità: il nuovo sistema di build (kbuild) e le modifiche apportate alle API di base per la gestione e il caricamento dei moduli.
Data di stesura: 23/12/2003
Data di pubblicazione: 09/02/2004
Ultima modifica: 04/04/2006
di Daniele Milan Discuti sul forum   Stampa

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:

$ insmod mymod.ko

mentre per rimuoverlo dal kernel:

$ rmmod mymod

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

#define MODULE

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:

MODULE_LICENSE("GPL");

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

module_put(&module)

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)

Informazioni sull'autore

Daniele Milan, laureando in Informatica all'Università degli Studi di Milano, si interessa di robotica, os programming e sicurezza. Convinto sostenitore dell'Open Source, usa prevalentemente Linux e il linguaggio C, interessandosi al contempo a sistemi meno conosciuti.

È possibile consultare l'elenco degli articoli scritti da Daniele Milan.

Altri articoli sul tema Linux / Kernel.

Discuti sul forum   Stampa

Cosa ne pensi di questo articolo?

Discussioni

Questo articolo o l'argomento ti ha interessato? Parliamone.