Costruire un Kernel Android

Benvenuti a un nuovo appuntamento con la Rubrica Firmware Reload di Elettronica Open Source. In questa Rubrica del blog EOS abbiamo raccolto gli articoli tecnici della vecchia rivista cartacea Firmware, che contengono argomenti e temi passati ancora di interesse per Professionisti, Makers, Hobbisti e Appassionati di elettronica. Sapere come ottenere un nuovo kernel di Android è una cosa importante, specie se si volesse realizzare una nuova distribuzione per una particolare piattaforma o se si intendesse modificarne alcune porzioni per esigenze costruttive. La costruzione di un nuovo kernel di Android non è un’operazione semplice, ma seguendo questo articolo vedremo come realizzarla, rendendola un’operazione semplice e divertente.

Introduzione

La diffusione di Android è ormai un fatto conclamato, tanto da diventare un vero caso commerciale-industriale senza precedenti, che porta l’attenzione sul ruolo dell’open source e sui rispettivi risvolti economici. Android è un esempio reale di che cosa possa fare il movimento open source se lasciato libero di crescere e di confrontarsi; infatti, anche se non è il primo caso, è riuscito ad essere utilizzato in qualsiasi tipo di piattaforma dimostrando la fattibilità dei progetti open source con le sue ricadute commerciali. Che cos’è Android? A questa domanda daremo tra poco la risposta, anche se possiamo, senza dubbio, affermare che non è di certo un sistema operativo, ma semmai un framework che consente di essere utilizzato in un’enormità di piattaforme hardware, perché l’obiettivo è di creare uno stack software open source per gli OEM e gli sviluppatori al fine di garantire un sistema che permetta la condivisione delle esperienze software insieme alla convenienza di utilizzarlo senza costi su soluzioni hardware tra loro più disparate. Certo, noi sappiamo che il codice sorgente di Android è aperto, permettendo così di adattare a proprio consumo il suo codice sorgente per ogni particolare personalizzazione e “porting”. Il progetto Android prende il via da un gruppo di società identificate come Open Handset Alliance, con al centro Google. Ad oggi, uscito dalla sua fase embrionale, Android è al centro di ottimi investimenti di molte aziende presenti in ogni angolo del mondo; infatti, queste hanno dato un notevole contributo per migliorarlo e utilizzarlo su tanti differenti dispositivi.

Lo sforzo delle società industriali è stato indirizzato non sulla quantità delle versioni in relazione alle società coinvolte ma, semmai, in una singola implementazione di un prodotto condiviso, poiché l’apertura di una piattaforma di questo tipo è una condizione necessaria al fine di consentirne l’uso così capillare. A detta di diversi sviluppatori, Android si adatta a una filosofia pragmatica perché, anche se tante società contribuiscono al suo successo, è necessario sempre garantirne l’adattamento su diverse piattaforme con l’ovvia personalizzazione delle risorse. L’idea di ottenere un prodotto condiviso consente di ostacolarne le personalizzazioni incontrollate, per evitare le implementazioni tra loro incompatibili, così come sostiene da sempre Android Open Source. Non solo, è necessario anche garantire la compatibilità a livello applicativo, o “Android compatible”. Se, da una parte, ognuno può utilizzare il suo codice sorgente, dall’altra i costruttori hanno il dovere di partecipare al programma di compatibilità di Android. Il progetto Android Open Source è guidato da Google, che mantiene e sviluppa ulteriormente il framework. La tecnica utilizzata per favorire la diffusione di Android è di costruire attorno ad esso innumerevoli sotto-progetti; infatti, Android non si gestisce come un unico prodotto software, non si realizza su una “distribuzione” specifica, ma su una raccolta di parti tra loro sostituibili. Per questa ragione, lo sviluppo di Android ruota intorno a determinate famiglie identificate con dei nomi in codice: a questo riguardo in Tabella 1 è possibile notare le differenti soluzioni, anche se occorre considerare anche l’ultima versione, la 5.1 Lollypop in uscita.

Tabella 1: Versioni di Android

