Parte I : creazione ambiente di lavoro

Transcript

Parte I : creazione ambiente di lavoro
SOLAB L2 : Note costruzione ambiente per esperimenti di sviluppo kernel linux serie 2.6.x
Parte I : creazione ambiente di lavoro
1. Download debian-6.0.7-i386-netinst.iso da repository immagini di installazione debian.
Versione kernel : 2.6.32-5-686 .
( origine iso netinst : http://ftp.sunet.se/pub/Linux/distributions/debian-cd/6.0.7/i386/iso-cd/)
2. In folder qemu creare un disco virtuale da 4Gb. Comando (prompt comandi windows) :
qemu.img.exe create solabl2_disk.img 4096M
3. Spostare il file creato al punto 3 nello stesso folder in cui teniamo l'immagine solab.iso
4. Nella stessa directory in cui si trova il file eseguibile qemu-system-i386w.exe copiate il file BAT
di avvio di qemu che esegue utilizzando solab.iso , rinominatelo in solabL2netinst.bat e modificate
come segue le opzioni di avvio di qemu-system-i386w.exe come segue:
REM ################################################
REM # Boot disk image
REM # Adapt "-k fr" to you keyboard
REM # X11, nic and soundhw were tested and are working!
REM # try madplay 20thfull.mp2 or xinit
REM # Added '-usb -usbdevice tablet' for Seamless mouse
REM ################################################
START qemu-system-i386w.exe -L Bios -k it ^
-rtc base=localtime,clock=host ^
-cdrom ..\debian-6.0.7-i386-netinst.iso ^
-hda ..\solabl2_disk.img -boot d -net nic,model=e1000 -net user ^
-m 1024 -no-reboot
5. fate doppio click sul file solabL2netinst.bat, questo avvia il processo di installazione.
Note sul processo di installazione:
a) Impostazione lingua, scelta disco, partizionamento e installazione bootloader
Scegliete come lingua di installazione Italiano, e scegliete la tastiera italiana. Al momento del
partizionamento del disco scegliete l'opzione più semplice: utilizza intero disco. Al momento qemu
è stato avviato con un solo disco (opzione -hda) quindi non c'è modo di sbagliarsi. Accettate le
opzioni di partizionamento generate in automatico dall'installer. Confermate che volete installare il
bootloader (GRUB2) nel MBR del disco.
b) Quando chiede di impostare un mirror inserire manualmente ftp.debian.org e non impostare
nessun proxy. Durante la selezione dei package da installare selezionate solamente 'Standard
system utilities' (deselezionate 'Graphical desktop environment'). Poerate a termine
l'installazione. Una volta che il sistema cerca di riavviarsi lasciamolo fare (ma non si riavvierà …
non è machcina fisica ma emulata).
Durante l'installazione ho impostato d3brp4sswd come password di root e ho creato un utente di
nome user con password userpwd.
6. Ora dobbiamo ripetere i passaggi effettuati al passo 4 creando un nuovo file bat di avvio che non
esegua il boot da CD ma da disco fisso (virtuale). Nella stessa directory in cui si trova il file
eseguibile qemu-system-i386w.exe copiate il file BAT di avvio di qemu che esegue utilizzando
solab.iso , rinominatelo in debian-670-solab2.bat e modificate come segue le opzioni di avvio di
qemu-system-i386w.exe come segue:
REM ################################################
REM # Boot disk image
REM # Adapt "-k fr" to you keyboard
REM # X11, nic and soundhw were tested and are working!
REM # try madplay 20thfull.mp2 or xinit
REM # Added '-usb -usbdevice tablet' for Seamless mouse
REM ################################################
START qemu-system-i386w.exe -smp 2 -L Bios -k it ^
-rtc base=localtime,clock=host -serial stdio -vga std ^
-cdrom ..\debian-6.0.7-i386-netinst.iso ^
-hda ..\solabl2_disk.img -net nic,model=virtio -net user ^
-m 1024 -no-reboot
Avviate il sistema (doppio click sul file debian-670-solab2.bat ). Notare che il cdrom lo legge
ancora ma non fa più il boot da CD (non c'è più -boot d). Fino a questo momento tutti i file utilizzati
durante l'installazione sono stati prelevati da debian-6.0.7-i386-netinst.iso . Ora è il momento di
impostare le interfacce di rete in modo da poter installare software da remoto. Come utente root
modificare il file /etc/network/interfaces impostando la primary network interface come segue:
# The primary network interface
auto eth0
allow-hotplug eth0
iface eth0 inet dhcp
Modificare (sempre come root) il file /etc/apt/sources.list aggiungendo/decommentando (in caso di
necessità) :
deb http://ftp.debian.org/debian/ squeeze main contrib non-free
deb http://security.debian.org/ squeeze/updates main
deb-src http://security.debian.org/ squeeze/updates main
Una volta modificato il file (come utente root) usate il comando : apt-get update per aggiornare la
lista delle sorgenti software registrare nel sistema. Ora arrestate il sistema. Come utente root
utilizzate il comando: shutdown -h now .
Riavviate il sistema (doppio click sul file debian-670-solab2.bat . Come utente root utilizzare i
seguenti comandi per installare alcuni software utili :
#
#
#
#
#
#
#
aptitude install gcc
aptitude install nasm
aptitude install sudo
aptitude install build-essentials
aptitude install wget
aptitude install tar
aptitude install perl
Aggiungere l'utente user al gruppo sudo. Come utente root utilizzare il comando:
# adduser user sudo
Da questo momento anche l'utente user può eseguire comandi come superutente semplicemente
anteponendo sudo al coando che richiede i privilegi di root.
Parte II : ottenere i sorgenti del kernel
Innanzitutto verifichiamo quale kernel è attivo attualmente nel sistema linux debian squeeze 6.0.7 :
$ uname -r
2.6.32-5-686
Siamo interessati al kernel linux 2.6.32.5 . Procuriamoci i sorgenti. Se non abbiamo fatto login
come utente user facciamo logout (comando : exit ), e autentichiamoci come utente user con
password userpwd.
Nella home di user creiamo una directory di nome kerneldev e spostiamoci al suo interno :
$ mkdir kerneldev
$ cd kerneldev
Ora scarichiamo i sorgenti del kernel su cui lavoreremo :
$ wget http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.32.5.tar.bz2
Ed estraiamo il folder compresso (ci mette un po' di tempo) :
$ tar xvjf linux-2.6.32.5.tar.bz2
Alla fine di questo processo otterremo una directory di nome linux-2.6.32.5.tar.bz2 all'interno della
quale troverete tutti i sorgenti del kernel linux. Prima di provare a compilare il kernel è necessario
preparare l'ambiente di sviluppo in modo da assicurarci che la compilazione di un kernel linux non
richieda tempi geologici. Dopo tutto siamo in un ambiente x86 emulato e con poca RAM …
Osservazione: creare un nuovo kernel a partire dai sorgenti è un processo che richiede la
compilazione di moltissimi file (migliaia di file) ma le modifiche che vorremmo introdurre nel
kernel non coinvolgono una parte molto consistente dei sorgenti.
Sarebbe utile poter effettuare una sola volta l'intera compilazione dell'intero kernel e, dopo aver
effettuato un numero relativamente basso di modifiche, ricompilare solo i sorgenti modificati da noi
riutilizzando il risultato delle compilazioni precedenti dei file che non sono cambiati dall'ultima
compilazione. Quello che servirebbe sarebbe una sorta di “cache” per i compilatori. Questo
strumento esiste (meno male) e si chiama ccache . Installiamolo e configuriamolo.
$ sudo apt-get install ccache
$ vim ~/.bashrc
Aggiungere in fondo a .bashrc quanto segue e salvare le modifiche :
# ccache
export CCACHE_DIR="/home/user/.ccache"
export CC="ccache gcc"
export CXX="ccache g++"
export PATH="/usr/lib/ccache:$PATH"
Aggiungendo ccache alla propria variabile di ambiente PATH , ogni invocazione del compilatore
gcc verrà "intercettata" da ccache (che si trova in /usr/lib/ccache ). Attivare le modifiche appena
introdotte in .bashrc :
$ source ~/.bashrc
Ora impostiamo la dimensione massima della cache ( a 2 Gb ):
$ ccache -M 2G
Per verificare in un qualsiasi momento lo stato della cache procediamo come segue:
Alt + F2
$ watch -n1 -d ccache -s
(otteniamo un nuovo terminale, login come utente user)
(visualizzazione, ogni 1 sec, dello stato della cache ccache)
Quando vogliamo interrompere, utilizziamo Ctrl+C
Per ritornare al primo terminale : Alt + F1
Parte III : compilare il kernel
III.a ) Configurazione :
Compilare il kernel è un processo relativamente semplice. Tale processo si può suddividere in due
passaggi principali: compilazione del kernel e compilazione dei moduli. Riguardo alla compilazione
del kernel va notato che il folder contenente i sorgenti contiene sia codice generico che codice
architettura specifico. Ovviamente dovremo impostare l'architettura per la quale stiamo creando il
nuovo kernel (il codice specifico per altre architetture non verrà considerato durante il processo di
compilazione).
I moduli sono file che, dopo essere stati compilati ed inseriti in /lib/modules/<nomeversionekernel>
possono essere caricati e rimossi dinamicamente nel kernel su richiesta in modo da permettere
l'utilizzo di funzionalità aggiuntive senza essere costretti ad includere tutto nel kernel, fatto che
risulterebbe in in kernel molto grande.
Il problema fondamentale è che i sorgenti del kernel linux contengono una gran quantità di driver e
moduli. E consideriamo anche che questo problema è esacerbato dal fatto che i kernel delle
principali distribuzioni linux (ad es. Debian, Ubuntu, Suse, ecc.) contengono una quantità di codice
ancora maggiore rispetto al kernel “vanilla” poiché tentano di supportare la maggior quantità di
hardware possibile.
E' evidente che non ha senso compilare tutti questi driver, moduli ecc. poiché la maggior parte di
essi non verrebbe comunque utilizzata.
Per avere un'idea del numero di moduli attualmente caricati nel kernel possiamo utilizzare il
comandi list modules ( lsmod ) che produce un output formato da 3 colonne: Module, Size, Used
by corrispondenti rispettivamente, a nome modulo, dimensione e numero (e nomi) di altri moduli
che utilizzano lo specifico modulo riportato nella riga corrente dell'output.
Una volta scompattato il folder contenente i sorgenti del kernel occupa 542 Mb … deve esistere un
modo per specificare quanti e quali di questi sorgenti vogliamo compilare, siano essi moduli o parte
del kernel linux.
Per decidere cosa compilare, e se rendere disponibile specifiche feature sottoforma di moduli o di
codice incluso direttamente nel kernel linux si utilizza un file di configurazione : il file .config .
Attenzione che questo è un file nascosto (il suo nome inizia con . ) .
E qui arrivano i problemi: modificare un file di configurazione in modo da produrre un kernel che
funzioni una volta riavviata la macchina (anche se emulata) è di gran lunga la parte più difficile del
processo di compilazione di un kernel (con o senza modifiche inserite da noi). Il numero di righe
presente nel file .config del kernel installato (reperibile in /boot/config-2.6.32-5-686) è 4980 . E
ogni riga (a parte alcune righe iniziali di commento) ha questo formato:
# NOMEVARCONFIGURAZONE is not set
NOMEVAR=y
NOMEVAR=m
NOMEVAR=stringa
(quando una determinata feature non è attiva)
(quando una determinata feature è attiva)
(se la feature sarà compilata come modulo)
(per scegliere un valore in un set di opzioni)
Ad esempio (queste sono righe vere):
# CONFIG_64BIT is not set
CONFIG_X86_32=y
# CONFIG_X86_64 is not set
CONFIG_X86=y
CONFIG_OUTPUT_FORMAT=”elf32-i386”
...
Editare a mano questo file è davvero difficile. Esistono infatti relazioni di interdipendenza tra le
varie “feature” (parametri di compilazione). Esistono dei tool specializzati per gestire questo
importante file e vengono invocati da make il comando di alto livello che si occupa di gestire in
maniera appropriata il processo di compilazione (sia nel caso di progetti aventi un source tree di
complessità media cje nel caso di source tree estremamente complessi come quello del kernel
linux). Per modificare il file .config possiamo utilizzare il comando:
$ make menuconfig
dopo essere entrati nella directory linux-2.6.32.5 . Ma prima di occuparci di modificare il file
.config dovremmo procurarcene uno che sappiamo già, a priori, essere in grado di produrre un file
funzionante per la macchina (anche se emulata) sulla quale dovrà essere eseguito il kernel.
Ovviamente il moglior candidato in questo senso è il file di configurazione del kernel che sta
girando in questo momento sulla nostra macchina emulata.
$ cp /boot/config-2.6.32-5-686 .
(non dimenticate il punto alla fine … copia nel folder corrente)
Abbiamo già visto come le righe del file di configurazione corrispondenti a feature (opzioni)
disabilitate iniziano con # . Verifichiamo quante sono le righe “commentate” nel file di
configurazione del kernel che abbiamo ricevuto durante l'installazione.
$ grep '#' /boot/config-2.6.32-5-686 | wc –l
1150 (su 4980)
Una parte consistente di queste feature non sono necessarie (e aumenterebbero di molto i tempi di
compilazione). Dobbiame cercare di “snellire” il kernel adattandolo alla nostra macchina (emulata).
Prima di tutto facciamo una copia di backup del file .config :
$ cp .config .config.bkp
Per snellire il kernel possiamo utilizzare uno degli script forniti insieme ai sorgenti del kernel che
abbiamo scaricato : streamline_config.pl :
$ sudo perl scripts/kconfig/streamline_config.pl > config_strip
Questo script effettua dei test per identificare i componenti (anche hardware) attivi nel sistema in
cui viene eseguito, verifica quali moduli sono attualmente attivi (utilizzando lsmod) e crea un file di
configurazione in cui tutte le feature non necessarie sono disabilitate. Nel comando precedente
l'output è stato rediretto nel file config_strip. Sostituiamo il file .config corrente (di cui, comunque,
avevamo fatto un backup in precedenza) con il file che abbiamo appena creato:
$ mv config_strip > .config
Prima di procedere con la compilazione vera e propria dobbiamo assicurarci che tutte le opzioni di
configurazione necessarie per compilare il kernel i cui sorgenti sono contenuti nel source tree che
abbiamo scaricato siano anche presenti nel file .config . In fondo siamo partiti dal file di
configurazione del kernel che abbiamo ricevuto in fase di installazione (che è stato modificato dai
produttori della distribuzione debian squeeze 6.0.7). Per effettuare questo step di configurazione
utilizziamo il comando:
$make oldconfig
che verifica la presenza di tutti i parametri di configurazione attesi e, in caso di assenza, ci propone
dei valori compatibili con il nostro file .config corrente. Ad ogni richiesta rispondere sempre con il
solo INVIO (che equivale ad utilizzare l'opzione di default).
Dopo questi step di configurazione il nostro file .config è passato a 2512 righe. Di queste :
$ grep '#' .config | wc -l
1699
sono righe di commento o opzioni non attive.
$ grep '=m' .config | wc -l
33
rappresentano feature del kernel che verranno compilate come moduli.
Ora, muniti di un file .config adattato alla macchina su cui dovrà essere eseguito il kernel e di
ccache possiamo procedere alla prima compilazione del kernel.
III.b) Compilazione :
Prima di tutto compiliamo il kernel. Il comando da utilizzare è:
$ time make -j5 bzImage
Questo comando produce un file in arch/x86/boot/bzImage (il kernel compresso). Il comando di
compilazione invocato mediante make esegue fino a 5 job contemporaneamente (opzione -j5).
Grazie a time è possibile verificare che impiega 166 minuti circa per essere completato (nel sistema
emulato).
Ora compiliamo i moduli. Il comando da utilizzare è:
$ time make -j5 modules
I moduli vengono compilati in circa 21 minuti.
Parte IV: installazione del nuovo kernel
IV.a)
Il kernel appena prodotto va installato nel sistema. Per copiare il kernel (insieme al file System.map
e ad altri file) nelle posizioni appropriate nel sistema utilizzare il comando:
$ sudo make install
Questo copierà i file ottenuti dalla compilazione (del kernel) e creerà i seguenti file nel sistema:
/boot/config-2.6.32.5
/boot/System.map-2.6.32.5
/boot/vmlinuz-2.6.32.5
E' assolutamente necessario che alcuni moduli siano disponibili al kernel al momento dell'avvio. In
particolare devono essere disponibili i moduli che permettono al kernel di riconoscere e montare un
file system, altrimenti non sarà possibile montare il file system / . Per risolvere questo problema è
possibile costruire un'immagine da caricare in RAM (un initramfs, ossia un filesystem conpresso da
caricare al momento dell'avvio) contenente i moduli appena compilati. Per effettuare questa
operazione è possibile utilizzare questo comando:
$ sudo mkinitramfs -o /boot/initrd.img-2.6.32.5 /lib/modules/2.6.32.5
Il comando legge il contenuto della directory /lib/modules/2.6.32.5 e crea il file immagine
/boot/initrd.img-2.6.32.5 .
Per installare i moduli utilizziamo il comando:
$ sudo make modules_install
Questo creerà un nuovo folder :
/lib/modules/2.6.32.5/
contenente tutti i moduli compilati.
IV.b)
Sembrerebbe tutto a posto ma manca ancora un passaggio. Far capire al bootloader come caricare il
nuovo kernel durante l'avvio. Il bootloader installato nel sistema è Grub2. Una volta compilati ed
installati in maniera appropriata kernel e moduli e una volta generata l'immagine da caricare in
RAM è sufficiente chiedere al sistema di aggiornare il contenuto del menù iniziale. Durante questa
operazione il sistema cercherà tutti i kernel validi in /boot e provvederà ad aggiungere un elemento
nel menù di avvio del sistema. E' possibile verificare il file di configurazione del bootloade aprendo
il file /boot/grub/grub.cfg con vim . Questo file non va editato direttamente. Il comando da
utilizzare per agigornare la ocnfigurazione di Grub2 è il seguente:
$sudo update-grub
Se vogliamo ridurre ulteriormente la lista di kernel disponibili all'avvio in Grub2 possiamo anche
editare a mano (come root) il file /etc/default/grub levando il # iniziale (e quindi attivando o, come
si dice in gergo, “decommentando”) dalla riga :
#GRUB_DISABLE_LINUX_RECOVERY=true
prima di utilizzare il comando update-grub.
Possiamo verificare la presenza del nuovo kernel esaminando il contenuto di /boot/grub/grub.cfg
Ora arrestiamo il sistema ( comando: sudo shutdown -h now ) e, al prossimo avvio (doppio click sul
file debian-670-solab2.bat ) , utilizziamo il nuovo kernel. Dovremmo trovare solo 2 voci : la
prima corrisponde la nuovo kernel mentre la seconda è quella del kernel derivante dall'installazione.
Parte V: Osservazioni post avvio del nuovo kernel
Innanzitutto il kernel si avvia quindi tutto ha funzionato a dovere. Ora possiamo indagare sulle
differenze di un kernel ottenuto mediante installazione e di un kernel compilato da noi. Innanzitutto
proviamo a confrontare le dimensioni del nuovo kernel compilato e del kernel dell'installazione di
debian 6.0.7 .
$ ls -lah /boot/vmlinuz-2.6.32.5
2,2M
Questo file è il bzImage che avevamo prodotto durante la compilazione. E' stato copiato in /boot/
con un nome diverso vmlinuz-<versionekernel> dal comando make install.
$ ls -lah /boot/vmlinuz-2.6.32-5-686
2,2M
Quindi, almeno fin qui, nessuna grande differenza (nonostante il lavoro che abbiamo fatto per
snellire il kernel). Quindi potevamo farne a meno? E, soprattutto, dove sono tutti i famosi
driver/moduli aggiunti dai produttori della distribuzione debian per supportare il maggior numero
possibile di componenti hardware?
Se non sono nel kernel li avranno compilati come moduli. O, almeno, questo avrebbe senso. Per
verificare possiamo controllare la dimensione degli initramfs.
$ ls -lah /boot/initrd.img-2.6.32.5
2,2M
(questo lo avevamo prodotto noi)
$ ls -lah /boot/initrd.img-2.6.32-5-686
8,4M
In definitiva l'utilizzo di streamline_config.pl ci ha permesso di risparmiarci la compilazione di più
di 6M di moduli.
Il secondo punto da verificare è se l'utilizzo di ccache permette effettivamente di compilare in
maniera più veloce.
Come è possibile notare dopo la compilazione del kernel e dei moduli in effetti sono presenti 3761
file in cache (44.0 Mbytes) . E' quindi ipotizzabile che sia possibile unrisparmio di tempo durante la
compilazione del kernel. Non ci resta che verificare.
Dato che avevamo utilizzato il comando time (cfr. III.c, Compilazione) sappiamo che il tempo
richiesto è circa 166 minuti. Parte di questo tempo è stato pagato pe rla costruzione della cache di
ccache ma il fattore determinante per i tempi di compilazione sono costituiti dalla compilazione dei
numerosi file presenti nel source tree del kernel. Vediamo quanto tempo richiede ora la
compilazione di bzImage :
user@solabL2:~/kerneldev/linux-2.6.32.4$ time make -j5 bzImage
il tempo richiesto per completare la compilazione è 4 min 4.584 sec . In effetti ccache può essere di
aiuto per velocizzare gli esperimenti con il kernel.
A questo punto si arresta il sistema e viene messa da parte una copia del file del disco virtuale (
solabl2_disk.img ) contenente il sistema la cui costruzione è stata descritta in queste note
unitamente al file di avvio post installazione (debian-670-solab2.bat) e del file di avvio per
l'instalalzione (unimilive-debian-netinst.bat). Viene inoltre salvata una copia del file iso utilizzato
durante il processo di instalalzione dle sistema (debian-6.0.7-i386-netinst.iso).