The Linux Kernel Module Programming Guide

Sommario

  1. Prefazione
  2. Introduzione
  3. Ciao Mondo
  4. Fasi preliminari
  5. Comportamento dei driver dei device
  6. Il filesystem /proc
  7. Usare /proc per l'input
  8. Comunicare con i file dei device
  9. Chiamate di sistema
  10. Bloccare i processi
  11. Rimpiazzare printk
  12. Pianificare compiti
  13. I gestori degli interrupt
  14. Multi-processing simmetrico
  15. Insidie comuni
  16. Appendice A
  17. Appendice B


Ciao, Mondo (parte 1): Il modulo più semplice

Quando il primo programmatore delle caverne scolpì il primo programma sul muro del primo computer delle caverne, fu un programma che disegnava la stringa "Ciao, mondo" sotto forma di graffiti. I libri di testo romani sulla programmazione iniziano con il programma "Salut, Mundi". Non so cosa possa succedere alle persone che rompono questa tradizione, ma penso sia più sicuro non scoprirlo. Inizieremo con una serie di programmi "hello world" che mostrano vari aspetti delle conoscenze di base nella scrittura di moduli del kernel.

Qui vediamo il modulo più semplice possibile. Non compilarlo ancora; analizzeremo la compilazione di un modulo nella prossima sezione.

Esempio 3-1. hello-1.c

/*  
 *  hello-1.c - Il modulo più semplice.
 */
#include <linux/module.h>	/* Necessario a tutti i moduli */
#include <linux/kernel.h>	/* Necessario per la macro KERN_INFO */

int init_module(void)
{
	printk(KERN_INFO "Ciao mondo 1.\n");

	/* 
	 * Un valore di ritorno diverso da 0 indica che init_module è fallita; il modulo non può essere caricato
	 */
	return 0;
}

void cleanup_module(void)
{
	printk(KERN_INFO "Arrivederci mondo 1.\n");
}

I moduli del kernel devono avere almeno due funzioni. Una funzione "iniziale" (inizializzazione) detta init_module() che è chiamata quando il modulo è inserito nel kernel tramite insmod, e una funzione "finale" (di pulizia) detta cleanup_module() che è chiamata prima che il modulo venga rimosso con rmmod. In realtà, le cose sono cambiate a partire dal kernel 2.3.13. Si può utilizzare qualunque nome che piaccia per la funzione di inizializzazione e di pulizia, e si imparerà a farlo nella Sezione XXXX2.3XXXX. Infatti, il nuovo metodo è quello preferito. Comunque, molti continuano ad utilizzare init_module() e cleanup_module() per le proprie funzioni iniziali e finali.

Tipicamente, init_module() o registra un handler con il kernel per fare una serie di azioni, oppure rimpiazza una della funzioni del kernel stesso con un codice proprio (usualmente questo codice fa una serie di azioni e poi chiama la funzione originale). Si suppone che la funzione cleanup_module() disfaccia tutto ciò che ha fatto init_module(), in modo tale che il modulo possa essere rimosso tranquillamente.

Infine, ogni modulo del kernel ha bisogno di includere linux/module.h. Nell'esempio abbiamo dovuto includere linux/kernel.h solo per la macro KERN_ALERT utilizzata per definire il livello di log della printk(), che imparerai nella Sezione XXXX.

Introduzione a printk()

Nonostante quello che si potrebbe pensare, printk() non aveva lo scopo di comunicare informazioni all'utente, anche se l'abbiamo utilizzato esattamente per questo in hello-1! E' stato pensato, invece, per essere un meccanismo di logging per il kernel, ed è utilizzato per loggare informazioni e mostrare dei warning. Perciò, ogni dichiarazione di printk() viene fornita di un livello di priorità, che, nell'esempio, è la KERN_ALERT (<1>). Ci sono 8 priorità e il kernel possiede delle macro per queste, quindi non c'è il bisogno di utilizzare numeri criptici. Si possono vedere le macro (e il loro significato) nel file linux/kernel.h. Se non si specifica un livello di priorità, verrà utilizzata quello di default: DEFAULT_MESSAGE_LOGLEVEL.

