Table of Contents

Configurări de boot. U-boot

Introducere

Vor fi prezentate în acest laborator aspecte diverse legate de configurarea sistemelor încorporate bazate pe Linux, în special pe Raspberrypi și pe placa de dezvoltare Atmel NGW100. Se va descrie procesul de încărcare a sistemului de operare, punctându-se fişierele ce pot fi modificate.

Linux boot

Hardware

La punerea sub tensiune, un procesor începe să execute cod de la o anumită adresă predefinită. La această adresă de obicei se conectează o memorie paralelă. În zona de memorie accesată la început de procesor se află un bootloader, un mic program a cărui sarcină principală este să încarce sistemul de operare.

Bootloader

La fabricarea unui sistem precum NGW100 memoria Flash este în principiu goală (numai când se produc foarte multe unităţi putem cere furnizorului memorii preprogramate). Scrierea bootloaderului în memoria Flash este prima etapă după fabricarea hardware-ului şi se realizează cu un dispozitiv special de testare şi depanare. Acest dispozitiv se conectează la procesor printr-un port JTAG (Joint Test Action Group) şi îi comandă acestuia să scrie în memorie.

Odată încărcat în Flash şi rulat, un bootloader avansat va permite încărcarea prin metode tradiţionale a imaginii sistemului de operare în memoria RAM, fără a mai necesita instrumentul JTAG: prin conexiune serială, reţea, card MMC/SD etc. Se poate de asemenea scrie imaginea sistemului în Flash pentru a nu mai fi necesară încărcarea manuală la pornirea viitoare. Sistemul NGW100 foloseşte bootloader-ul U-Boot, care permite toate aceste operaţii. Comunicaţia cu U-Boot se face prin portul serial al sistemului, interfaţa fiind o consolă simplă.

Kernel

În momentul în care începe să ruleze, kernel-ul de Linux își primește parametrii de la bootloader (așa-zisa linie de comandă, cmdline), apoi începe identificarea/inițializarea sistemului:

La finalul inițializărilor, kernel-ul face primul fork, copilul va deveni procesul init (cu pid 1), iar părintele va deveni procesul idle (pid 0).

Init

Init reprezintă primul proces user-space care este rulat de către kernel. Rolul lui este de a porni restul proceselor user-space (este strămoșul tutror proceselor de pe sistem). De asemenea, el rămâne activ până la oprirea sistemului și adoptă automat toate procesele orfane (al căror părinte direct s-a terminat). Kernelul va căuta executabilul pentru procesul init pe partiția root confrom priorităților:

  1. linia de comandă pasată kernelului (ex: init=/bin/init)
  2. /sbin/init
  3. /etc/init
  4. /bin/init
  5. /bin/sh

Dacă nici unul dintre executabile nu este prezent bootarea sistemului se va termina cu kernel panic.

Există mai multe implementări pentru procesul init, cum ar fi System V, Upstart, systemd etc. În continuare ne vom concentra asupra System V, care poate fi întâlnit în majoritatea sistemelor embedded. Acest sistem de inițializare folosește conceptul de runlevel, care definește ce servicii (programe) trebuie pornite la trecerea în acel runlevel. Exemple de runlevel-ul în Linux sunt:

Configuările pentru System V sunt stocate în fișierul /etc/inittab. Pentru RaspberryPi, liniile relevante din inittab sunt:

Se observă o oarecare asemnănare între liniile din fișierul de configurare. Acestea au formatul <id>:<runlevel>:<action>:<process>, unde id reprezintă un identificator ales de noi, runlevel specifică runlevel-urile în care va fi rulat programul, action specifică circumstanțele în care va fi rulat programul, iar process specifică linia de comandă.

Configurarea serviciilor care vor fi rulate în fiecare runlevel se face printr-o serie de link-uri simbolice din directoarele /etc/rcN.d/ (unde N este numărul runlevel-ului). Link-urile care încep cu S vor fi rulate la intrarea în acel runlevel, iar cele care încep cu K vor fi rulate la părăsirea runlevel-ului respectiv. În cadrul unui runlevel serviciile sunt rulate în ordinea alfabetică a link-urilor. De aceea imediat dupa litera S sau K urmează un număr din 2 cifre care specifică prioritatea serviciului respectiv. Dependețele între servicii sunt rezolvate prin specificarea unei priorități mai mari pentru serviciile de bază, pentru a fi rulate înaintea serviciilor dependente.

