Embedded GNU/Linux partendo da zero: ricompilare il kernel

Embedded GNU/Linux partendo da zero

L’obiettivo di questa serie di articoli è di fornire una guida passo passo per la creazione da zero di un sistema GNU/Linux perfettamente funzionante, applicabile ad esempio in scenari di tipo embedded come base per la generazione di un semplice BSP (Board Support Package) [1], di un firmware Linux-based, o di una micro-distribuzione totalmente custom.

Introduzione

Creare un sistema GNU/Linux da zero costituisce un buon esercizio per comprendere alcuni aspetti chiave del suo funzionamento interno.

Tuttavia, oltre all’aspetto didattico, ci sono anche risvolti pratici
interessanti: conoscere i componenti e gli strumenti base che servono a
costruire un sistema GNU/Linux ci permette spesso, specie in ambito
embedded, di generare ambienti più compatti ed efficienti rispetto
alle soluzioni general-purpose messe a disposizione dal fornitore del
prodotto o della board di sviluppo su cui stiamo lavorando.

Per agevolare la lettura, l’articolo è strutturato in più parti distinte:

  • preparazione del kernel
  • preparazione del root filesystem
  • integrazione e test su QEMU [2]

Tutti i passaggi illustrati di seguito sono stati testati su un sistema desktop GNU/Linux usando la recente distribuzione Ubuntu 12.04.1.

Requisiti

Il primo requsito per creare un sistema GNU/Linux consiste nello scegliere un’architettura target. Dato che l’articolo ha un taglio prevalentemente orientato all’embedded e vista la predonimanza delle architetture ARM in tale settore, opteremo per un’architettura ARM.
Una volta scelta l’architettura target abbiamo bisogno di due componenti fondamentali:

  1. una piattaforma di prova su cui testare il nostro sistema
  2. una toolchain che ci permetta di cross-compilare i binari che vanno a costituire l’intero sistema operativo

Board (virtuale) di sviluppo

Il mercato mette a disposizione una vasta gamma di board di sviluppo ARM-based, anche a basso costo. Tuttavia chi si vuole cimentare nella creazione di un sistema GNU/Linux su ARM può farlo completamente a costo zero, emulando virtualmente la piattaforma di sviluppo.

Come software di emulazione ci baseremo su QEMU [4], che di fatto costituisce l’emulatore di architettura per eccellenza nel mondo Linux. QEMU permette di emulare diverse piattaforme ARM-based, ad esempio nella versione di QEMU installata su Ubuntu 12.04 abbiamo a disposizione le seguenti piattaforme:

$ qemu-system-arm -M .
Supported machines are:
beagle               Beagle board (OMAP3530)
beaglexm             Beagle board XM (OMAP3630)
collie               Collie PDA (SA-1110)
nuri                 Samsung NURI board (Exynos4210)
smdkc210             Samsung SMDKC210 board (Exynos4210)
connex               Gumstix Connex (PXA255)
verdex               Gumstix Verdex (PXA270)
highbank             Calxeda Highbank (ECX-1000)
integratorcp         ARM Integrator/CP (ARM926EJ-S) (default)
mainstone            Mainstone II (PXA27x)
musicpal             Marvell 88w8618 / MusicPal (ARM926EJ-S)
n800                 Nokia N800 tablet aka. RX-34 (OMAP2420)
n810                 Nokia N810 tablet aka. RX-44 (OMAP2420)
n900                 Nokia N900 (OMAP3)
sx1                  Siemens SX1 (OMAP310) V2
sx1-v1               Siemens SX1 (OMAP310) V1
overo                Gumstix Overo board (OMAP3530)
cheetah              Palm Tungsten|E aka. Cheetah PDA (OMAP310)
realview-eb          ARM RealView Emulation Baseboard (ARM926EJ-S)
realview-eb-mpcore   ARM RealView Emulation Baseboard (ARM11MPCore)
realview-pb-a8       ARM RealView Platform Baseboard for Cortex-A8
realview-pbx-a9      ARM RealView Platform Baseboard Explore for Cortex-A9
akita                Akita PDA (PXA270)
spitz                Spitz PDA (PXA270)
borzoi               Borzoi PDA (PXA270)
terrier              Terrier PDA (PXA270)
lm3s811evb           Stellaris LM3S811EVB
lm3s6965evb          Stellaris LM3S6965EVB
tosa                 Tosa PDA (PXA255)
versatilepb          ARM Versatile/PB (ARM926EJ-S)
versatileab          ARM Versatile/AB (ARM926EJ-S)
vexpress-a9          ARM Versatile Express for Cortex-A9
vexpress-a15         ARM Versatile Express for Cortex-A15
z2                   Zipit Z2 (PXA27x)