E' importante prestare una certa attenzione nel leggere le macro di priorità. L'header file descrive anche cosa significa ogni priorità. In pratica, è bene non utilizzare i numeri, come <4>. Si utilizzino sempre le macro, come KERN_WARNING.

Se la priorità è minore di int console_loglevel, il messaggio è stampato sul proprio terminale corrente. Se sia syslogd che klogd stanno girando, allora il messaggio verrà inserito in coda a /var/log/messages, indipendentemente dal fatto che venga stampato sulla console. Nell'esempio abbiamo utilizzato una priorità alta, come KERN_ALERT, per essere sicuri che il messaggio di printk() venga stampato sulla console piuttosto che inserito nel file di log. Quando si scrivono moduli reali, si utilizzano le priorità che meglio si adattano alla situazione in esame.

Compilare i moduli del kernel

I moduli del kernel devono essere compilati un po' differentemente dalle applicazioni userspace regolari. Le vecchie versioni del kernel richiedevano molta attenzione riguardo queste impostazioni, che sono, di solito, memorizzate nei Makefiles. Sebbene siano organizzate gerarchicamente, sono presenti molte impostazioni ridondanti nei Makefiles e ciò fa in modo che sia abbastanza difficile da mantenere. Fortunatamente, c'è un nuovo modo di fare queste cose, chiamato kbuild, e il processo di generazione dei moduli esterni è adesso completamente integrato nel meccanismo standard. Per saperne di più su come compilare moduli che non fanno parte del kernel ufficile (come ad esempio tutti gli esempi di questa guida), si veda il file linux/Documentation/kbuild/modules.txt.

Analizziamo ora un semplice Makefile per compilare un modulo chiamato hello-1.c:

Esempio 3-2. Makefile per un modulo basilare del kernel

obj-m += hello-1.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Da un punto di vista tecnico solamente la prima linea è realmente necessaria, i target "all" e "clean" sono stati aggiunti per convenienza.

Adesso si può compilare il modulo digitando il comando make. Dovresti ottenere un output che somigli al seguente:

hostname:~/lkmpg-examples/02-HelloWorld# make
make -C /lib/modules/2.6.11/build M=/root/lkmpg-examples/02-HelloWorld modules
make[1]: Entering directory `/usr/src/linux-2.6.11'
  CC [M]  /root/lkmpg-examples/02-HelloWorld/hello-1.o
 Building modules, stage 2.
  MODPOST
  CC      /root/lkmpg-examples/02-HelloWorld/hello-1.mod.o
  LD [M]  /root/lkmpg-examples/02-HelloWorld/hello-1.ko
make[1]: Leaving directory `/usr/src/linux-2.6.11'
hostname:~/lkmpg-examples/02-HelloWorld#

Si noti che il kernel 2.6 introduce una nuova convenzione sui nomi dei file: i moduli del kernel hanno adesso l'estensione .ko (invece della vecchia estensione .o) che permette una facile distinzione dai file oggetto convenzionali. La ragione di ciò risiede nel fatto che i file .ko contengono una sezione addizionale (.modinfo) in cui vengono mantenute informazioni addizionali. Vedremo tra breve a cosa possono servire queste informazioni.

Per vedere di che tipo di informazione si tratta viene usato il comando: modinfo hello-*.ko

hostname:~/lkmpg-examples/02-HelloWorld# modinfo hello-1.ko
filename:       hello-1.ko
vermagic:       2.6.11 preempt PENTIUMII 4KSTACKS gcc-3.3
depends:

Niente di spettacolare, finora. Però se utilizziamo modinfo su uno degli esempi futuri, hello-5.ko, vedremo:

