LKMPG: Introduzione

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


Cos'è un modulo del kernel?

E così vuoi scrivere un modulo del kernel. Conosci il C, hai scritto un po' di programmi normali che girano come processi, e adesso vuoi mettere le mani laddove c'è la vera azione, dove un singolo wild pointer può annientare il tuo file system e un core dump significa riavviare.

Cos'è esattamente un modulo del kernel? I moduli sono pezzi di codice che possono essere caricati e rimossi a richiesta. Estendono la funzionalità del kernel senza la necessità di riavviare il sistema. Per esempio, un tipo di modulo è il device driver, il quale permette al kernel l'accesso all'hardware connesso con il sistema. Senza i moduli, dovremmo costruire un kernel monolitico e aggiungere nuove funzionalità direttamente all'interno dell'immagine del kernel. Oltre ad avere kernel molto più grandi, ciò ha lo svantaggio di richiederci di ricompilare e riavviare il kernel ogni volta che abbiamo bisogno di nuove funzionalità.

Come fanno i moduli a essere caricati nel kernel?

Si può controllare quali moduli sono già stati caricati nel kernel con lsmod, che recupera le proprie informazioni leggendo il file /proc/modules.

Come fanno questi moduli a trovare la loro strada all'interno del kernel? Quando il kernel ha bisogno di una caratteristica che non è presente nel kernel stesso, il demone kmod[1] esegue modprobe per caricare il modulo. A modprobe viene passata una stringa in una delle due forme:

  • Un nome del modulo, come softdog o ppp
  • Un identificatore più generico, come char-major-10-30

Se modprobe viene richiamato con l'identificatore generico, per prima cosa cerca la stringa di questo identificatore in /etc/modprobe.conf.[2] Se trova una linea che contiene un alias del genere:

alias char-major-10-30 softdog

esso riconosce il fatto che l'identificatore generico si riferisce al modulo softdog.ko.

Successivamente, modprobe analizza il file /lib/modules/version/modules.dep, per vedere se altri moduli devono essere caricati prima di quello richiesto. Questo file (modules.dep) è creato dal comando depmod -a e contiene le dipendenze dei moduli. Per esempio, msdos.ko richiede che il modulo fat.ko sia già caricato nel kernel. Il modulo richiesto ha una dipendenza con un altro modulo se l'altro modulo definisce simboli (= variabili o funzioni) utilizzati dal modulo richiesto.

In ultimo, modprobe usa insmod per caricare nel kernel prima tutti i moduli prerequisiti e dopo il modulo richiesto. modproble dirige insmod in /lib/modules/version/[3], la directory standard per i moduli. insmod non sa nulla sul percorso dei moduli, mentre modprobe ne è a conoscenza, sa capire le dipendenze e caricare i moduli nell'ordine giusto. Quindi, per esempio, se si vuole caricare il modulo msdos, puoi eseguire o:

insmod /lib/modules/2.6.11/kernel/fs/fat/fat.ko
insmod /lib/modules/2.6.11/kernel/fs/msdos/msdos.ko

oppure:

modprobe msdos

Quello che abbiamo visto è: insmod impone che gli si debba passare il percorso completo e che vengano inseriti i moduli nell'ordine giusto, mentre a modprobe è necessario solamente il nome, senza nessuna estensione, e capisce tutto ciò che è necessario conoscere facendo un parsing del file /lib/modules/version/modules.dep.

Le distribuzioni Linux forniscono modprobe, insmod e depmod all'interno del package module-init-tools. Nelle versioni precedenti questo pacchetto era chiamato modutils. Qualche distribuzione gestisce alcuni wrapper che permettono ad entrambi i pacchetti di essere installati in parallelo e utilizzano la versione giusta a seconda del fatto che si abbia a che fare con i kernel 2.4 o 2.6. Gli utenti non dovrebbero avere la necessità di preoccuparsi dei dettagli se hanno a che fare con le ultime versioni di questi strumenti.

