venerdì 4 gennaio 2008

Linux: creare un modulo del kernel

Introduzione
Lo scopo di questo post è descrivere l'infrastruttura di base per lo sviluppo di un modulo del kernel. Realizzeremo a questo proposito il classico "hallo-world", cioè un modulo la cui unica funzionalità sarà quella di annunciare, tramite un messaggio nei log di sistema, il suo caricamento o la sua rimozione dal kernel.
Non ho ovviamente la pretesa di scrivere una guida esaustiva, ma intendo riportare quanto ho appreso durante la lettura dei documenti del kernel e i risultati delle mie prove "sul campo".

Prerequisiti
L'ambiente su cui ho lavorato è una OpenSuse 10.2 con kernel 2.6.23.9 compilato manualmente. Credo però che la compilazione a partire dai sorgenti non sia strettamente necessaria, e che basti disporre del pacchetti di sviluppo del kernel, ed eventualmente del pacchetto sorgenti se si vuole consultare la documentazione ufficiale (oltre ovviamente agli strumenti di sviluppo come make, gcc, ecc.).

Preparazione
Per prima cosa decidiamo il nome del modulo (con molta fantasia ho scelto "modulo_prova"), e creiamo una cartella di lavoro (nel mio caso è Sviluppo/kernel/modulo_prova).
All'interno di questa cartella creeremo tutti i file necessari alla realizzazione del modulo.

La compilazione di un modulo del kernel non può prescindere dalle opzioni con cui è stato compilato il kernel stesso, pena l'impossibilità di caricamento o, peggio, l'introduzione di gravi instabilità nel sistema.
Per questo non è sufficiente dotarsi di un normale Makefile, ma è necessario sfruttare il sistema di compilazione del kernel stesso.
Come spiegato nei documenti citati in fodo all'articolo, questo sistema si basa sia sui comuni Makefile sia sui file Kbuild.

Nel nostro caso il file Kbuild è molto semplice. Il suo contenuto è costituito dalla seguente riga:

obj-m += modulo_prova.o

La direttiva obj-m sta ad indicare che vogliamo produrre un modulo. Un'alternativa è utilizzare obj-y, per indicare che vogliamo integrare il codice oggetto direttamente nel kernel. Le lettere "m" e "y" non sono ovviamente casuali: derivano infatti dai valori delle direttive CONFIG_* presenti nel file di configurazione del kernel (/usr/src/linux/.config).
Il valore della direttiva, "modulo_prova.o", indicha al sistema di build qual è il file oggetto da includere nel modulo. Per produrre tale file oggetto il sistema cercherà automaticamente il file sorgente corrispondente, sostituendo l'estensione ".o" con ".c".

Il Makefile contiene il comando da impartire per avviare la compilazione. Dato che stiamo parlando del kernel, è necessario fornire a "make" due informazioni importanti: la prima è la build del kernel nel quale il modulo verrà inserito, la seconda è la cartella di lavoro in cui si trovano i sorgenti del modulo (stiamo infatti operando al di fuori dell'albero dei sorgenti del kernel).
Il contenuto del makefile è dunque il seguente:

KERNELDIR := /lib/modules/`uname -r`/build
all::
make -C $(KERNELDIR) M=`pwd`


L'opzione "-C" indica a make di spostarsi nella cartella indicata prima di effettuare qualsiasi operazione. In questo modo viene avviata un'operazione ricorsiva del tutto simile a quella che avviene durante la costruzione del kernel vero e proprio. Usando "uname -r" produrremo un modulo per il kernel attualmente in esecuzione.
La direttiva "M=`pwd`" indica che il codice del modulo si trova nella cartella corrente.

Il codice
Veniamo ora al codice vero e propio.
Il contenuto del file modulo_prova.c è il seguente:

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

static int __init prova_init(void)
{
printk(KERN_INFO "Modulo 'prova' inizializzato\n");
return 0;
}

static void __exit prova_exit(void)
{
printk(KERN_INFO "Modulo 'prova' terminato\n");
}

module_init(prova_init);
module_exit(prova_exit);
MODULE_LICENSE("GPL");


Le due funzioni di inizializzazione e di uscita non fanno altro che emettere una stringa sul log di sistema. In questo modo possiamo verificare l'avvenuta esecuzione del codice del modulo.
Le funzioni module_init() e module_exit() indicano al kernel quali sono le funzioni da chiamare rispettivamente al caricamento e alla rimozione del modulo.

Compilazione
Grazie alla preparazione svolta in precedenza la compilazione si risolve nel semplice comando

make

impartito dalla cartella di lavoro.
Dovrebbero essere prodotti alcuni file, tra i quali il più importante è ovviamente modulo_prova.ko, che rappresenta il modulo nella sua forma compilata adatta al caricamento all'interno del kernel.

Utilizzo
Per vedere il risultato delle nostre fatiche è sufficiente inserire e rimuovere il modulo e verificare che compaiano i messaggi nel log di sistema, impartendo i seguenti comandi come utente root:

insmod modulo_prova.ko
dmesg
rmmod modulo_prova
dmesg


Riferimenti
La documentazione presente nei sorgenti di Linux è esaustiva ma piuttosto complessa. Per lo sviluppo dei moduli due documenti fondamentali sono Documentation/kbuild/modules.txt e Documentation/kbuild/makefiles.txt.