hostname:~/lkmpg-examples/02-HelloWorld# modinfo hello-5.ko
filename:       hello-5.ko
license:        GPL
author:         Peter Jay Salzman
vermagic:       2.6.11 preempt PENTIUMII 4KSTACKS gcc-3.3
depends:
parm:           myintArray:An array of integers (array of int)
parm:           mystring:A character string (charp)
parm:           mylong:A long integer (long)
parm:           myint:An integer (int)
parm:           myshort:A short integer (short)
hostname:~/lkmpg-examples/02-HelloWorld# 

Sono presenti moltissime informazioni. Il nome dell'autore per la segnalazione di eventuali bug, informazioni sulla licenza, persino una piccola descrizione dei parametri che il modulo accetta.

Ulteriori dettagli sui Makefiles dei moduli del kernel sono disponibili in linux/Documentation/kbuild/makefiles.txt. Bisogna essere sicuri di aver letto questo file e i files relativi prima di iniziare a mettere mano sui Makefiles: vi risparmierà molto lavoro.

Adesso è arrivato il momento di inserire il nostro modulo fresco di compilazione all'interno del kernel con insmod ./hello-1.ko (ignora qualsiasi cosa che sembra non funzionare, ne discuteremo a breve).

Tutti i moduli caricati nel kernel sono menzionati in /proc/modules. Va alla fine del file e utilizza cat per vedere che il tuo modulo fa veramente parte del kernel. Congratulazioni, adesso sei l'autore di parte del codice del kernel Linux. Quando la novità svanisce, rimuovi il modulo dal kernel usando rmmod hello-1. Dai un'occhiata a /var/log/messages per vedere se effettivamente l'operazione di rimozione è stata loggata nel file di log del sistema.

Adesso c'è un altro esercizio per il lettore. Avete presente il commento sul valore di return in init_module()? Cambiate il valore con qualcosa di negativo, rimcompilate e caricate il modulo di nuovo. Cosa succede?

Ciao, Mondo (parte 2)

Dalla versione 2.4 del kernel Linux, si possono rinominare le funzioni di init e cleanup dei propri moduli; infatti non devono essere chiamate per forza rispettivamente init_module() e cleanup_module. Questo è reso possibile della macro module_init() e module_exit(). Queste macro sono definite in linux/init.h. L'unica attenzione da avere è quella di definire le funzioni di init e cleanup prima di chiamare le macro, pena errori di compilazione. Vediamo un esempio di questa tecnica:

Esempio 3-3. hello-2.c

/*  
 *  hello-2.c - Dimostrazione dell'utilizzo delle macro module_init() e module_exit().
 *  Questa tecnica è da preferire all'uso di init_module() e cleanup_module()
 */
#include <linux/module.h>	/* Necessario per tutti i moduli */
#include <linux/kernel.h>	/* Necessario per KERN_INFO */
#include <linux/init.h>		/* Necessario per le macro */

static int __init hello_2_init(void)
{
	printk(KERN_INFO "Ciao, mondo 2\n");
	return 0;
}

static void __exit hello_2_exit(void)
{
	printk(KERN_INFO "Arrivederci, mondo 2\n");
}

module_init(hello_2_init);
module_exit(hello_2_exit);

Quindi, a questo punto, abbiamo due moduli reali sotto le nostre mani. Aggiungere un altro modulo è semplice:

Esempio 3-4. Makefile for entrambi i nostri moduli

obj-m += hello-1.o
obj-m += hello-2.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Adesso diamo un'occhiata a linux/drivers/char/Makefile per un esempio concreto. Come si può vedere, qualcosa è stata inserita nel kernel (obj-y) ma dove sono andate tutte le obj-m? Chi ha familiarità con gli script di shell può facilmente individuarli. Per quelli che non hanno familiarità, le voci obj-$(CONFIG_FOO) che si vedono ovunque vengono espanse in obj-y o in obj-m a seconda del fatto che la variabile CONFIG_FOO è stata settata a 'y' o a 'm'. Già che ci siamo, queste erano esattamente il tipo di variabili che avete impostato nel file linux/.config l'ultima volta che avete dato make menuconfig o qualche comando simile.