Script-urile care realizează efectiv pornirea și oprirea serviciilor se găsesc în directorul /etc/init.d (acestea sunt de fapt target-ul link-urilor simbolice din directoarele runlevel-urilor). Fiecare script corespunde unui serviciu (are același nume cu serviciul) și este apelat de către System V cu unul dintre parametrii start sau stop. Script-urile trebuie să respecte un anumit format (prezentat în rândurile următoare), iar link-urile simbolice sunt create automat de către System V după rularea comenzii:

update-rc.d <numele serviciului> defaults
schelet
#! /bin/sh
### BEGIN INIT INFO
# Provides: schelet
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Schelet serviciu
# Description: Schelet serviciu
### END INIT INFO
 
case "$1" in
 start)
   echo "Starting schelet"
   ;;
 
 stop)
   echo "Stopping schelet"
   ;;
 
 status)
   echo "Schelet is running (or not)"
   ;;
 
 *)
   echo "Usage: /etc/init.d/schelet {start|stop|status}"
   exit 1
   ;;
esac
 
exit 0

Boot la RaspberryPi

Procesul de boot la Raspberrypi este un pic mai complicat și este o implementare aparte:

Dacă aveați impresia că procesul e complicat…

Berryboot

Berryboot este un installer pentru distribuții de Raspberrypi, care vă poate folosi dacă vreți să vă instalați diferite distribuții fără prea mult efort.

Presupune scrierea unui card sd cu imaginea sa, la pornire va afișa un installer cu selecție de distribuție, format, etc.

Mai multe informații găsiți la site-ul BerryBoot

U-Boot

U-Boot este un bootloader folosit foarte des în lumea dispozitivelor embedded. Are suport pentru multe dispozitive diferite și poate încărca diverse programe/sisteme de operare (de obicei kernel de Linux) din mai multe surse. Ca și capabilități de comunicație avem:

U-Boot foloseşte o zonă de memorie pentru a stoca informaţii de configurare, aşa-numitul environment. La NGW100, această zonă se găsește în memoria flash într-o partiție specială, pe Raspberrypi se va găsi pe partiția de boot a cardului SD. Aceste informaţii pot fi afişate cu comanda U-Boot printenv.

> printenv
baudrate=115200
ethaddr=00:04:25:1C:90:7E
bootdelay=1
ethact=macb0
serverip=192.168.0.106
tftpip=192.168.0.106
eth1addr=00:04:25:1C:90:7F
bootargs=console=ttyS0 root=/dev/mtdblock1 rootfstype=jffs2
stdin=serial
stdout=serial
stderr=serial
bootcmd=fsload /boot/uImage;bootm

Parametrii esențiali sunt:

Nume Funcție
bootcmd comanda care se execută la bootare
stdin, stdout, stderr redirectarea fluxului de text, poate fi către serială sau către lcd
bootargs parametri dați kernel-ului Linux la bootare de către U-Boot
serverip IP-ul serverului de TFTP de unde dorim să luăm datele
ipaddr IP-ul dispozitivului

Comenzile care se dau în consola U-Boot sunt foarte asemănătoare cu cele dintr-o consolă bash, cu excepția argumentului implicit, care reprezintă valoarea returnată de comanda precedentă. De exemplu, fsload este comanda care încarcă în memorie un fișier dat ca argument dintr-un Flash paralel. Valoarea returnată este adresa la care a fost încărcat. bootm lansează o imagine de kernel de la o adresă dată ca parametru. Dacă nu este dat niciun parametru, se consideră adresa ca fiind ultima valoare returnată:

fsload /boot/uImage bootm

Aici bootm nu mai are nevoie de parametru pentru că valoarea returnată de fsload este exact adresa de care avem nevoie!

Comenzile care pot fi date în consola U-Boot sunt:

Nume Argumente Funcție
bootm Adresa Bootează o imagine de la o adresă
cp adresă start, adresă sfârșit, număr de bytes Copiază de la sursă la destinație
dhcp Folosește DHCP pentru a obține un IP și imagini de boot
tftpboot adresă, cale_fișier Încarcă la o adresă imaginea obținută prin TFTP de la server-ul cu serverip
usbboot Încarcă o imagine de kernel de pe un dispozitiv USB fără sistem de fișiere
fatload dispozitiv, partiție, cale_fișier Încarcă o imagine de kernel de pe un sistem de fișiere FAT

O listă completă de comenzi o găsiți aici