A partire dalla release Cupcake, le release sono state identificate con un codice in funzione della compilazione; ad esempio, con FRF85B la prima lettera individua il nome in codice della famiglia di rilascio, ad esempio, F è Froyo, la seconda lettera è un codice filiale che consente a Google di identificare l’esatto ramo di codice con cui la build è stata fatta, mentre R è per convenzione la lettera che identifica il ramo di release primario. Le lettere successive, con le due cifre, identificano il codice della data, come Q1 2009. Infine, l’ultima lettera identifica le singole versioni relative allo stesso codice a partire da A; A è in realtà implicito ed è di solito omesso per brevità. È bene ricordare che la data è di per sé una garanzia della generazione della versione perché, al solito, questa è puramente indicativa e non comprende le eventuali variazioni minori aggiunte a una build esistente. Non solo, partendo da Donut, la lista esatta dei tag con le informazioni legate al suo build è evidenziata nella Tabella 2 limitandoci, per brevità, solo per la release Lollypop.

Tabella 2: Tag e builds per Lollypop

È anche bene ricordare che le immagini e i file binari per dispositivi Nexus possono essere scaricati dal sito:  https://developers.google.com/android/nexus/drivers. Come vediamo dalla Figura 1, Android si presenta sempre con Linux; questo vuol dire che se volessimo portare il nostro framework su una piattaforma differente dovremo sempre pensare di portare per prima cosa Linux e in seguito, una volta applicate ed eventuali patch, portare lo stack Android.

Figura 1: Correlazione tra Android e Linux

Quando affrontiamo un lavoro di questo tipo possono presentarsi due possibili percorsi: o portare Android su una piattaforma già presente o, in alternativa, costruire Android su una nuova board. In questo articolo ci soffermeremo sulla prima alternativa riservandoci, in futuro, di portare Android su una nuova scheda con processore ARM: un’ipotetica board con risorse pensate allo scopo.

COSTRUIRE IL KERNEL DI ANDROID PER PANDABOARD

Una volta identificata la board di lavoro dobbiamo cercare il kernel più idoneo per raggiungere lo scopo del nostro obiettivo. Per questa ragione il primo passo sarà quello di identificare il nostro kernel candidato e compilarlo al fine di ottenere la nostra nuova baseline. Poiché pensiamo di utilizzare una Pandaboard, allora dobbiamo ottenere un kernel in grado di supportare questa scheda di sviluppo. La Pandaboard (vedi Figura 2) è una piattaforma di sviluppo a basso costo basata su OMAP4430 con Cortex-A9 e con 1 GB di memoria DDR2.

Figura 2: La Pandaboard OMAP4430

 

Tabella 3: Kernel & locations

Nella Tabella 4 possiamo notarne le diverse caratteristiche fisiche.

Tabella 4: Caratteristiche hardware della Pandaboard

Ricordiamo che in questo articolo siamo interessati solo al kernel e per questa ragione, una volta identificata la scheda, sarà necessario scaricare e compilare solo il relativo kernel. Una possibile checklist di lavoro potrebbe essere la seguente. Per prima cosa è necessario recuperare l’elenco dei file che sono utili per il nostro lavoro e per farlo dobbiamo conoscere in che modo sono inseriti nel database operativo, poiché i progetti sono identificati nella forma <device>/<vendor>/<name. A questo riguardo nella Tabella 3 si pone in evidenza la forma utilizzata. Per questa ragione, per recuperare la baseline riferita alla Pandaboard è necessario intervenire in questo modo:

$ git clone https://android.googlesource.
com/device/ti/panda
$ cd panda
$ git log --max-count=1 kernel

Successivamente, occorre identificare la versione del kernel attraverso il ricorso alla seguente sequenza di comandi, da shell (ricordiamo che per fare questo lavoro stiamo utilizzando come piattaforma host un sistema Linux):

$ dd if=kernel bs=1 skip=$(LC_ALL=C
grep -a -b -o
$’\x1f\x8b\x08\x00\x00\x00\x00\x00’
kernel | cut -d ‘:’ -f 1) | zgrep -a
‘Linux
version’