Ciao, Mondo (parte 3): Le macro __init e __exit

Ora vedremo una caratteristica dei kernel 2.2 e superiori. Si faccia caso alla diversa definizione delle funzioni di init e di cleanup. La macro __init permette, per i driver built-in, di scartare la funzione e di liberare la memoria ad essa associata una volta che la funzione termina. Lo stesso non vale per i moduli caricabili: infatti quanto abbiamo detto ha perfettamente senso se si pensa a come la funzione iniziale viene invocata. Esiste anche la macro __initdata che funziona similmente a __init ma per le "variabili di init" piuttosto che per le funzioni.

La macro __exit fa in modo che la funzione venga ignorata quando il modulo è compilato staticamente nel kernel, e come __init, l'effetto è diverso per i moduli caricabili. Ancora: la macro __exit è un segna-posto per indicare di liberare memoria una volta che termina la funzione di cleanup; a differenza dei moduli caricabili, i driver built-in non hanno bisogno della funzione di clean-up.

Queste macro sono definite in linux/init.h e servono per liberare la memoria del kernel. Quando il kernel viene avviato e si nota qualcosa come Freeing unused kernel memory: 236k freed, questo è precisamente ciò che il kernel sta liberando.

Esempio 3-5 hello-3.c

/*  
 *  hello-3.c - Illustrazione delle macro __init, __initdata e __exit
 */

#include <linux/module.h>	/* Necessario per tutti i moduli */
#include <linux/kernel.h>	/* Necessario per KERN_INFO */
#include <linux/init.h>		/* Necessario per le macro */

static int hello3_data __initdata = 3;

static int __init hello_3_init(void)
{
	printk(KERN_INFO "Ciao, mondo %d\n", hello3_data);
	return 0;
}

static void __exit hello_3_exit(void)
{
	printk(KERN_INFO "Arrivederci, mondo 3\n");
}

module_init(hello_3_init);
module_exit(hello_3_exit);

Ciao Mondo (parte 4): Licenza e Documentazione dei moduli

Se state utilizzando un kernel 2.4 o superiore, quando caricate un modulo proprietario è possibile che siate avvisati con un messaggio del genere:

# insmod xxxxxx.o
Warning: loading xxxxxx.ko will taint the kernel: no license
  See http://www.tux.org/lkml/#export-tainted for information about tainted modules
Module xxxxxx loaded, with warnings

Nei kernel 2.4 e superiori, è stato messo a punto un meccanismo che permette di identificare il codice con licenza GPL (e simili) in modo tale che la gente può essere avvisata se il codice non è open-source. Ciò si può realizzare con la macro MODULE_LICENSE che è mostrata nel prossimo pezzo di codice. Impostando la licenza GPL, è possibile fare in modo che l'avviso non venga stampato. Questo meccanismo è definito e documentato in linux/module.h:

/*
 * The following license idents are currently accepted as indicating free
 * software modules
 *
 *	"GPL"				[GNU Public License v2 or later]
 *	"GPL v2"			[GNU Public License v2]
 *	"GPL and additional rights"	[GNU Public License v2 rights and more]
 *	"Dual BSD/GPL"			[GNU Public License v2
 *					 or BSD license choice]
 *	"Dual MIT/GPL"			[GNU Public License v2
 *					 or MIT license choice]
 *	"Dual MPL/GPL"			[GNU Public License v2
 *					 or Mozilla license choice]
 *
 * The following other idents are available
 *
 *	"Proprietary"			[Non free products]
 *
 * There are dual licensed components, but when running with Linux it is the
 * GPL that is relevant so this is a non issue. Similarly LGPL linked with GPL
 * is a GPL combined work.
 *
 * This exists for several reasons
 * 1.	So modinfo can show license info for users wanting to vet their setup 
 *	is free
 * 2.	So the community can ignore bug reports including proprietary modules
 * 3.	So vendors can do likewise based on their own policies
 */

