Compilare Linux-2.6

Una descrizione di come funzionano la configurazione e la compilazione
della versione 2.6 del kernel.

Introduzione

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.

* Configurazione del kernel

Una volta scaricati i sorgenti, 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 e` basato sulle librerie Qt.  Il programma che viene
     eseguito è "scripts/kconfig/qconf", chee sul mio sistema risulta
     linkato con 23 librerie dinamiche.

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 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.


* La definizione dei parametri di configurazione

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. 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 e` "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 cun contesto di
cross-compilazione, descritto piu` avanti. L'unica eccezione e`
user-mode linux.

* User-mode Linux (UML)

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 pero`
include arch/um/Makefile-$(SUBARCH) per le definizioni che
devono cambiare da una piattaforma ospite all'altra. Al momento,
user-mode Linux è supportato solo su i386, ppc e ia64, come si può
vedere dal contenuto della directory arch/um .

* La compilazione

I comandi per la compilazione del kernel non sono cambiati rispetto
alla versione 2.4 o 2.2. Il comando "make" senza argomenti crea il
file eseguibile "vmlinux" nella directory principale dei sorgenti,
mentre "make bzImage" crea il file avviabile "arch/i386/boot/bzImage",
quello che viene passato a Grub o a Lilo all'avvio del sistema.  Le
immagini avviabili per altre piattaforme in genere non si chiamano
"bzImage", per cui il comando di compilazione sarà presumibilmente
diverso. Per esempio per le piattaforme sparc e sparc64 è sufficiente
"make", in quanto il boot loader in quel caso può leggere
direttamente un eseguibile ELF (quindi "vmlinux"). Nel caso del
PowerPC, invece, "make bzImage" è un comando accettato: la regola
"bzImage" è definita per uniformarsi al mondo PC e alla
documentazione già disponibile che parla sempre e solo di bzImage,
assumento di riferirsi al mondo PC.

È 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
sportchi 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 che permette 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 piu` 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. Nel riquadro 4 
è riportato
un estratto dei messaggi stampati durante la compilazione.

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 pero` 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 constesto, è realizzato nel Makefile principale,
che verifica dove e` 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 mone univoco alle
variabili di ambiente che influenzano la compilazione del kernel,
permettendo pero` di modificare il comportamento di make 
scribendo direttive molto piu` brevi sulla riga di comando di make.
Questo perche`, si sa, il programmatore e` pigro.

La piu` interessante di queste variabili di ambiente credo sia
KBUILD_OUTPUT, abbreviabile come "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 puo` 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 e` sicuramente un grosso vantaggio in termini di spazio su disco.

* Le dipendenze

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 e` 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 puo` 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.
analizza la directory.

* Cross-compilazione

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 e`
riportato dai messsaggi stampati durante la compilazione, come
si vede nel riquadro 4.

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 e` "?=", 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).

* I moduli

La compilazione dei moduli rimane immutata rispetto alle precedenti
versioni: "make modules" e "make modules_install" sono i comandi usati
per compilare ed installare i moduli. Il meccanismo è però 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.

Alessandro Rubini

<Riquadro 1 - Dove scaricare i sorgenti del kernel>
<Riquadro 2 - Aggiornare tramite patch>
<Riquadro 3 - lex e yacc>
<Riquadro 4 - output di compilazione>



<Riquadro 1 - Dove scaricare i sorgenti del kernel>

Le versioni "vanilla" di Linux, cioe` quelle rilasciate da Linus
Torvalds, sono disponibili su ftp.kernel.org e sui mirror nazionali.
Altri autori distribuiscono versioni modificate, per esempio
aggiungendo funzionalita` 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" e` 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 e` 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
creera` una directory chiamata "linux-2.6.0-test9", di circa 200MB.



<Riquadro 2 - Aggiornare tramite patch>

Una volta scaricato un tar contenente i sorgenti del kernel, il modo
migliore per passare alla versione successiva e` scaricare il file di
"patch", cioe` 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



<Riquadro 3 - lex e yacc>

Il programma "lex" e` 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) e` 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 e` 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 gia`
presenti i file di output generati da flex e bison, per cui non e`
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.



<Riquadro 4 - output di 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