Il Kernel di Linux
2004
Il kernel Linux
Quando ci si riferisce a Linux si pensa spesso ad uno dei tanti CD che si possono facilmente trovare allegati ad una rivista di informatica e che contengono una distribuzione Linux completa e pronta da usare.
In realta’, il nome Linux designava in origine il kernel scritto da Linus Torvalds sulla base di Minix, un sistema operativo creato a fini didattici da Tanenbaum, e venne in seguito esteso, nel gergo popolare, per definire anche il sistema operativo GNU/Linux e le sue varie distribuzioni.
Il Kernel e’ lo strato software piu’ basso del sistema, a diretto contatto con l’hardware, e si occupa di gestire i processori, di assegnare le risorse ai vari processi che vengono eseguiti, della gestione della memoria, del caricamento dei driver per le periferiche, i protocolli di rete, e cosi’ via.
In questo modo i programmi che girano nello spazio utente non hanno bisogno di sapere quale particolare periferica e’ installata fisicamente sulla macchina, possono concentrarsi esclusivamente sul loro lavoro, poiche’ ogni istruzione che dovra’ essere inviata all’hardware verra’ gestita dal kernel insieme a quelle di tutte le altre applicazioni e del kernel stesso.
Appare evidente che per poter svolgere il suo compito e permettere alle applicazioni di essere eseguite, il kernel deve essere caricato prima di ogni altra cosa. A questo pensano il BIOS della macchinae il bootloader. Il BIOS, dopo aver eseguito le inizializzazioni di base e aver testato la memoria, cerchera’ di eseguire il codice contenuto nel primo settore di ognuno dei dispositivi a blocchi in cui gli abbiamo detto di cercare (dischi fissi, floppy o CD-ROM), fermandosi appena ne avra’ trovato uno valido. Nel caso in cui avremmo installato Linux sul disco fisso, sul primo settore di quel disco si trovera’ un bootloader (di solito lilo o grub) che si occupera’ di caricare il kernel.
Struttura del kernel
La forma in cui si trova il kernel Linux e’ quella di un file, detto immagine del kernel, solitamente contenuto nella directory /boot. Se abbiamo compilato il kernel includendo i vari driver direttamente al suo interno, allora diremmo che abbiamo creato un kernel monolitico.
Nei sistemi desktop questa pratica e’ poco seguita in quanto un kernel del genere, dovendo necessariamente includere il supporto a tutto quello che intenderemo usare, finisce per raggiungere dimensioni considerevoli. Inoltre, ogni singolo driver verrebbe caricato in memoria anche quando non ci serve realmente, e questo comporta un aumento della memoria allocata per il kernel a danno di quella a disposizione dei programmi che girano nello spazio utente.
I kernel monolitici vengono utilizzati generalmente su macchine che svolgono compiti specifici, di solito quindi dei server, i quali non hanno bisogno di caricare all’occorrenza dei driver perche’ quello che hanno installato gli serve solitamente fin dall’inizio, e non gli serve null’altro. In questo modo si puo’ evitare il rischio che dei malintenzionati dall’esterno possano sfruttare una debolezza nel meccanismo di caricamento dei driver, potendo cosi’ dare qualche dispiacere all’amministratore del sistema.
Nell’uso comune, invece, si preferisce creare dei kernel modulari, in modo che le specifiche funzionalita’ vengano caricate in memoria solo quando occorrono, senza caricare inutilmente il sistema. Inoltre, possiamo in questo modo cambiare la configurazione di un driver semplicemente scaricandolo e ricaricandolo con parametri diversi.
Questo e’ ancor piu’ vero se si deve preparare un kernel che debba poter funzionare su diverse configurazioni hardware, come quelli che vengono inclusi nelle comuni distribuzioni del sistema GNU/Linux. In tal caso il kernel non si limita a risiedere nel file immagine, ma ad esso vanno affiancati i moduli da caricare a runtime, che possiamo trovare nella directory /lib/modules/<numero_versione>. Si capisce che in questo modo possiamo tenere sulla nostra macchina diversi kernel, purche’ ognuno di essi abbia un numero di versione diverso dall’altro, in quanto in /lib/modules avremo una directory per i moduli di ogni kernel; l’unico limite e’ rappresentato dalla capienza del disco fisso.
Ricompilazione del kernel
Nel momento in cui installiamo una distribuzione qualunque ci viene fornito un kernel generico, funzionante di solito in tutte le configurazioni hardware, dotato di tutti i moduli che potrebbero servire, e a volte con qualche piccola modifica rispetto al kernel originale per implementare qualche funzionalita’ aggiuntiva. Nella maggior parte dei casi questo kernel svolge egregiamente il suo compito, ma a volte e’ necessario dover compilare un kernel personale per poter sfruttare appieno tutte le potenzialita’ del nostro sistema, o anche per poter sfruttare un particolare dispositivo, o infine per poter semplicemente avere un kernel scaricato di tutto quello che sappiamo non potra’ mai servirci, aumentando cosi’ l’efficienza globale del sistema.
Per poter compilare un kernel occorre avere detrminati requisiti di base:
– il compilatore C di GNU, ‘gcc’
– il pacchetto ‘make’, sempre di GNU
– una serie di pacchetti generalmente inclusi in ogni distribuzione, come ad esempio le binutils e le util-linux
– vari pacchetti in funzione dei supporti che si desidera includere (ad esempio e2fsprogs se si vuole includer il supporto per ext2)
– eventualil tool per la gestione dei moduli (modutils per i kernel fino al 2.4.x, module-init-tools per quelli della serie 2.6.x)
– pacchetti necessari ai tool di configurazione (vedi piu’ avanti)
La versione corretta di ogni pacchetto e una lista completa dei requisiti minimi e’ contenuta nel file Documentation/Changes all’interno dei sorgenti del kernel.
Per quanto riguarda i sorgenti abbiamo due possibilita’: installare il pacchetto contenente i sorgenti del kernel della nostra distribuzione, oppure scaricare i sorgenti ‘vanilla’, quelli originali di Linus, dal sito http://www.kernel.org . Questi ultimi si presentano sotto forma di archivio compresso, per cui occorrera’ copiarli nella directory scelta e scompattarli con un ‘tar xzvf <nome_pacchetto>’.
La directory standard per i sorgenti del kernel e’ /usr/src/linux, ma si puo’ optare per una directory qualsiasi purche’ si faccia un collegamento simbolico /usr/src/linux che punti a quest’ultima (di solito si usa /usr/src/linux che punta a /usr/src/linux-<versione>).
Configurazione dei sorgenti
Una volta scompattato il file contenente i sorgenti, prima di iniziare la compilazione vera e propria dovremo configurarli. Esistono diversi metodi per la configurazione del kernel, e per usare ognuno di essi sara’ necessario aver installato determinati software, in modo che sia possibile al programma di configurazione creare l’interfaccia per interagire con l’utente.
L’unica eccezione in questo senso e’ rappresentata dal comando ‘make config’; per configurare i sorgenti con questo comando non e’ necessario nessun software aggiuntivo, in quanto questi ci presentera’ una semplice interfaccia testuale, ponendoci una domanda per ogni voce di configurazione. Per ogni domanda possiamo rispondere con uno di quattro possibili tasti: ‘y’ dira’ al configuratore di includere il relativo driver/supporto nell’immagine del kernel, ‘n’ gli dira’ di non includerlo, con ‘m’ (presente solo se avremo risposto affermativamente alla domanda che ci chiede se vogliamo abilitare il supporto ai moduli) si fara’ in modo che il driver/supporto venga compilato come modulo esterno, e con ‘?’ otterremo un messaggio di aiuto che ci spiega nei dettagli a cosa si rifersce quella particolare opzione. Il difetto principale di ‘make config’ e’ che non si puo’ tornare indietro per correggere gli eventuali errori, per cui e’ necessario ripetere l’intera procedura se abbiamo un ripensamento. Questo lo rende effettivamente poco usato da chiunque.
In alternativa si possono configurare i sorgenti con ‘make menuconfig’, una simpatica interfaccia a menu in modalita’ semigrafica (utilizzabile quindi da console o da terminale) che utilizza le librerie ncurses (che dovremo avere installate). Con ‘make menuconfig’ il lavoro e’ decisamente piu’ facile. Le opzioni di configurazione sono raggruppate in vari menu che potremo navigare semplicemente con i tasti freccia su/giu. Nella parte bassa della finestra ci sono tre pulsanti, selezionabili con i tasti freccia dx/sx, che rispettivamente ci fanno entrare in un sottomenu, ci fanno tornare al menu precedente, o visualizzano il testo di aiuto. Posizionati su ciascuna opzione possiamo selezionarla, deselezionarla o marcarla per essere compilata come modulo, rispettivamente con i tasti <invio>, <n> e <m>.
Purtroppo al momento la lingua utilizzata per i nomi delle opzioni e per i testi di aiuto e’ l’inglese, ma esiste un progetto per la traduzione di questi testi in italiano mantenuto dal sottoscritto, quindi state sintonizzati 🙂
Esistono inoltre altre due possibilita’ di configurazione, che utilizzano delle finestre in modalita’ grafica e che quindi hanno bisogno del server X per poter funzionare.
La prima e’ ‘make xconfig’, per la quale avremo bisogno di installare le librerie grafiche QT (quelle che usa KDE, per intenderci). La seconda e’ ‘make gconfig’, che invece richiede le librerie GTK utilizzate dall’ambiente grafico GNOME.
Entrambi i comndi ci presentano un’interfaccia a menu molto simile a quella di ‘make menuconfig’, quindi la scelta di una di queste e’ puramente una questione di preferenza estetica.
Possiamo fare in modo che il nostro kernel abbia un numero di versione diversa da quello che abbiamo gia’, pur trattandosi degli stessi sorgenti, semplicemente editando il file ‘Makefile’ presente nella directory radice dei sorgenti stessi.
Nelle prime righe di questo file vengono definite le variabili che identificano il numero di versione, ma mentre le prime tre (VERSION, PATCHLEVEL e SUBLEVEL) non vanno toccate, la quarta (EXTRAVERSION) puo’ essere modificata a piacimento. Questo potra’ esserci utile nel momento in cui dovremo avviare il nuovo kernel senza per questo eliminare il vecchio.
Compilazione e installazione
Una volta configurato i sorgenti possiamo passare alla compilazione vera e propria. Qui si tratta solo di pazienza, in quanto bastera’ dare semplicemente il comando ‘make bzImage’. Se abbiamo scelto di compilare dei moduli occorrera’ dare anche il comando ‘make modules’ per compilarli, e ‘make modules_install’ per installare i moduli nella directory /lib/modules/<versione>, creando inoltre dei file contenenti le dipendenze tra i vari moduli. E’ possibile dare questi comandi tutti insieme, con una stringa come ‘make bzImage && make modules && make modules_install’, in modo da non doverci preoccupare di essere presenti nel momento che uno dei comandi avra’ terminato il suo compito.
Dopo un po’ di tempo, dipendente dalle prestazioni della macchina sulla quale stiamo compilando e dal numero di funzionalita’ incluse in fase di configurazione, se tutto va per il verso giusto avremo il nostro nuovo kernel.
Per poter avviare la macchina con il nuovo kernel dovremo copiare l’immagine, e preferibilmente anche la mappa dei simboli, e reinstallare il boot-loader. La mappa dei simboli e’ il file ‘System.map’ che si trova nella directory radice dei sorgenti (verosimilmente /usr/src/linux), l’immagine del kernel si puo’ invece trovare nella directory /usr/src/linux/arch/<tipo_architettura>/boot/ con il nome ‘bzImage’. La destinazione di questi file dovrebbe essere la directory /boot. Se in /boot e’ gia’ presente il file ‘System.map’ del vecchio kernel conviene rinominare entrambi i file in ‘System.map-<versione>’.
Fatto questo si va ad editare il file di configurazione del bootloader (qui si assume che come loader si utilizzi lilo, quindi il file relativo e’ /etc/lilo.conf) aggiungendovi, insieme alla voce per l’avvio del vecchio kernel, anche una voce anche per il nuovo kernel. Si puo’ prendere spunto dalla voce gia’ presente per la composizione della nuova, tenendo presente che una voce per l’avvio di un kernel Linux inizia con la riga ‘image = /boot/<file_immagine>’ ed e’ seguita da varie righe per la definizione della modalita’ di avvio del kernel. Se tra le opzioni generali, all’inizio del file, non e’ presente una riga contenente la parola ‘prompt’ (che indica al loader di chiederci con quale kernel avviare la macchina), occorrera’ aggiungerla.
Dopo aver salvato il file sara’ sufficiente lanciare il comando ‘lilo -v’ per installare il nuovo loader. Al prossimo avvio potremo scegliere di partire con il nostro nuovo kernel, e se tutto e’ andato per il verso giusto, e il kernel funziona senza problemi, potremo editare (se lo vogliamo) nuovamente il file di configurazione di lilo rimuovendo il riferimento al kernel che non usiamo piu’, e ridare nuovamente il comando ‘lilo -v’. Potremo ovviamente rimuovere anche i file relativi al kernel in disuso (l’immagine del kernel, la mappa dei simboli, e la directory con tutti i moduli) e goderci appieno il nostro kernel personalizzato.