Similmente, la macro MODULE_DESCRIPTION() è utilizzata per descrivere ciò fa il modulo, la macro MODULE_AUTHOR dichiara il nome dell'autore, e la MODULE_SUPPORTED_DEVICE() dichiara quali tipi di device sono supportati dal modulo.

Queste macro sono tutte definite in linux/module.h e non sono usate dal kernel. Sono semplicemente necessarie per la documentazione e possono essere analizzate con uno strumento come objdump. Come esercizio per l'utente, si provi a cercare queste macro in linux/driver per vedere come gli autori dei moduli le utilizzano per la documentazione.

Raccomando di usare qualcosa tipo grep -inr MODULE_AUTHOR * in /usr/src/linux-2.6.x/. Le persone che non hanno familiarità con gli strumenti da linea di comando preferiranno qualche soluzione via web, cerca qualche sito che offre tutto il kernel tree che puo' essere indicizzato con LXR (o configuralo localmente sul tuo PC).

L'uso di editor Unix tradizionali, come emacs o vi, hanno la possibilità di ricercare nei file tags utili. Questi tags possono essere generati tramite make tags o make TAGS in /usr/src/linux-2.6.x/. Una volta che hai generato il tagfile nel tuo kernel tree, puoi spostare il cursore su qualche chiamata di funzione e usare qualche combinazione di tasti per saltare direttamente alla definizione della funzione stessa.

Esempio 2-6. hello-4.c

/*  
 *  hello-4.c - Demonstrates module documentation.
 */
#include <linux/module.h>	/* Needed by all modules */
#include <linux/kernel.h>	/* Needed for KERN_INFO */
#include <linux/init.h>		/* Needed for the macros */
#define DRIVER_AUTHOR "Peter Jay Salzman <p@dirac.org>"
#define DRIVER_DESC   "A sample driver"

static int __init init_hello_4(void)
{
	printk(KERN_INFO "Hello, world 4\n");
	return 0;
}

static void __exit cleanup_hello_4(void)
{
	printk(KERN_INFO "Goodbye, world 4\n");
}

module_init(init_hello_4);
module_exit(cleanup_hello_4);

/*  
 *  You can use strings, like this:
 */

/* 
 * Get rid of taint message by declaring code as GPL. 
 */
MODULE_LICENSE("GPL");

/*
 * Or with defines, like this:
 */
MODULE_AUTHOR(DRIVER_AUTHOR);	/* Who wrote this module? */
MODULE_DESCRIPTION(DRIVER_DESC);	/* What does this module do */

/*  
 *  This module uses /dev/testdevice.  The MODULE_SUPPORTED_DEVICE macro might
 *  be used in the future to help automatic configuration of modules, but is 
 *  currently unused other than for documentation purposes.
 */
MODULE_SUPPORTED_DEVICE("testdevice");

Passare argomenti ad un modulo da linea di comando

I moduli possono prendere argomenti da linea di comando, ma non tramite argc/argv come potresti essere abituato a fare.

Per passare argomenti al tuo modulo, bisogna dichiarare una variabile globale che assumerà il valore passato da linea di comando e usare la macro module_param() (definita in linux/moduleparam.h) per impostare il meccanismo d'associazione. A runtime, insmod copierà l'argomento da linea di comando passato al modulo nella variabile globale dichiarata come per esempio ./insmod mymodule.ko myvariable=5. La dichiarazione della variabile e le macro dovrebbero essere poste all'inizio del modulo, per chiarezza. L'esempio dovrebbe chiarire la mia pessima spiegazione.

La macro module_param() prende 3 argomenti: il nome della variabile, il suo tipo e i permessi per il corrispondente file in sysfs. Le variabili di tipo integer possono essere signed (come al solito) o unsigned. Se preferisci usare array di interi o stringhe, dai uno sguardo a module_param_array() e module_param_string().

