Creare una distro Linux per una scheda elettronica
Creare una distribuzione Linux personalizzata per una scheda elettronica può essere un’ardua impresa, persino per un appassionato di Linux o un esperto di microcontrollori: ci sono diverse parti in movimento, e sincronizzarle richiede una conoscenza approfondita dell’ecosistema Linux.
Ma non temere: questo post ti fornirà tutte le indicazioni necessarie per far apparire quell’agognato prompt di login sulla tua scheda quanto più semplicemente possibile. E’ necessario soltanto del tempo, voglia di imparare e qualche nozione di base su Linux e i suoi strumenti di sviluppo.
Un mare di alternative
Lungo la strada che conduce alla creazione della nostra distribuzione (o distro) Linux, ci troviamo immediatamente ad un bivio: creare l’intero sistema da zero e completamente a mano, o cercare qualche strumento che possa aiutarci in questa ardua impresa?
La prima opzione risulta certamente in un’esperienza gratificante, sebbene estremamente lunga e complicata. Ha il vantaggio di fornire all’utente uno sguardo veloce ai meccanismi interni della propria macchina, da cui può trarre diverse perle di saggezza. Tuttavia è anche un percorso che richiede diverse conoscenze avanzate sul funzionamento di un sistema operativo, e sicuramente non è la strada più efficiente. Nonostante ciò, esistono diversi progetti online che si propongono di guidare l’utente in quest’avventura: LFS è uno di questi.
La seconda strada è invece quella regolarmente battuta. Nel corso degli anni sono stati creati e perfezionati diversi strumenti per ovviare al problema della realizzazione di una distro personalizzata. Alcuni di questi sono semplici da usare ma mancano di flessibilità, mentre altri riescono a fare qualsiasi cosa, dati abbastanza tempo ed esperienza. Sono noti come Linux build systems, e si pongono come obiettivo quello di fare per noi tutto il lavoro sporco: scaricare i sorgenti necessari, ottenere una toolchain, compilare tutti i pezzi e metterli insieme. L’unica cosa che richiedono all’utente è di fornire una configurazione ed avviare la build: del resto si occupano loro!
In questo post intraprenderemo la strada più breve ed useremo uno di questi build systems. Nonostante ce ne siamo diversi in giro, al giorno d’oggi il mercato è praticamente dominato da soli due contendenti:
- Buildroot è semplice e facile da usare™, sempre e comunque fornendo tutte le funzionalità fondamentali che l’utilizzatore si aspetta di trovare
- Yocto è tutt’altro che semplice da usare, ma è incredibilmente flessibile, ed è quindi maggiormente supportato dai produttori di hardware
Esistono delle alternative, come OpenWRT o LTIB, ma sono spesso troppo settoriali (la prima) o mancano di alcune feature fondamentali (la seconda).
Per un neofita, Buildroot è certamente lo strumento migliore, per cui ci concentreremo su di esso nelle prossime sezioni. Yocto è il naturale next-step per quando Buildroot non è più sufficiente.
Le basi
Prima di iniziare ad usare Buildroot, è necessario avere un’idea generale dei componenti necessari per arrivare ad un sistema Linux funzionante. Alcuni di questi possono variare a seconda dell’architettura, ma solitamente servirà sempre:
- un bootloader, ossia il primo programma utente eseguito dalla macchina quando questa viene avviata, con il compito di inizializzarne l’hardware e caricare il sistema operativo
- il kernel Linux, ossia il nucleo del sistema operativo, che si occupa della gestione delle risorse, schedulazione, accesso all’hardware, e di tutte le operazioni di basso livello di cui il programmatore non vuole preoccuparsi
- un root filesystem, che contiene tutti i programmi e le utility di sistema,
le configurazioni e i dati utente (in parole povere, l’equivalente del disco
C:\
su Windows)
Una volta ottenuti tutti e tre questi componenti, il passo finale sta nel metterli insieme e caricarli sulla scheda, secondo le metodologie previste da essa (che tipicamente varieranno da un modello all’altro). A titolo d’esempio, nel resto di questo post faremo riferimento a Raspberry Pi come scheda target dello sviluppo.
Preparare l’ambiente di build
Per iniziare, avremo bisogno di una macchina su cui è installata una distribuzione Linux, essendo il solo sistema operativo supportato da Buildroot. Non è importante la distribuzione utilizzata, a patto che sia possibile installare su di essa gli strumenti necessari per utilizzare Buildroot.
Come prossimo passo, avremo bisogno di scaricare i sorgenti di Buildroot. Abbiamo due scelte: scaricare un tarball o effettuare il checkout di un repository git. Personalmente consiglio l’utilizzo del repository: in questo modo viene gratis il setup del controllo di revisione e possiamo facilmente annullare potenziali cambiamenti distruttivi durante le nostre sperimentazioni.
Ciò si traduce di fatto in:
~ $ git clone git://git.buildroot.net/buildroot
~ $ cd buildroot/
È sempre buona norma iniziare a sviluppare partendo da un tag stabile, così da poter sempre tornare indietro ad un punto noto della storia:
~/buildroot $ git checkout 2018.08
Diamo adesso un’occhiata al contenuto della cartella buildroot/
:
~/buildroot $ ls
arch/ docs/ toolchain/ CHANGES DEVELOPERS
board/ fs/ support/ Config.in Makefile
boot/ linux/ system/ Config.in.legacy Makefile.legacy
configs/ package/ utils/ COPYING README
Ci sono diversi file e cartelle dentro, ma un paio di cose saltano all’occhio:
- è presente un
Makefile
, per cui probabilmente utilizzeremo il sistema di build make per interagire con Buildroot - è presente anche una cartella
configs/
, che possiamo ragionevolmente assumere contenga dei file di configurazione - infine, prendiamo nota della cartella
board/
, di cui parleremo a breve
Per il resto dei file sorgente, Buildroot fornisce un’ottima documentazione che è possibile consultare nel caso fossimo curiosi e volessimo saperne di più.
Configurare Buildroot
Come detto prima, questi sistemi di build richiedono una qualche configurazione
per poter sapere cosa compilare e per quale target, e come visto prima, è
presente una directory configs/
tra i sorgenti. Come ci si aspetterebbe, è
possibile trovare al suo interno diversi file di configurazione pre-esistenti per
diversi prodotti: evaluation kit, schede di sviluppo e consumer, ecc. Tra questi,
c’è anche un comodo raspberrypi_defconfig
che possiamo utilizzare come punto di
partenza per la nostra distribuzione.
La configurazione di Buildroot segue il formato Kconfig, usato anche dal kernel Linux. Non è strettamente necessario conoscerlo per interagire con Buildroot, ma per i curiosi, qui è possibile trovare alcune informazioni su di esso. Inoltre, quando possibile, è bene non iniziare da zero a sviluppare una configurazione per una scheda custom, ma partire dalla configurazione di una evaluation board o scheda di sviluppo simile e costruirci sopra le nostre modifiche.
In Buildroot, è possibile applicare il contenuto di un file di configurazione
tramite il comando make <config-file-name>
, assunto che il nome del file termini
in _defconfig
e si trovi nella cartella configs/
. Nel nostro caso lanceremo:
~/buildroot $ make raspberrypi_defconfig
...
#
# configuration written to ~/buildroot/.config
#
Passiamo adesso a dare un’occhiata al contenuto della configurazione che abbiamo appena applicato!
Modificare la distribuzione di base
Sebbene sia possibile aprire il file di configurazione tramite un editor di testo per analizzarlo, Buildroot fornisce degli strumenti che permettono di visualizzare e modificare la configurazione attuale con più semplicità.
Per gli amanti del terminale, il comando make menuconfig
aprirà un configuratore
interattivo basato su curses nel quale è possibile navigare per modificare la
configurazione attiva. Questo è molto utile per quando si è loggati sulla
macchina di build da remoto. Per gli amanti delle GUI, lo stesso risultato lo si
può ottenere usando make xconfig
o make gconfig
, che apriranno rispettivamente
un configuratore Qt o GTK.
In entrambi i casi, ci si troverà davanti ad un menu di questo tipo:
Target options --->
Build options --->
Toolchain --->
System configuration --->
Kernel --->
Target packages --->
Filesystem images --->
Bootloaders --->
Host utilities --->
Legacy config options --->
Le voci del menu sono abbastanza autoesplicative, e il modo migliore per farsi
le ossa è girare un po’ tra le varie opzioni e leggerne la documentazione
(accessibile tramite il tasto ?
sulla singola opzione). Alcuni punti chiave:
- Target options include tutto ciò che è relativo all’architettura del target
- Toolchain permette di scegliere tra lasciare che sia Buildroot a compilare per noi una toolchain, o usarne una pre-compilata che noi stessi forniamo
- System configuration permette di modificare delle impostazioni a livello di sistema, come hostname, password di root e così via
- Kernel e Bootloaders permettono di specifiare diverse opzioni relative a questi due componenti (da dove recuperare i sorgenti, versione utilizzata, ecc.)
- In Target packages è possibile selezionare quali pacchetti saranno installati nella distribuzione finale, ed è il menu in cui si concentrano gran parte delle modifiche
Supponiamo ad esempio di voler modificare l’hostname della nostra distro custom
in rpi
e installare un server OpenSSH. Inoltre, aumenteremo la dimensione del
filesystem a 100MB
per poter ospitare queste modifiche.
L’hostname di default (buildroot
) può essere modificato usando l’opzione
System hostname in System configuration:
System configuration --->
(rpi) System hostname
OpenSSH può essere installato selezionando la voce openssh nel percorso Target packages -> Networking applications:
Target packages --->
Networking applications --->
[*] openssh
Per finire, è possibile modificare la dimensione dell’immagine del root
filesystem agendo sull’opzione exact size in Filesystem images (di default
a 60M
):
Filesystem images --->
(100M) exact size
E con questo abbiamo terminato le modifiche. Possiamo adesso salvare ed uscire
dal configuratore per applicare i cambiamenti. Nel caso stiamo usando menuconfig
,
per uscire è sufficiente premere ESC
diverse volte fino a quando apparirà
il prompt di salvataggio, al qual punto possiamo selezionare <Yes>
per salvare
e uscire.
In Buildroot tutte le modifiche alla configurazione attuale sono salvate
localmente nel file .config
nella root del progetto. Questo file è temporaneo,
e non va tracciato tramite controllo di versione. Per salvare definitivamente una
modifica alla configurazione e condividerla con altri membri del team, è possibile
lanciare il comando make savedefconfig
. Questo andrà a sovrascrivere il file
usato in origine per generare la configurazione (nel nostro caso
raspberrypi_defconfig
) applicando le nuove modifiche, pronte per essere committate
su Git.
Finalmente è tempo di compilare la distribuzione!
Rimani aggiornato!
Ricevi novità su Develer e sulle tecnologie che utilizziamo iscrivendoti alla nostra newsletter.
Creare l’immagine di sistema
A seconda del numero di pacchetti selezionati, e se stiamo compilando o meno da zero toolchain, kernel e/o booloader, il tempo di build può variare da diversi minuti a diverse ore. Una build può essere avviata usando semplicemente il comando:
~/buildroot $ make
È importante notare che Buildroot non supporta l’esecuzione parallela di make
tramite l’opzione -jN
fornita al top-level. I singoli task verranno tuttavia
lanciati parallelamente (questo comportamento è comunque configurabile).
Una volta terminata la build, verranno create due nuove cartelle:
dl/
, che funge da cache per i sorgenti scaricati durante il processo di buildoutput/
, che contiene tutti gli artefatti della compilazione
In particolare, ciò che ci interessa principalmente si trova all’interno di
output/images/
, che contiene gli output finali della build. Il contenuto di
questa cartella dipende principalmente dall’architettura target e da alcune
opzioni di configurazione, ma in genere ci saranno:
- l’immagine del kernel (ad esempio
zImage
,uImage
o soloImage
) e i relativi device tree - l’immagine del root filesystem, in vari formati che possono essere specificati usando il menu Filesystem images
Per alcune architetture, Buildroot è anche in grado di produrre un’immagine del
disco da scrivere su SD card o chiavina USB, già correttamente partizionata e
formattata in modo che il processore possa effettuare il boot da essa. È questo
il caso della Rasbperry Pi: tale immagine viene salvata come
output/images/sdcard.img
.
Testare l’immagine
L’ultimo passo di questa procedura consiste nello scrivere tale immagine su una SD card e avviare la scheda. È possibile usare uno dei vari tool adatti allo scopo; una maniera veloce e universale per tutte le distribuzioni Linux è usare il comando dd
. Se non si ha familiarità con dd
, è bene prima leggerne attentamente il manuale, poiché passare dei parametri errati può comportare la perdita di dati.
Il comando completo sarà:
~/buildroot $ sudo dd if=output/images/sdcard.img of=<sd-device>
dove <sd-device>
è il device sotto /dev/
corrispondente alla propria SD card
(a seconda della macchina, potrebbe essere nel formato /dev/sdX
o /dev/mmcblkX
).
Una volta concluso questo passaggio, è possibile inserire l’SD sulla propria RPi
e alimentarla. Dopo la sequenza di boot iniziale, si dovrebbe vedere un prompt
sulla porta seriale: basta inserire root
come username per effettuare il login
sul nostro nuovo sistema!
Ulteriori modifiche
Abbiamo prima brevemente introdotto la cartella board/
all’interno della root
di Buildroot. Questa può essere utilizzata per memorizzare tutto ciò che è
specifico per una singola scheda o prodotto supportato da Buildroot.
Ad esempio, provando a guardare all’interno di board/raspberrypi/
possiamo
notare dei file interessanti:
- diversi file
.cfg
che specificano, per versioni differenti di RPi, il formato e il contenuto dell’immagine per SD card generata dalla build, di modo da renderla bootabile su revisioni hardware diverse - due script,
post-build.sh
epost-image.sh
, che vengono eseguiti ciascuno dopo la rispettiva fase di build per poter ulteriormente adattare l’immagine finale ad una RPi - un
readme.txt
specifico per RPi contenente istruzioni su come compilare e utilizzare le immagini appena prodotte
Come si può notare, sono tutti file relativi a RPi, che non troverebbero un posto
adatto in altre parti del build system, per cui vengono tutte raggruppate in
una sottocartella di board/
.
Ulteriori utilizzi di questa directory sono il salvataggio delle configurazioni o di patchset per il kernel o il bootloader, asset pre-compilati e così via. Altre informazioni su come personalizzare un proprio progetto o scheda si possono trovare nella sezione dedicata della guida.
Conclusioni
Questo è tutto! Buildroot rende estremamente semplice la creazione di una distro Linux completamente personalizzata, anche con una conoscenza minima di ciò che sta effettivamente succedendo dietro le quinte.
E visto che fare è il modo migliore per imparare, prova a modificare ulteriormente questa distribuzione di base, o addirittura crea da zero un nuovo progetto basato su una tua scheda, applicando tutto quello che hai imparato oggi!