Nel nostro caso prenderemo in esame la Versatile Express (vexpress-a9) [5], visto che è quella supportata perfettamente dal kernel “mainline” (la versione del kernel ufficiale, reperibile da http://www.kernel.org), sia da QEMU, senza dover applicare patch aggiuntive. Inoltre, la Versatile Express monta un core Cortex-A9, una CPU ARM-based utilizzata su gran parte dei tablet, smartphone e netbook di recente produzione.

Emulare la piattaforma di sviluppo è piuttosto semplice, basta lanciare il comando:

$ qemu-system-arm -M vexpress-a9 -kernel flash.img

Dove flash.img è un file contenente l’immagine del nostro kernel/firmware che vogliamo far girare.

Nei passi seguenti vedremo come generare tale immagine.

Toolchain di cross-compilazione

La toolchain è un insieme di strumenti (tipicamente un compilatore, un linker e delle librerie) che ci permettono di generare le applicazioni per il sistema target a partire dai sorgenti. La GNU toolchain tipica è costituita dal compilatore GCC, le binutils (strumenti per la manipolazione dei binari) e le librerie glibc.

Per questa prima fase ci vengono in aiuto le toolchain pre-compilate (creare un sistema da zero non implica necessariamente di doverci ricompilare anche gli strumenti per creare il sistema stesso).

In rete si trovano molte toolchain pre-compilate, nel nostro caso utilizzeremo la Linaro Toolchain Binaries [3], una toolchain ottimizzata per le più recenti famiglie di CPU ARM (Cortex A8, A9, etc.).

Il setup della toolchain è costituito dai seguenti passaggi:

  • download dei binari della toolchain da internet: https://launchpad.net/linaro-toolchain-binaries/trunk/2012.09/+download/gcc-linaro-arm-linux-gnueabihf-2012.09-20120921_linux.tar.bz2
  • estrazione del pacchetto .tar.bz2:
    $ tar xjf gcc-linaro-arm-linux-gnueabihf-2012.09-20120921_linux.tar.bz2
  • setup della variabile di ambiente PATH per rendere disponibili i binari della toolchain dalla shell corrente:
    $ export PATH=$PWD/gcc-linaro-arm-linux-gnueabihf-2012.09-20120921_linux/bin:$PATH

Fase 1: preparazione del kernel

A questo punto abbiamo tutti gli strumenti necessari per iniziare a sviluppare sulla board virtuale.

Il primo passo consiste nel reperire i sorgenti “mainline” del kernel da http://www.kernel.org e ricompilarli per la nostra archiettura target.

Al momento della stesura di questo articolo l’ultimo kernel del ramo “stable” è il 3.5.4, procediamo quindi al download e all’estrazione dei sorgenti:

$ wget http://www.kernel.org/pub/linux/kernel/v3.0/linux-3.5.4.tar.bz2
$ tar xjf linux-3.5.4.tar.bz2
$ cd linux-3.5.4

Il kernel Linux supporta un’ampia gamma di architetture e piattaforme eterogenee: fondamentalmente esso è composto da un layer di basso livello
(./arch/*) che si interfaccia direttamente con il particolare hardware ed esporta ai livelli più alti interfacce generiche indipendenti dall’hardware sottostante.

Per l’utente o il programmatore di alto livello Linux è sempre lo stesso, indipendentemente da dove gira la propria applicazione.

La prima fase della ricompilazione del kernel consiste nel processo di configurazione. Esso permette di scegliere la particolare architettura che vogliamo utilizzare per il layer di basso livello, assieme a tutti i driver e le funzionalità di alto livello che vogliamo fornire alle applicazioni user-space.

Come abbiamo detto la piattaforma Versatile Express è già supportata pienamente dal kernel Linux, quindi possiamo procedere direttamente alla configurazione senza dover applicare patch o modificare codice:

$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- vexpress_defconfig

Le variabili di ambiente ARCH e CROSS_COMPILE sono informazioni per il compilatore; esse permettono di selezionare la particolare architettura (ARCH) e la particolare versione compilatore (CROSS_COMPILE) per generare il codice oggetto.

L’opzione “vexpress_defconfig” viene usata dal kernel nel processo di build: essa permette di selezionare la configurazione di default (defconfig) stabilita dalla comunità di sviluppatori che mantengono il codice per tale architettura. Le varie configurazioni *_defconfig per ARM si trovano tutte in arch/arm/configs/*.

Infine resta da effettuare il passo di compilazione vero e proprio:

$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

La ricompilazione di un intero kernel richiede dai 5 ai 10 minuti. Al termine, se tutto è andato correttamente, troveremo il seguente file nelle directory dei sorgenti del kernel:

$ du -h arch/arm/boot/zImage
2.4M   arch/arm/boot/zImage

Il file zImage rappresenta l’immagine della flash per la nostra board virtuale. In questo caso il singolo kernel richiede 2.4MB. Al momento però non abbiamo ancora nessuna applicazione, i 2.4MB rappresentano solo il “contenitore” dove far girare le applicazioni utente.

Testare il kernel sulla board virtuale

E’ possibile testare il kernel appena generato sull board virtuale QEMU
lanciando il comando seguente:

$ qemu-system-arm -M vexpress-a9 -kernel ./arch/arm/boot/zImage \
  -serial stdio -display none -append "console=ttyAMA0"

QEMU emula un ambiente identico ad una board Versatile Express fisica, quindi per il kernel non fa nessuna differenza, di fatto è come eseguirlo sull’hardware reale. Con le opzioni `-serial stdio -display none` stiamo dicendo a QEMU di collegare la seriale emulata direttamente alla console da cui lanciamo il comando. Con `-append console=ttyAMA0″` stiamo invece dicendo al kernel di ridirigere tutti i suoi messagi verso la seriale primaria. In pratica per noi sarà come avere aperto una sessione seriale sulla board.

Ecco il risultato pratico una volta eseguito il comando sopra:

  Booting Linux on physical CPU 0
  Initializing cgroup subsys cpuset
  Linux version 3.5.4 (righiandr@thinkpad) (gcc version 4.7.2 20120910
 (prerelease) (crosstool-NG linaro-1.13.1-2012.09-20120921 - Linaro GCC
  2012.09) ) #2 SMP Tue Oct 2 10:21:53 CEST 2012
  CPU: ARMv7 Processor [410fc090] revision 0 (ARMv7), cr=10c53c7d
  CPU: PIPT / VIPT nonaliasing data cache, VIPT aliasing instruction cache
  Machine: ARM-Versatile Express
  Memory policy: ECC disabled, Data cache writealloc
  sched_clock: 32 bits at 24MHz, resolution 41ns, wraps every 178956ms
  PERCPU: Embedded 7 pages/cpu @805a2000 s5824 r8192 d14656 u32768
  Built 1 zonelists in Zone order, mobility grouping on.  Total pages: 32512
  Kernel command line: console=ttyAMA0
  PID hash table entries: 512 (order: -1, 2048 bytes)
  Dentry cache hash table entries: 16384 (order: 4, 65536 bytes)
  Inode-cache hash table entries: 8192 (order: 3, 32768 bytes)
  Memory: 128MB = 128MB total
  Memory: 125168k/125168k available, 5904k reserved, 0K highmem
  Virtual kernel memory layout:
      vector  : 0xffff0000 - 0xffff1000   (   4 kB)
      fixmap  : 0xfff00000 - 0xfffe0000   ( 896 kB)
      vmalloc : 0x88800000 - 0xff000000   (1896 MB)
      lowmem  : 0x80000000 - 0x88000000   ( 128 MB)
      modules : 0x7f000000 - 0x80000000   (  16 MB)
        .text : 0x80008000 - 0x80425d84   (4216 kB)
        .init : 0x80426000 - 0x804516c0   ( 174 kB)
        .data : 0x80452000 - 0x80480520   ( 186 kB)
         .bss : 0x80480544 - 0x8049e928   ( 121 kB)
  SLUB: Genslabs=11, HWalign=64, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
  Hierarchical RCU implementation.
  NR_IRQS:256
  Console: colour dummy device 80x30
  Calibrating delay loop... 555.41 BogoMIPS (lpj=2777088)
  pid_max: default: 32768 minimum: 301
  Mount-cache hash table entries: 512
  CPU: Testing write buffer coherency: ok
  CPU0: thread -1, cpu 0, socket 0, mpidr 80000000
  hw perfevents: enabled with ARMv7 Cortex-A9 PMU driver, 1 counters
  available
  Setting up static identity map for 0x603242d8 - 0x60324330
  Brought up 1 CPUs
  SMP: Total of 1 processors activated (555.41 BogoMIPS).
  NET: Registered protocol family 16
  L310 cache controller enabled
  l2x0: 8 ways, CACHE_ID 0x410000c8, AUX_CTRL 0x02420000, Cache size:
  131072 B
  hw-breakpoint: debug architecture 0x0 unsupported.
  Serial: AMBA PL011 UART driver
  mb:uart0: ttyAMA0 at MMIO 0x10009000 (irq = 37) is a PL011 rev1
  console [ttyAMA0] enabled
  mb:uart1: ttyAMA1 at MMIO 0x1000a000 (irq = 38) is a PL011 rev1
  mb:uart2: ttyAMA2 at MMIO 0x1000b000 (irq = 39) is a PL011 rev1
  mb:uart3: ttyAMA3 at MMIO 0x1000c000 (irq = 40) is a PL011 rev1
  bio: create slab  at 0
  SCSI subsystem initialized
  usbcore: registered new interface driver usbfs
  usbcore: registered new interface driver hub
  usbcore: registered new device driver usb
  Advanced Linux Sound Architecture Driver Version 1.0.25.
  Switching to clocksource v2m-timer1
  NET: Registered protocol family 2
  IP route cache hash table entries: 1024 (order: 0, 4096 bytes)
  TCP established hash table entries: 4096 (order: 3, 32768 bytes)
  TCP bind hash table entries: 4096 (order: 3, 32768 bytes)
  TCP: Hash tables configured (established 4096 bind 4096)

  TCP: reno registered
  UDP hash table entries: 256 (order: 1, 8192 bytes)
  UDP-Lite hash table entries: 256 (order: 1, 8192 bytes)
  NET: Registered protocol family 1
  RPC: Registered named UNIX socket transport module.
  RPC: Registered udp transport module.
  RPC: Registered tcp transport module.
  RPC: Registered tcp NFSv4.1 backchannel transport module.
  jffs2: version 2.2. (NAND) © 2001-2006 Red Hat, Inc.
  msgmni has been set to 244
  io scheduler noop registered (default)
  clcd-pl11x ct:clcd: PL111 rev2 at 0x10020000
  clcd-pl11x ct:clcd: CT-CA9X4 hardware, XVGA display
  Console: switching to colour frame buffer device 128x48
  v2m_cfg_write: writing 03c8eee0 to 00110001
  v2m_cfg_write: writing 00000000 to 00710000
  v2m_cfg_write: writing 00000002 to 00b10000
  smsc911x: Driver version 2008-10-21
  smsc911x-mdio: probed
  smsc911x smsc911x: eth0: attached PHY driver [Generic PHY]
  (mii_bus:phy_addr=smsc911x-fffffff:01, irq=-1)
  smsc911x smsc911x: eth0: MAC Address: 52:54:00:12:34:56
  isp1760 isp1760: NXP ISP1760 USB Host Controller
  isp1760 isp1760: new USB bus registered, assigned bus number 1
  isp1760 isp1760: Scratch test failed.
  isp1760 isp1760: can't setup
  isp1760 isp1760: USB bus 1 deregistered
  isp1760: Failed to register the HCD device
  Initializing USB Mass Storage driver...
  usbcore: registered new interface driver usb-storage
  USB Mass Storage support registered.
  mousedev: PS/2 mouse device common for all mice
  rtc-pl031 mb:rtc: rtc core: registered pl031 as rtc0
  mmci-pl18x mb:mmci: mmc0: PL181 manf 41 rev0 at 0x10005000 irq 41,42
 (pio)
  usbcore: registered new interface driver usbhid
  usbhid: USB HID core driver
  aaci-pl041 mb:aaci: ARM AC'97 Interface PL041 rev0 at 0x10004000, irq 43
  aaci-pl041 mb:aaci: FIFO 512 entries
  oprofile: using arm/armv7-ca9
  TCP: cubic registered
  NET: Registered protocol family 17
  VFP support v0.3: implementor 41 architecture 3 part 30 variant 9 rev 0
  input: AT Raw Set 2 keyboard as /devices/mb:kmi0/serio0/input/input0
  rtc-pl031 mb:rtc: setting system clock to 2012-10-06 18:45:12 UTC
  (1349549112)
  ALSA device list:
    #0: ARM AC'97 Interface PL041 rev0 at 0x10004000, irq 43
  input: ImExPS/2 Generic Explorer Mouse as
  /devices/mb:kmi1/serio1/input/input1
  VFS: Cannot open root device "(null)" or unknown-block(0,0): error -6
  Please append a correct "root=" boot option; here are the available
  partitions:
  Kernel panic - not syncing: VFS: Unable to mount root fs on
  unknown-block(0,0)
  [] (unwind_backtrace+0x0/0xf8) from []
  (panic+0x8c/0x1cc)
  [] (panic+0x8c/0x1cc) from []
  (mount_block_root+0x174/0x228)
  [] (mount_block_root+0x174/0x228) from []
  (mount_root+0xf4/0x114)
  [] (mount_root+0xf4/0x114) from []
  (prepare_namespace+0x12c/0x180)
  [] (prepare_namespace+0x12c/0x180) from []
  (kernel_init+0x1e0/0x224)
  [] (kernel_init+0x1e0/0x224) from []
  (kernel_thread_exit+0x0/0x8)

In output possiamo vedere i messaggi di un tipico boot di Linux. Il kernel è partito correttamente, rilevando tutti i dispositivi hardware emulati dalla board virtuale, come se questa fosse effettivamente una board fisica.

Al termine della fase di boot otteniamo tuttavia un kernel panic (cioè un
errore irreversibile che compromette il funzionamento del sistema). Il motivo di tale errore lo possiamo trovare nei messaggi immediatamente precedenti al panic:

VFS: Cannot open root device "(null)" or unknown-block(0,0): error -6
Please append a correct "root=" boot option; here are the available
partitions:

In pratica per il kernel non esiste nessun root filesystem. Come abbiamo detto il kernel è solo un contenitore in cui girano le applicazioni, una volta terminato il processo di boot il kernel cerca di montare un filesystem radice (root) da uno dei dispositivi a blocchi rilevati ed eseguire un binario denominato “init”. Se questo binario non viene trovato il kernel non sa cosa fare e entra nella condizione di panic.

Nella prossima puntata vedremo come creare un root filesystem adatto al kernel, continuando a seguire stessa filosofia di questo tutorial: generando tutto completamente da zero.

Riferimenti

  1. http://en.wikipedia.org/wiki/Board_support_package
  2. http://en.wikipedia.org/wiki/QEMU
  3. https://launchpad.net/linaro-toolchain-binaries/+download
  4. http://wiki.qemu.org/Main_Page
  5. http://www.arm.com/products/tools/development-boards/versatile-express/index.php

 

Quello che hai appena letto è un Articolo Premium reso disponibile affinché potessi valutare la qualità dei nostri contenuti!

Gli Articoli Tecnici Premium sono infatti riservati agli abbonati e vengono raccolti mensilmente nella nostra rivista digitale EOS-Book in PDF, ePub e mobi.

volantino eos-book1

Vorresti accedere a tutti gli altri Articoli Premium e fare il download degli EOS-Book?
Allora valuta la possibilità di sottoscrivere un abbonamento a partire da € 2,95!

Scopri di più

7 Comments

  1. Andrea Righi 3 maggio 2013
  2. northpole 3 febbraio 2013
  3. Andrea Righi 6 febbraio 2013
  4. northpole 6 febbraio 2013
  5. Andrea Righi 6 febbraio 2013
  6. k0ral 30 aprile 2013
  7. Marco.Garzuoli 5 marzo 2015

Leave a Reply