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

Moduli divisi in piu file=