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

Moduli vs Programmi

Come i moduli iniziano e finiscono

Un programma di solito inizia con la funzione main(), esegue una serie di istruzioni e termina al completamento di queste istruzioni. I moduli del kernel lavorano in modo leggermente diverso. Un modulo inizia sempre o con la funzione init_module o con la funzione che specifichi nella chiamata di module_init. Questa è la funzione d'ingresso per i moduli: dice al kernel che funzionalità il modulo fornisce e imposta il kernel in modo da eseguire le funzioni del modulo quando richieste. Una volta fatto tutto questo, la funzione d'ingresso termina ed il modulo non fa nulla finchè il kernel vuole fare qualcosa con il codice che il modulo fornisce.

Tutti i moduli terminano chiamando o la funzione cleanup_module o la funzione specificata nella chiamata di module_exit. Questa è la funzione d'uscita dei moduli: questa rimuove qualsiasi cosa la funzione d'ingresso ha fatto. Elimina le funzionalità registrate dal modulo all'interno del kernel dalla funzione d'ingresso.

Ogni modulo deve avere una funzione d'ingresso ed una d'uscita. Poichè c'è piu di un modo di specificare le funzioni d'ingresso ed uscita, cercherò di fare del mio meglio usando i termini `funzione d'ingresso' e `funzione d'uscita', ma se dovessi confondermi, riferendomi a loro come init_module e cleanup_module, penso che capirai cosa cerco di dire.

Funzioni disponibili per i moduli

I programmatori usano funzioni che non definiscono tutte le volte. Un primo esempio è printf(). Utilizzi queste funzioni messe a disposizione dalla libreria standard del C, libc. Le definizioni di queste funzioni non vengono inserite nel tuo programma fino al momento del linking, che assicura che il codice (per la printf() ad esempio) è disponibile, e aggiusta le chiamate alla funzione in modo da puntare al relativo codice.

I moduli del kernel sono differenti in questo. Nell'esempio "Ciao Mondo", potresti aver notato che abbiamo usato una funzione, printk(), ma non abbiamo incluso una libreria di I/O. Questo perchè i moduli sono file oggetti i cui simboli sono risolti quando si carica il modulo con insmod. Le definizioni dei simboli arrivano dal kernel stesso; le uniche funzioni esterne che puoi usare sono quelle messe a disposizione dal kernel. Se sei curioso sui simboli che sono esportati dal tuo kernel, dai uno sguardo a /proc/kallsyms.

Un punto da tenere a mente è la differenza tra funzioni di libreria e chiamate di sistema. Le funzioni di libreria sono di alto livello, vengono eseguite ad user space e mettono a disposizione una comoda interfaccia, per il programmatore, alle funzioni che fanno il vero lavoro---le chiamate di sistema (syscall). Le syscall vengono eseguite in kernel mode per conto dell'utente e sono messe a disposizione dal kernel stesso. La funzione di libreria printf() può sembrare una funzione di stampa generica, ma tutto quello che fa è formattare i dati in stringhe e scriverle usando una syscall di basso livello, write(), che poi invia i dati allo standard output.

Vuoi vedere da quali syscall è fatta printf()? E' facile! Compila il seguente programma:

#include <stdio.h>
int main(void)
{ printf("hello"); return 0; }

con gcc -Wall -o hello hello.c. Esegui il programma con strace ./hello. Sei impressionato? Ogni linea che vedi corrisponde ad una syscall. strace[4] è un comodo programma che ti dà i dettagli su quali syscall il programma chiama, come una chiamata è fatta, quali sono gli argomenti e cosa ritorna. E' uno strumento inestimabile per capire cose come a quali file un programma sta provando ad accedere. Verso la fine, vedrai una linea come write(1, "hello", 5hello). Questo è. La faccia dietro la maschera printf(). Potresti non essere familiare con write() poichè molte persone usano le funzioni di libreria per le operazioni di I/O su file (come fopen,fputs, fclose). Se questo è il caso, prova a dare uno sguardo a man 2 write. La seconda sezione del man è dedicata alle chiamate di sistema (come kill() e read()). La terza sezione del manuale è dedicata alle chiamate di libreria, alle quali probabilmente siete molto più abituati (come cosh() e random()).

Puoi anche scrivere moduli per rimpiazzare le syscall, come faremo a breve. I crackers spesso fanno uso di questo tipo di cose per eseguire backdoors o trojan, ma puoi scrivere il tuo modulo per fare cose benigne, come stampare Tee hee, fa il solletico! ogni volta che qualcuno prova a cancellare un file sul tuo sistema.

User Space vs Kernel Space

Il kernel gestisce l'accesso a qualsiasi risorsa, sia essa relativa ad una scheda video, ad un hard disk o anche alla memoria. I programmi spesso competono per le stesse risorse. Nel momento in cui ho salvato questo documento, updatedb ha iniziato ad aggiornare il database locale. La mia sessione di vim ed updatedb stanno entrambi usando l'hard disk in modo concorrenziale. Il kernel ha bisogno di mantenere le cose ordinate e non dare agli utenti l'accesso alle risorse in qualunque modo vogliano. A questo punto, la CPU può eseguire codice in modalità differenti. Ogni modalità dà diversi livelli di libertà per fare quello che si vuole sul sistema. L'architettura Intel 80386 ha 4 modalità differenti, che sono chiamati anelli (rings). Unix usa solo due anelli; l'anello più alto (ring 0, anche conosciuto come `modalità supervisore' dove tutto è permesso) e l'anello più basso, che è chiamato `modalità utente'.

Ricordati della discussione riguardo alle funzioni di libreria e le chiamate di sistema. Di solito, si usano le librerie di funzioni nella modalità utente. La funzione di libreria chiama una o più chiamate di sistema, e queste chiamate di sistema viengono eseguite alle spalle delle funzioni di libreria, ma in modalità supervisore poichè fanno parte del kernel stesso. Una volta che la chiamata di sistema ha completato il suo compito, finisce e l'esecuzione ritorna in modalità utente.

Name Space

Code Space

Driver dei device

Numeri Major e Minor

Note

[4] E' uno strumento inestimabile per capire cose come a quali file un programma sta provando ad accedere. Mai avuto un programma che fallisce silenziosamente perchè non riesce a trovare un file? E' una PITA!