In presenza di Nexus 5 (hammerhead), è possibile ricorrere alla seguente sequenza:

$ dd if=zImage-dtb bs=1 skip=$(LC_ALL=
C od -Ad -x -w2 zImage-dtb | grep
8b1f | cut -d ‘ ‘ -f1 | head -1) |
zgrep -a ‘Linux version’

Subito dopo è necessario scaricare i sorgenti che si intendono utilizzare, o meglio la scelta dipende da quale kernel intendiamo partire o modificare. Nel nostro caso può essere utile utilizzare i seguenti pacchetti: goldfish, x86_64, exynos, common, msm, omap, samsung e tegra. Per fare questa selezione occorre conoscere la funzionalità di ciascun pacchetto al fine di ottenere quello che più risponde alle nostre esigenze.

Nel nostro caso:

  • Il progetto goldfish contiene i sorgenti del kernel per le piattaforme emulate
  • Il progetto msm dispone dei sorgenti per ADP1, ADP2, Nexus One, Nexus 4, Nexus 5, Nexus 6 e può essere utilizzato come punto di partenza per lavorare sul chipset Qualcomm
  • Il progetto omap project è utilizzato per PandaBoard e Galaxy Nexus, oltre ad essere utilizzato come punto di partenza per lavorare con il chipset TI OMAP
  • Il progetto samsung project è utilizzato per Nexus S e può essere utilizzato come punto di partenza per lavorare sul
    chipset Samsung Hummingbird
  • L’ambiente Tegra è utilizzato per Xoom, Nexus 7, Nexus 9 e, come al solito, come punto di partenza per lavorare sul
    chipset NVIDIA tegra
  • Il progetto exynos dispone dei sorgenti del kernel per Nexus 10 e, come gli altri, può essere utilizzato come punto
    di partenza per lavorare con il chipset Samsung Exynos
  • Infine, il progetto x86_64 può vantare l’uso dei sorgenti del kernel per Nexus Player e può essere utilizzato come punto di partenza per lavorare con i chipset Intel x86_64

Per utilizzare tutti questi pacchetti è necessario utilizzare, da shell, il comando git, così:

$ git clone https://android.googlesource.
com/kernel/common.git
$ git clone https://android.googlesource.
com/kernel/x86_64.git
$ git clone https://android.googlesource.
com/kernel/exynos.git
$ git clone https://android.googlesource.
com/kernel/goldfish.git
$ git clone https://android.googlesource.
com/kernel/msm.git
$ git clone https://android.googlesource.
com/kernel/omap.git
$ git clone https://android.googlesource.
com/kernel/samsung.git
$ git clone https://android.googlesource.
com/kernel/tegra.git

A questo punto si è conclusa la parte di definizione del nostro ambiente e inizia la seconda fase; in altre parole dobbiamo compilare tutto il nostro nuovo ambiente utilizzando una nostra toolchain. Ci troviamo, così, di fronte a un bivio: o costruirci la nostra toolchain o scaricare una prebuilt e visto che quest’ultima è già disponibile su “android.googlesource.com”, non ci rimane altro che configurare il nostro ambiente di lavoro per scaricarla:

$ export PATH=$(pwd)/prebuilts/
gcc/linux-x86/arm/arm-eabi-4.6/
bin:$PATH
o
$ export PATH=$(pwd)/prebuilts/
gcc/darwin-x86/arm/arm-eabi-
4.6/bin:$PATH

In ambiente Linux possiamo a questo punto scaricare una prebuilt toolchain da:

$ git clone https://android.googlesource.
com/platform/prebuilts/
gcc/linuxx86/
arm/arm-eabi-4.6

Una volta definite le nostre variabili d’ambiente possiamo procedere alla compilazione dell’intero sistema:

$ export ARCH=arm
$ export SUBARCH=arm
$ export CROSS_COMPILE=arm-eabi-
$ cd omap
$ git checkout <commit_from_
first_step>
$ make panda_defconfig
$ make