int myint = 3;
module_param(myint, int, 0);

Anche gli array sono supportati, ma le cose sono leggermente differenti ora rispetto a come lo erano nei giorni del kernel 2.4. Per tenere traccia dei numeri di parametri è necessario passare un puntatore ad una variabile di conteggio come terzo parametro. A tua scelta, puoi ignorare il conteggio e passare NULL invece. Noi mostriamo entrambe le possibilità qui:

int myintarray[2];
module_param_array(myintarray, int, NULL, 0); /* not interested in count */

int myshortarray[4];
int count;
module_parm_array(myshortarray, short, , 0); /* put count into "count" variable */

Un buon utilizzo è di avere un valore di default per la variabile settato, come una porta o un indirizzo IO. Se le variabili contengono il valore di default, allora eseguire l'autoriconoscimento (spiegato da qualche parte). Altrimenti, mantenere il valore corrente. Tutto questo sarà chiarito in seguito.

Infine, c'e' una maco, MODULE_PARM_DESC() che è usata per documentare gli argomenti che il modulo accetta. La macro prende due parametri: un nome di variabile e una descrizione della variabile.

Esempio 2-7. hello-5.c

/*
 *  hello-5.c - Demonstrates command line argument passing to a module.
 */
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/stat.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Peter Jay Salzman");

static short int myshort = 1;
static int myint = 420;
static long int mylong = 9999;
static char *mystring = "blah";
static int myintArray[2] = { -1, -1 };
static int arr_argc = 0;

/* 
 * module_param(foo, int, 0000)
 * The first param is the parameters name
 * The second param is it's data type
 * The final argument is the permissions bits, 
 * for exposing parameters in sysfs (if non-zero) at a later stage.
 */

