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