Il file binario, il nostro kernel, si trova ora posizionato nel folder "arch/arm/boot/zI-mage" e può essere copiato e utilizzato ora nel nostro ambiente con Android. E se avessimo bisogno di un’altra toolchain? Chiariamo subito che una toolchain è un insieme di programmi che permettono di compilare un codice sorgente, e quindi non solo il kernel. Ogni processore e piattaforma hardware hanno una loro specifica toolchain e, al solito, tipicamente è possibile reperire una particolare toolchain collegandosi al sito: http://developer. android.com/sdk/ndk/index.html. Ma non solo: per i più audaci è anche possibile costruirsi da soli la propria toolchain. Una volta reperita una toolchain, ad esempio android-ndk-r7-linux-x86.tar.bz2, è necessario procedere ad estrarre i file, così:

tar -jvxf android-ndk-r7-linux-
x86.tar.bz2

Ogni kernel, così come qualsiasi software, viene compilato ricorrendo al comando make. Il programma make invoca uno specifico file chiamato Makefile: questo contiene le diverse regole utilizzate per generare il binario; infatti, un Makefile contiene le opzioni di configurazione specifiche per quell’immagine binaria. Qualsiasi porzione o modulo software è compilato con il programma GCC, ovvero il compilatore C di GNU. Ogni toolchain ne contiene uno, allo stesso modo anche un qualsiasi NDK ne ha una versione ottimizzata. È necessario essere, così, sicuri che durante la compilazione si usi sempre una versione GCC della toolchain o della NDK, per evitare di generare codice non cross-compilato ma, semmai, utilizzabile solo una macchina host. Ecco perché diventa importante specificare che il GCC da utilizzare è quello definito nella variabile CROSS_COMPILE, in questo modo:

CROSS_COMPILE= arm-linux-androideabi-

In questo modo, il path per GCC dovrà diventare arm-linux-androideabi. Ogni sviluppatore può utilizzare le opzioni di compilazione in modo differente perché dipendono dalla particolare piattaforma hardware utilizzata. È così possibile utilizzare, ad esempio, il supporto ext4 come parte del kernel zImage o come modulo chiamato loadable, ovvero nella forma name.ko, caricato a init.d/init.rc. Ad ogni modo, è possibile anche ricorrere a menuconfig, uno strumento che consente di ottenere le possibili combinazioni per la configurazione del kernel. Infatti, esiste una grande quantità di opzioni da selezionare, talmente ampia che un progettista non può conoscerle tutte; ecco perché diventa importante saper utilizzare il tool di configurazione nel modo più preciso possibile, perché la scelta incide sulla bontà dell’immagine generata. Non solo, oggi, i sorgenti del kernel vengono forniti già con un set predefinito di parametri che possono essere facilmente impostati. È utile ricordare che tutti i comandi impostati da shell devono essere eseguiti all’interno della cartella di lavoro; nel nostro caso, ad esempio, può essere panda-defconfig, così:

cd ~/android/kernel/bravo_2.6.35_gb-mr
make ARCH=arm CROSS_COMPILE=arm-linux-
androideabi- bravo_defconfig

Al termine di questo comando, il sistema genera un file .config. Questo file contiene tutti i parametri utilizzati per produrre il kernel. Ogni kernel di solito contiene un file .config per ogni piattaforma conosciuta, in questo modo sarà possibile ottenere una versione del kernel per ogni piattaforma. Ad esempio, supponendo di utilizzare la board example:

make ARCH=arm CROSS_COMPILE=arm-linux-
androideabi- example_defconfig

Il kernel, infine, può essere compilato ricorrendo semplicemente al comando:

make -j4 ARCH=arm CROSS_COMPILE=
arm-linux-androideabi

Il parametro j4 specifica il numero di job per eseguire l’operazione: questo parametro è tipicamente uguale al numero dei core del sistema in uso. Per inciso, a volte può essere necessario applicare delle patch al kernel quando si ha la necessità di modificare parte del codice perché il software originario, ad esempio, non risulta essere compatibile con la piattaforma in uso.

Articolo della rivista cartacea Firmware Numero 109-110 Anno: 2015

Scarica subito una copia gratis

Scrivi un commento

Seguici anche sul tuo Social Network preferito!

Send this to a friend