Ora si conosce come un modulo viene inserito nel kernel. C'è ancora qualcosina da sapere se si vuole scrivere un modulo personale che dipende da altri moduli (chiameremo questi tipi di moduli 'stacking module'). Ma di tutto ciò ne parleremo in un prossimo capitolo. Abbiamo molto da imparare prima di affrontare questo problema relativamente difficile.

Prima di iniziare

Prima di addentrarci nel codice, ci sono alcuni problemi che bisogna affrontare. Ogni sistema è differente e ognuno ha le sue particolarità. Compilare e caricare correttamente il primo programma "hello world" può essere, alcune volte, una fregatura. Bisogna rimanere comunque tranquilli: dopo aver superato l'ostacolo iniziale della prima volta, successivamente tutto sarà in discesa.

Modversioning

Un modulo compilato per un certo kernel non verrà caricato se si avvia un kernel differente a meno che non venga abilitata l'opzione CONFIG_MODVERSIONS nello stesso. Per ora non andremo ad analizzare la modversions. Fin quando non copriremoquesto argomento, gli esempi della guida potrebbero non funzionare se si sta utilizzando un kernel con il modversioning abilitato. Comunque, la maggior parte delle distribuzioni Linux hanno un kernel con l'opzione abilitata. Se si riscontrano problemi nel caricare i moduli per errori di versioning, si compili un kernel con il modversioning disabilitato.

Usare X

E' altamente raccomandato che tutti gli esampi discussi in questa guida vengano digitati, compilati e caricati. E' anche altamente raccomandato che si faccia tutto questo da una console. Non si dovrebbe lavorare con questa roba sul server X.

I moduli non possono stampare sullo schermo così come fa una printf(), ma possono loggare informazioni e warning, i quali finiscono per essere stampati sullo schermo, ma solo si sta utilizzando una console. Se si inserisce un modulo con insmod da xterm, le informazioni e i warning saranno loggati, ma solo sul proprio file di log. Non si vedrà niente finquando non si analizza questo file. Per avere un accesso immediato a questa informazione, si lavori su una console.

Problemi di compilazione e versioni del kernel

Molto spesso, le distribuzioni Linux distribuiscono il sorgente del kernel patchato in varie modalità non standard che possono creare problemi.

Un problema veramente comune è che certe distribuzioni Linux distribuiscono gli headers del kernel incompleti. Ci sarà la necessità di compilare il proprio codice utilizzando vari file di header del kernel Linux. La legge di Murphy stabilisce che gli header non presenti solo esattamente quelli di cui si ha bisogno affinchè il proprio modulo funzioni.

Per evitare questi due problemi, raccomando caldamente di scaricare, compilare ed avviare un kernel Linux fresco e disponibile che può essere scaricato da qualsiasi mirror ufficiale del kernel. Vedi il Linux Kernel HOWTO per maggiori dettagli.

Ironicamente, quanto detto può causare problemi. Di default, il compilatore gcc sul proprio sistema può cercare gli header del kernel nelle locazioni standard piuttosto che laddove si è appena installata la nuova versione del kernel (usualmente in /usr/src/). Ciò può essere risolto usando l'opzione -I di gcc.

Note

  • [1] Nelle prime versioni di Linux, era conosciuto come kerneld
  • [2] Se tale file esiste. Nota che l'effittivo comportamento potrebbe dipendere dalla distribuzione. Se sei interessanto ai dettagli, leggi le man page su module-init-tools, e controlla cosa succede realmente. Potresti usare qualcosa come strace modprobe dummy per vedere come possa essere caricato il modulo dummy.ko. Per tua informazione: il modulo dummy.ko di cui sto parlando qui è una parte della mainline del kernel e può essere trovato nella sezione networking. E' necessario che sia compilato come un modulo (e installato, ovviamente) per far funzionare il tutto.
  • [3] Se stai modificando il kernel, per evitare sovrascrittura dei moduli esistenti potresti volere usare la variabile EXTRAVERSION nel Makefile del kernel per creare una directory separata.