Parametrii pot fi modificaţi din U-Boot cu comanda askenv <nume_parametru> şi salvaţi în Flash cu comanda saveenv.

La pornire, U-Boot aşteaptă o secundă (bootdelay=1) primirea unui caracter Space, prin apăsarea tastei respective într-o consolă serială. În acest caz va afişa un prompt şi va executa comenzile date. Altfel, va executa automat comenzile date de parametrul bootcmd: va încărca din Flash în RAM kernel-ul, apoi îi va ceda controlul, cu argumentele bootargs: va folosi portul serial drept consolă şi va monta rădăcina sistemului de fişiere din Flash.

Pentru a încărca de pe prima partiție(ext2) a unui card SD kernel-ul și root-ul de pe a doua partiție, se vor da, spre exemplu, următoarele comenzi:

Uboot> askenv bootcmd
Please enter 'bootcmd':mmcinit; ext2load mmc 0:1 0x10400000 /boot/uImage; bootm
Uboot> set bootargs 'console=ttyS0 root=/dev/mmcblk0p1 rootwait'
Uboot> boot

Se observă posibilitatea U-Boot de a încărca un kernel (/boot/uImage) de pe un sistem de fişiere standard. fsload încarcă de pe un sistem de fişiere jffs2 aflat în Flash, ext2load încarcă de pe un sistem de fişiere ext2, fatload încarcă de pe un sistem de fișiere FAT etc.

U-Boot oferă și posibilitatea încărcării unui kernel/sistem de fișiere prin rețea. Astfel, kernelul este încărcat prin FTP, iar sistemul de fișiere poate fi încărcat prin NFS. Configurările sunt următoarele:

bootcmd= 
bootargs= console=ttyS0 root=/dev/nfs

Exerciții

1. Boot pe rețea

În această secțiune vom vedea cum se poate boota de pe rețea cu raspberrypi. Pentru aceasta, vom avea nevoie de:

Setup-ul inițial este următorul: Față de boot-ul normal, în lanțul de boot al raspberrypi a fost introdus uboot, care își are toate fișierele pe cardul sd, pe partiția de boot (VFAT). Este prezentă atât imaginea cu bootloader-ul (codul executabil), cât și un fișier care să inițializeze environment-ul și un fișier script de U-Boot boot.scr. Fișierul boot.scr conține un script de boot adus la formă binară cu ajutorul tool-ului mkimage:

usb start
setenv ipaddr 10.0.0.6
setenv serverip 10.0.0.16
printenv
tftpboot uImage-arm
bootm
git init
git fetch --depth 1 git://github.com/raspberrypi/linux.git rpi-3.6.y:refs/remotes/origin/rpi-3.6.y
git checkout rpi-3.6.y
make mrproper
make ARCH=arm CROSS_COMPILE=<prefix_compilator> oldconfig
make ARCH=arm CROSS_COMPILE=<prefix_compilator> -j3 uImage

Rezultatul compilării este fișierul arch/arm/boot/uImage, pe care trebuie să-l puneți în folder-ul tftpboot cu numele uImage-arm.

După ce ați urmat pașii aceștia, puteți trece la rulare. Dați drumul la placă, verificați să aveți un ip corect pe subinterfața eth0:0 sau eth1:1 (de forma 10.0.0.1x, unde x este numărul de pe card).

:!: Suportul pentru U-Boot pe raspberrypi este neoficial (un branch făcut de cineva dinafara echipei U-Boot) și în curs de dezvoltare. Datorită instabilității pe această platformă, s-ar putea să trebuiască să încercați de mai multe ori până să meargă! Așteptați 30 de secunde, dacă LED-urile nu se aprind și dispozitivul nu apare în avahi-browse –all -t, atunci resetați placa!

2. Script-uri de init

În continuare vom instala un server vnc pe Raspberrypi și vom adăuga un script de inițializare al acestuia la boot.

Mai întâi vom instala și verifica funcționalitatea:

pi$ sudo apt-get update
pi$ sudo apt-get install tightvncserver
pi$ vncserver :1

Folosiți un client vnc pe PC, de exemplu tigervncviewer sau VLC.

Creați fișierul vncboot în formatul necesar unui script din /etc/init.d/, sub forma unui serviciu cu parametrii start și stop (scriptul trebuie făcut executabil).

Comanda

update-rc.d vncboot defaults 

adaugă link-uri simbolice la /etc/init.d/vncboot.sh din foldere-le rcX.d, unde X este un runlevel inclus în lista defaults