di Alessandro Rubini
Riprodotto con il permesso di Linux Magazine, Edizioni Master.
Una descrizione di come funzionano la configurazione e la compilazione della versione 2.6 del kernel.
In questo articolo, primo di una serie incentrata sul kernel 2.6, viene descritta la procedura di configurazione e compilazione, rivolgendo l'attenzione ai dettagli implementativi più che alle modalità d'uso. Le procedure e le implementazioni descritte sono state verificate sulla versione 2.6.0-test9, la più recente al momento della stesura del testo.
Riquadro 1 - Dove scaricare i sorgenti del kernel
Le versioni "vanilla" di Linux, cioè quelle rilasciate da Linus
Torvalds, sono disponibili su ftp.kernel.org e sui mirror
nazionali.
Altri autori distribuiscono versioni modificate, per esempio
aggiungendo funzionalità utili ma ancora sperimentali, oppure
aggiungendo codice specifico a piattaforme hardware diverse
da quelle direttamente supportate da Linus. Tali versioni sono
di solito identificate da suffissi nel nome del kernel; per
esempio, linux-2.6.0-test9-rmk1
è la versione di
Russel King (rmk) contenente codice per piattaforma ARM non
ancora integrato nei sorgenti di Linus.
Il luogo suggerito da cui scaricare i kernel di Torvalds
è sicuramente il mirror italiano ftp.it.kernel.org:
ftp://ftp.it.kernel.org/pub/linux/kernel/v2.6/
Il file "linux-2.6.0-test9.tar.bz2", di 33MB, una volta decompresso creerà una directory chiamata "linux-2.6.0-test9", di circa 200MB.
Una volta scaricati i sorgenti (si veda il riquadro per i
dettagli), il primo passo necessario per poter compilare il proprio
linux-2.6 è la configurazione, ovvero la scelta di quali funzionalità
attivare o meno e quali driver includere nalla propria immagine di kernel.
L'output della fase di configurazione
è il file .config
, un semplice file di testo che viene
letto da make al momento di compilare il kernel.
La configurazione, come gli altri passi, viene invocata dal comando
make
, e può essere effettuata in diversi modi:
make config
Il comando pone delle domande usando il terminale testuale come
strumenti di interazione, aspettandosi risposte semplici ("Y" per
si, "N" per no, "M" per modulo). Il numero di domande è
dell'ordine di qualche centinaio e la procedura risulta oltremodo
pesante; si tratta del meccanismo storico di configurazione,
l'unico presente nelle prime versioni del kernel, quando le
opzioni da scegliere erano poche decine. L'implementazione del
comando in linux-2.6 è un eseguibile scritto in C,
scripts/kconfig/conf
, linkato dinamicamente con la libreria
scripts/kconfig/libkconfig.so
. Nelle precedenti versioni il
comando veniva eseguito da uno script di shell.
make menuconfig
La configurazione avviene tramite un menu pseudo-grafico in
modalità testo, sul terminale corrente. Il programma che
interagisce con l'utente si chiama scripts/kconfig/mconf
ed
il suo codice sorgente sta in scripts/lxdialog
. L'eseguibile
è linkato con libkconfig e con libncurses per la gestione
del terminale testuale.
make xconfig
Il comando compila ed esegue una applicativo grafico per gestire
la configurazione. Mentre linux-2.2 e linux-2.4 usavano uno
strumento scritto in Tcl/Tk come motore di make xconfig
,
ora la procedura è basato sulle librerie Qt. Il programma che viene
eseguito è scripts/kconfig/qconf
, che sul
mio sistema risulta linkato con 23 librerie dinamiche.
make gconfig
Per chi usa Gnome invece di Kde, questo comando compila ed
esegue un applicativo grafico con interfaccia Gnome, che
si troverà in scripts/kconfig/gconf
.
make oldconfig
Il comando invoca l'eseguibile scripts/kconfig/conf
(come make config
), ma pone
solo le domande di cui non trova risposta nel file
.config
.
È la scelta migliore quando si dispone già di un .config
adatto alle proprie necessità, come succede per esempio quando si passa
da una versione del kernel
alla successiva applicando una patch (si veda il riquadro)
al proprio albero di sorgenti.
I vari meccanismi di configurazione, quindi, assumono che sul sistema
sia disponibile un compilatore C e gli strumenti necessari per
sviluppare con le librerie utilizzate (Qt, ncurses). Sicuramente
il compilatore deve essere stato installato se si vuole compilare
un kernel, ma le altre richieste potrebbero non essere
soddisfatte sul vostro sistema. In particolare, per usare make
menuconfig
è necessario il pacchetto ncurses-dev
(o equivalente) e per usare make xconfig
è necessario il
pacchetto qt-dev
(o equivalente). Personalmente, non usando
KDE o altro software basato su qt, ho dovuto scaricare e installare 22MB di
software per poter provare make xconfig
e verificare che
tutto sommato non mi serve.
Riquadro 2 - Aggiornare tramite patch
Una volta scaricato un tar contenente i sorgenti del kernel, il modo migliore per passare alla versione successiva è scaricare il file di "patch", cioè la descrizione delle differenze tra due versioni successive, ovvero la "pezza" da applicare alla versione precedente per rinnovarla.
I file di patch si trovano nello stesso luogo da cui si scaricano i tar completi e, come i tar completi, sono compressi con gzip o bzip2.
Per applicare un file di differenze del kernel, occorre entrare nella directory dei sorgenti che si vogliono modificare e mandare il file sullo standard-input del comando "patch -p1". Per esempio:
cd /usr/src/linux-2.6 bzcat /path/to/patch-2.6.0-test9.bz2 | patch -p1
Le domande poste durante la fase di configurazione dipendono spesso le une dalle altre. Per esempio, a chi specifica di non avere bisogno del supporto SCSI non verrà posta alcuna domanda relativa alle periferiche SCSI supportate. Tali dipendenze possono anche essere più complesse: per esempio una specifica scheda di rete PCI può essere selezionata solo se si sta compilando un kernel che abbia sia il supporto di rete sia quello per il bus PCI.
Definire un formato di file che funzioni come input per i vari sistemi di configurazione non è impresa facile; con linux-2.2 e linux-2.4, infatti, capitava talvolta che l'introduzione di nuove opzioni di configurazione funzionasse con "make config" ma non, per esempio, con xconfig, in quanto ognuno dei tre meccanismi di configurazione aveva il suo interprete dei file di input: tre interpreti diversi perché scritti in tre linguaggi diversi.
Con Linux-2.6 la lettura dei file di input per la configurazione viene
centralizzata in libkconfig, una libreria che viene usata da tutte e
tre le interfacce utente che, ora, sono tutte scritte in C.
L'interprete (o "parser") contenuto in libkconfig è scritto usando
lex e yacc, i classici
strumenti Unix per la creazione di interpreti (si veda il riquadro
successivo). I file sorgente del parser
si chiamano zconf.l
(input di lex) e zconf.y
(input di yacc).
Scopo del parser appena descritto è l'interpretazione di un file,
solitamante chiamato Kconfig
, che definisce i
vari parametri di configurazione, le loro dipendenze, la descrizione
dettagliata del loro significato. Un file Kconfig
può richiedere la lettura di altri file, perciò in pratica troviamo uno o
più di tali file in quasi ogni directory dell'albero di sorgenti (nella
versione 2.6.0-test9 ci sono 207 file Kconfig
,
che definiscono un totale di 3727 opzioni di configurazione diverse).
La sintassi dei file Kconfig
e l'organizzazione
dei makefile del nucleo sono descritti nella directory
Documentation/kbuild/
,
all'interno dei sorgenti del kernel.
Il nome del Kconfig
principale viene passato
sulla linea di comando ai tre programmi interattivi; per chi compila un
kernel per piattaforma PC il nome del file è
arch/i386/Kconfig
. La stringa i386
viene
determinata dal Makefile tramite l'esecuzione del comando
uname -m
, ma si può configurare un kernel per un'altra
piattaforma semplicemente assegnando la variabile ARCH
sulla
linea di comando di make. Per esempio:
make menuconfig ARCH=ppc
La variabile ARCH
viene semplicemente usata come
nome di subdirectory nell'albero arch/
, deve quindi
essere uno dei nomi di piattaforma supportati dal kernel mentre altri valori
di ARCH
generano un errore.
In pratica, però, configurare un kernel per un'architettura diversa da
quella nativa ha senso quasi solo in un contesto di
cross-compilazione, descritto più avanti. L'unica eccezione è
user-mode linux.
Il programma "lex" è un generatore di analizzatori lessicali. Il suo file di input di solito usa l'estensione .l (per "lex", appunto) e l'esecuzione del programma genera un sorgente in C che contiene l'analizzatore lessicale richiesto. La versione GNU di lex si chiama flex.
Il programma "yacc" (yet another compiler compiler) è un generatore di analizzatori sintattici (quindi un "compilatore di compilatori"). Il nome del suo file di input solitamente termina in ".y", mentre il suo output è un sorgente C contenente l'analizzatore definito nel file di input. La versione di yacc inclusa nel progetto GNU si chiama bison.
Nei sorgenti del kernel, nella directory scripts/kconfig
sono già
presenti i file di output generati da flex e bison, per cui non è
necessario avere installato i programmi per poter compilare un kernel.
Chi modificasse la sintassi o il lessico dei file di configurazione
avrebbe invece bisogno di lex e/o yacc per generare i nuovi
analizzatori.
Le directory nell'albero arch/
contengono il
codice sorgente relativo all'interfacciamento del kernel con i vari
ambienti operativi in cui è stato portato. Si tratta del codice relativo
all'avvio del sistema, alla gestione delle interruzioni, al controllo
della MMU (memory management unit) per implementare la memoria
virtuale. Quasi tutti tali ambienti operativi sono famiglie di
processori: i386 (il PC), ppc (PowerPC, come nei Macintosh), arm
(Acorn Risc Machine, un processore molto usato in campo embedded).
La directory arch/um
, a differenza di tutte le
altre, non contiene il codice relativo ad un tipo di processore su cui può
girare linux-2.6, quanto la definizione di un ambiente operativo "user mode".
Si tratta, cioè, del codice per far eseguire il kernel Linux come programma
applicativo all'interno di un sistema operativo già avviato. Questo
permette di osservare facilmente il kernel tramite un debugger
convenzionale e fare esperimenti con configurazioni strane senza
riavviare la propria macchina e senza il rischio di cadute del
sistema.
La cosa interessante di UML, guardando al processo di configurazione e
compilazione, è che si tratta di codice indipendente
dall'architettura che lo ospita: se si compila usando
ARCH=um
lavorando su PC si otterrà un eseguibile i386, se
si compila su una macchina PowerPC si otterrà un eseguibile ppc altrettanto
funzionale.
Il meccanismo usato per ottenere un eseguibile UML che corrisponda con
la macchina ospite è ben documentato nel Makefile principale del
kernel: la variabile SUBARCH
viene ottenuta
invocando uname -m
,mentre ARCH=um
deve essere
specificata sulla linea di comando di make.
ARCH
viene usata normalmente per la compilazione,
selezionando arch/um/Kconfig
come file di
configurazione principale e arch/um/Makefile
come
makefile di piattaforma; quest'ultimo però include
arch/um/Makefile-$(SUBARCH)
per le definizioni che
devono cambiare da una piattaforma ospite all'altra. Al momento,
il lavoro su user-mode Linux viene svolto solo su i386, ppc e ia64, come si può
vedere dal contenuto della directory arch/um
, anche se
UML è ad oggi utilizzabile solo su x86 e solo con l'applicazione di
modifiche apposite, disponibili su user-mode-linux.sourceforge.net
oppure people.redhat.com/mingo/UML-patches/
.
I comandi per la compilazione del kernel non sono cambiati in
maniera significativa rispetto alla versione 2.4 o 2.2 ma c'è una
differenza: il semplice make
, che prima creava solo il file
eseguibile vmlinux
ora crea anche il file avviabile
per la specifica piattaforma e tutti i moduli. Il comando
make bzImage
, come nelle precedenti versioni,
crea il file avviabile
arch/i386/boot/bzImage
, quello che viene passato a
Grub o a Lilo all'avvio del sistema ma non i moduli.
Anche se le immagini avviabili per altre
piattaforme in genere non si chiamano bzImage
, in
genere si invocherà semplicemente make
, senza argomenti,
indipendentemente dalla piattaforma.
È interessante ricordare che il nome del file
bzImage
("big zImage", ovvero "big compressed
image"), come la sua struttura, risentono della storia passata del kernel
Linux su piattaforma i386, un processore che all'avvio si rifiuta di vedere
più di 640k di memoria e richiede sporchi trucchi per poter avviare un
vero sistema operativo (il cui nucleo, oggi, supera spesso la dimensione
di 1MB). Il nome "big zImage" indica il meccanismo di caricamento chepermette di caricare in memoria un'immagina compressa del kernel che eccede i 512kB
disponibili nella memoria bassa; il nome zImage
e
la relativa procedura di avvio dell'immagine compressa erano stati introdotti
prima di linux-1.0, quando alcune configurazioni del kernel iniziavano
ad occupare più dei 512kB disponibili all'accensione del sistema.
Qualunque sia il comando adottato, la compilazione di linux-2.6 genera un output molto più parsimonioso rispetto al comportamento delle precedenti versioni. Invece di stampare nella loro interezza le righe di comando invocate, il sistema visualizza una breve riga di testo che riassume l'operazione in corso. In Figura 1 è riportato un estratto dei messaggi stampati durante la compilazione.
Figura 1 - Output della compilazione
ostro% make bzImage
CHK include/linux/version.h
UPD include/linux/version.h
SYMLINK include/asm -> include/asm-i386
HOSTCC scripts/split-include
HOSTCC scripts/conmakehash
[...]
CC arch/i386/kernel/process.o
CC arch/i386/kernel/semaphore.o
CC arch/i386/kernel/signal.o
AS arch/i386/kernel/entry.o
CC arch/i386/kernel/traps.o
[...]
CC fs/partitions/check.o
CC fs/partitions/msdos.o
LD fs/partitions/built-in.o
CC fs/proc/task_mmu.o
CC fs/proc/inode.o
[...]
LD arch/i386/boot/compressed/vmlinux
OBJCOPY arch/i386/boot/vmlinux.bin
HOSTCC arch/i386/boot/tools/build
BUILD arch/i386/boot/bzImage
Root device is (3, 1)
Boot sector 512 bytes.
Setup is 4736 bytes.
System is 1297 kB
Kernel: arch/i386/boot/bzImage is ready
Per chi volesse vedere l'intera riga di comando usata per compilare
ogni file sorgente, è possibile aggiungere l'assegnamento
V=1
nell'invocazione di make: per esempio,
make bzImage V=1
. Come alternativa si può assegnare il
valore "1" alla variabile di ambiente KBUILD_VERBOSE
;
si noti però che assegnare la variabile di ambiente V
non
ha alcun effetto sulla compilazione del kernel.
L'uso di una variabile che viene specificata con due nomi diversi a
seconda del contesto, è realizzato nel Makefile principale,
che verifica dove è stata definita la variabile V
,
se presente, e utilizzandone il valore solo se è stato definito sulla riga di
comando. Questo meccanismo, usato altre due volte all'interno
del Makefile, permette di dare un nome univoco alle
variabili di ambiente che influenzano la compilazione del kernel,
permettendo però di modificare il comportamento di make
scrivendo direttive molto più brevi sulla riga di comando di make.
Questo perché, si sa, il programmatore è pigro.
La più interessante di queste variabili di ambiente credo sia
KBUILD_OUTPUT
, abbreviabile come x"O" sulla riga di comando.
La variabile specifica la directory di output del processo di compilazione,
istruendo make a non modificare in alcun modo l'albero di sorgenti del kernel.
KBUILD_OUTPUT
si può usare solo a condizione che la directory dei
sorgenti sia pulita, non contenga cioè file di output di precedenti
compilazioni.
La variabile KBUILD_OUTPUT
permette in pratica di compilare
lo stesso albero di sorgenti con configurazioni diverse o addirittura per
piattaforme diverse, senza dover replicare tutto l'albero dei sorgenti
per ogni compilazione (come invece era necessario fare con le versioni
precedenti del kernel). Naturalmente usando KBUILD_OUTPUT
è
anche possibile compilare un albero di sorgenti sul quale non si abbia il
permesso di scrittura, per esempio un CD o un disco condiviso in rete
tra più macchine. Data la mole dei sorgenti, evitarne copie
inutili è sicuramente un grosso vantaggio in termini di spazio su disco.
Una interessante novità introdotta nel sistema di compilazione di linux-2.6 è la gestione automatica delle dipendenze. Con "dipendenza" si indica la direttiva con cui si dice al comando make che un certo file di output deve essere rigenerato se un altro file è stato modificato successivamente; si tratta cioè della dichiarazione che il contenuto di un certo file (di output) dipende dal contenuto di un altro file.
Mentre la dipendenza di un file oggetto dal file sorgente C con lo stesso nome è immediata e gestita internamente dal comando make, la dipendenza dai file di header ("intestazione") è più problematica, in quanto un file sorgente C può includere decine di header, e ciascuno di questi includerne altri. Nelle precedenti versioni del kernel, occorreva invocare "make dep" al fine di creare in ogni directory la dichiarazione di tutte le dipendenze di ogni file di output presente in quella directory. Questo meccanismo manuale può dare dei problemi nel momento in cui l'albero dei sorgenti viene modificato (applicando delle patch, per esempio) e ci si dimentica di rigenerare le dipendenze: una prima patch e una prima ricompilazione possono funzionare senza problemi, ma se questa patch ha cambiato le dipendenze dei file dagli header, una successiva modifica all'albero potrebbe non scatenare tutte le ricompilazioni necessarie.
Il meccanismo introdotto nella nuova versione del kernel elimina del
tutto il passaggio "make dep", in quanto usa il compilatore stesso per
generare i file di dipendenza, tramite l'opzione -DM
di gcc.
In questo modo, le dipendenze associate ad un file vengono rigenerate ogni
volta che questo viene compilato. Nel caso di linux-2.6, le dipendenze
associate ad ogni file oggetto, insieme alla riga di comando usata per
compilarlo, vengono salvate in un file nascosto nella stessa directory
del file oggetto (un oggetto vt.o, per esempio, sarà controllato dal
file .vt.o.cmd); tale file viene poi letto da make durante la compilazione,
per decidere i comandi da invocare in questa directory.
La "cross-compilazione" è la procedura usata per compilare un eseguibile che giri su una piattaforma diversa da quella dove gira il compilatore. L'esempio più comune oggi è la generazione di kernel e applicazioni per macchine palmari (di solito basate su processore ARM), lavorando su piattaforma i386. Per creare file eseguibili per un'altra piattaforma occorre installare (o compilare) sulla macchina ospite, in questo caso i386 un insieme di strumenti per la cross-compilazione: compilatore, linker, assemblatore e altri. Questi strumenti sono programmi eseguibili al cui nome usuale viene aggiunto un prefisso uguale per tutti; per esempio "arm-linux-gcc", "arm-linux-ld" eccetera.
Il Makefile del kernel fornisce il supporto per la cross-compilazione
in maniera immediata: la variabile CROSS_COMPILE
, se
assegnata, indica il prefisso da usare per invocare gli strumenti di
compilazione. Nel caso preso come esempio, quindi, occorretà specificare nella
variabili di ambiente o sulla riga di comando di make
CROSS_COMPILE=arm-linux-
(comprensivo di trattino finale).
La variabile CROSS_COMPILE
, però, non può modificare tutte le
istanze del comando gcc, in quanto durante la creazione del kernel è
necessario compilare alcuni programmi da eseguire immediatamente,
sulla macchina ospite. A tal fine il Makefile del kernel distingue tra
le invocazioni di CC
(il cross-compilatore) e
HOSTCC
(il compilatore nativo della macchina ospite).
L'utilizzo delle due variabili è riportato dai messsaggi stampati durante
la compilazione, come si vede in Figura 1.
L'unica differenza di linux-2.6 rispetto a linux-2.4 riguardo la
cross-compilazione è la possibilità di assegnare CROSS_COMPILE
nelle variabili di ambiente, in quanto nelle precedenti versioni occorreva
specificarlo sulla riga di comando di make, oppure modificare il
Makefile con un editor. Il costrutto di GNU-make usato è ?=
, di
assegnazione condizionale: la variabile viene assegnata dal Makefile
al suo valore di default solo se non precedentemente definita (per
esempio come variabile di ambiente).
La compilazione dei moduli rimane immutata rispetto alle precedenti
versioni: Alessandro Rubinimake modules
e make modules_install
sono i comandi usati per compilare ed installare i moduli, anche
se come abbiamo visto make modules
è oggi invocato
automaticamente dal semplice make
senza argomenti.
Il meccanismo di compilazione dei moduli, comunque,
è cambiato notevolmente rispetto a linux-2.4, sia per come vengono
compilati i moduli sia per come vengono caricati. Una descrizione
dettagliata del funzionamento dei moduli con linux-2.6 sarà l'argomento
trattato nel prossimo numero.
La copia letterale e la redistribuzione su qualsiasi supporto di questo articolo nella sua integrità è permessa, purchè questa nota sia conservata.