module_param(myshort, short, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
MODULE_PARM_DESC(myshort, "A short integer");
module_param(myint, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
MODULE_PARM_DESC(myint, "An integer");
module_param(mylong, long, S_IRUSR);
MODULE_PARM_DESC(mylong, "A long integer");
module_param(mystring, charp, 0000);
MODULE_PARM_DESC(mystring, "A character string");

/*
 * module_param_array(name, type, num, perm);
 * The first param is the parameter's (in this case the array's) name
 * The second param is the data type of the elements of the array
 * The third argument is a pointer to the variable that will store the number 
 * of elements of the array initialized by the user at module loading time
 * The fourth argument is the permission bits
 */
module_param_array(myintArray, int, &arr_argc, 0000);
MODULE_PARM_DESC(myintArray, "An array of integers");

static int __init hello_5_init(void)
{
	int i;
	printk(KERN_INFO "Hello, world 5\n=============\n");
	printk(KERN_INFO "myshort is a short integer: %hd\n", myshort);
	printk(KERN_INFO "myint is an integer: %d\n", myint);
	printk(KERN_INFO "mylong is a long integer: %ld\n", mylong);
	printk(KERN_INFO "mystring is a string: %s\n", mystring);
	for (i = 0; i < (sizeof myintArray / sizeof (int)); i++)
	{
		printk(KERN_INFO "myintArray[%d] = %d\n", i, myintArray[i]);
	}
	printk(KERN_INFO "got %d arguments for myintArray.\n", arr_argc);
	return 0;
}

static void __exit hello_5_exit(void)
{
	printk(KERN_INFO "Goodbye, world 5\n");
}

module_init(hello_5_init);
module_exit(hello_5_exit);

Raccomando di giocare con questo codice:

satan# insmod hello-5.ko mystring="bebop" mybyte=255 myintArray=-1
mybyte is an 8 bit integer: 255
myshort is a short integer: 1
myint is an integer: 20
mylong is a long integer: 9999
mystring is a string: bebop
myintArray is -1 and 420

satan# rmmod hello-5
Goodbye, world 5

satan# insmod hello-5.ko mystring="supercalifragilisticexpialidocious" \
> mybyte=256 myintArray=-1,-1
mybyte is an 8 bit integer: 0
myshort is a short integer: 1
myint is an integer: 20
mylong is a long integer: 9999
mystring is a string: supercalifragilisticexpialidocious
myintArray is -1 and -1

satan# rmmod hello-5
Goodbye, world 5

satan# insmod hello-5.ko mylong=hello
hello-5.o: invalid argument syntax for mylong: 'h'

Moduli divisi in piu file

A volte ha senso dividere un modulo del kernel in vari codici sorgenti. Ecco un esempio di un modulo del genere:

Esempio 2-8. start.c

/*
 *  start.c - Illustration of multi filed modules
 */

#include <linux/kernel.h>	/* We're doing kernel work */
#include <linux/module.h>	/* Specifically, a module */

int init_module(void)
{
	printk(KERN_INFO "Hello, world - this is the kernel speaking\n");
	return 0;
}

Il prossimo file:

Esempio 2-9. stop.c

/*
 *  stop.c - Illustration of multi filed modules
 */

#include <linux/kernel.h>	/* We're doing kernel work */
#include <linux/module.h>	/* Specifically, a module  */

void cleanup_module()
{
	printk(KERN_INFO "Short is the life of a kernel module\n");
}

E, infine, il makefile:

Esempio 2-10. Makefile

obj-m += hello-1.o
obj-m += hello-2.o
obj-m += hello-3.o
obj-m += hello-4.o
obj-m += hello-5.o
obj-m += startstop.o
startstop-objs := start.o stop.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Questo è il makefile completo per tutti gli esempio che abbiamo appena visto. Le prime cinque linee non sono niente di speciale, ma nell'ultimo esempio abbiamo bisogno di due linee aggiuntive. Per primo, inventiamo un nome per il nostro modulo combinato, per secondo diciamo a make quali file oggetto sono parti del modulo.

Creare moduli per kernel precompilati

Ovviamente, raccomandiamo fortemente di ricompilare il tuo kernel in modo da abilitare un certo numero di caratteristiche di debugging utili, come MODULE_FORCE_UNLOAD (forzare l'unload di moduli): quando questa opzione è abilitata, puoi forzare il kernel a rimuovere un modulo, anche quando crede (il kernel, n.r.d.) che non è sicuro, tramite il comando rmmod -f module. Questa opzione può farti risparmiare tanto tempo ed un certo numero di riavvii durante lo sviluppo di un modulo.

Tuttavia, ci sono alcuni casi in cui si voglia caricare un modulo in un kernel precompilato in esecuzione, come per esempio quelli forniti nelle comuni distribuzioni Linux, o in un kernel compilato tempo fa. In alcune circostanze potresti avere la necessità di compilare ed inserire un modulo in un kernel in esecuzione che non hai il permesso di ricompilare, o su una macchina che preferiresti non riavviare. Se non riesci a pensare ad un caso che ti costringerà ad usare moduli per un kernel precompilato, potresti voler saltare questa sezione e trattare il resto di questo capitolo come una grande nota a pie' di pagina.

Ora, se hai appena installato un kernel da sorgente, usalo per compilare i tuoi moduli e prova ad inserirli nel kernel; in molti casi potresti ottenere un errore come il seguente:

insmod: error inserting 'poet_atkm.ko': -1 Invalid module format

Informazioni meno criptiche sono presenti in /var/log/messages:

Jun  4 22:07:54 localhost kernel: poet_atkm: version magic '2.6.5-1.358custom 686 
REGPARM 4KSTACKS gcc-3.3' should be '2.6.5-1.358 686 REGPARM 4KSTACKS gcc-3.3'

In altre parole, il kernel si rifiuta di accettare il modulo per via delle versioni (più precisamente, magics version) che non corrispondono. Per inciso, le version magics sono conservate nell'oggetto del modulo come stringa statica che inizia per vermagic:. Le informazioni di versione sono inserite nel tuo modulo quando è linkato al file init/vermagic.o. Per dare uno sguardo alle version magics ed altre stringhe conservate in un modulo, puoi dare il comando modinfo module.ko:

[root@pcsenonsrv 02-HelloWorld]# modinfo hello-4.ko 
license:        GPL
author:         Peter Jay Salzman <p@dirac.org>
description:    A sample driver
vermagic:       2.6.5-1.358 686 REGPARM 4KSTACKS gcc-3.3
depends:        

Per superare questo problema possiamo ricorrere all'opzione --force-vermagic, ma questa soluzione è potenzialmente insicura, e indiscutibilmente inaccettabile nella produzione di moduli. Di conseguenza, vogliamo compilare il nostro modulo in un ambiente che sia identico a quello in cui il nostro kernel precompilato è stato costruito. Come fare questo è il tema del resto del capitolo.

Prima di tutto, assicurarsi che il codice sorgente del kernel è disponibile e che abbia esattamente la stessa versione del kernel che in esecuzione. Dunque, trova il file di configurazione che è stato usato per compilare il kernel precompilato. Di solito, è disponibile nella directory /boot, sotto un nome tipo config-2.6.x. Potresti volerlo solo copiare nella directory del kernel: cp /boot/config-`uname -r` /usr/src/linux-`uname -r`/.config.

Concentriamoci di nuovo sul precedente errore: uno sguardo da vicino alle stringe version magic suggeriscono che, anche con due file di configurazione che sono esattamente gli stessi, una piccola differenza nel version magic è possibile, ed è sufficiente a prevenire l'inserimento del modulo nel kernel. Questa piccola differenza, cioè la stringa personalizzata che appare nel version magic del modulo e non in quella del kernel, è dovuta ad una modifica nel makefile, che alcune distribuzioni includono, rispetto all'originale. Quindi, esamina il tuo /usr/src/linux/Makefile ed assicurati che l'informazione della versione specificata è esattamente la stessa di quella usata per il tuo kernel corrente. Per esempio, il tuo makefile potrebbe iniziare cosi:

VERSION = 2
PATCHLEVEL = 6
SUBLEVEL = 5
EXTRAVERSION = -1.358custom
...
	

In questo caso, hai bisogno di ripristinare il valore del simbolo EXTRAVERSION a -1.358. Suggeriamo di tenere una copia di backup del makefile usato per compiare il tuo kernel disponibile in /lib/modules/2.6.5-1.358/build. Un semplice cp /lib/modules/`uname -r`/build/Makefile /usr/src/linux-`uname -r` dovrebbe essere sufficiente. Inoltre, se hai già inziato a compilare il kernel con il precedente (sbagliato) Makefile, dovresti anche rieseguire make, o modificare direttamente il simbolo UTS_RELEASE nel file /usr/src/linux-2.6.x/include/linux/version.h con lo stesso valore presente in /lib/modules/2.6.x/build/include/linux/version.h, o sovrascrivere quest'ultimo sul primo.

Ora, eseguire make per aggiornare la configurazione, la versione degli headers e gli oggetti:

[root@pcsenonsrv linux-2.6.x]# make
CHK     include/linux/version.h
UPD     include/linux/version.h
SYMLINK include/asm -> include/asm-i386
SPLIT   include/linux/autoconf.h -> include/config/*
HOSTCC  scripts/basic/fixdep
HOSTCC  scripts/basic/split-include
HOSTCC  scripts/basic/docproc
HOSTCC  scripts/conmakehash
HOSTCC  scripts/kallsyms
CC      scripts/empty.o
...
	

Se non desideri compiare il kernel attualmente, puoi interrompere il processo (CTRL-C) subito dopo la linea che inizia per SPLIT poichè in quel momento i file che ti servono saranno già pronti. Ora puoi tornare indietro alla directory del tuo modulo e compilarlo: sarà compilato correttamente in accordo con le impostazioni del tuo kernel corrente e verrà caricato senza nessun errore.