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.
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.
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ă.
Î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 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:
init=/bin/init
)/sbin/init
/etc/init
/bin/init
/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:
id:2:initdefault:
- specifică runlevel-ul inițial (2).si::sysinit:/etc/init.d/rcS
- specifică scriptul (/etc/init.d/rcS) care va fi rulat la pornirea sistemului, înainte de intrarea în runlevel-ul inițial.ca:12345:ctrlaltdel:/sbin/shutdown -t1 -a -r now
- specifică acțiunea (/sbin/shutdown ...) apelată la apăsarea tastelor Ctrl+Alt+Del în runlevel-urile 1, 2, 3, 4 și 5.1:2345:respawn:/sbin/getty –noclear 38400 tty1
- pornește programul de login getty pentru terminalul 1 (tty1); procesul va fi repornit automat (respawn) dacă este terminat.T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100
- pornește programul de login pentru interfața serială (ttyAMA0).
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
#! /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
Procesul de boot la Raspberrypi este un pic mai complicat și este o implementare aparte:
bootcode.bin
, care este un second stage bootloaderstart.elf
de pe cardul SDstart.elf
încarcă în memoria RAM la adresa 0 imaginea kernel.img
și dă drumul la procesorDacă aveați impresia că procesul e complicat…
start.elf
va încărca u-boot.img
în loc de kernel. Asta ne va da libertatea să încărcăm apoi, cu ajutorul U-boot, orice fișier și prin diverse metode (TFTP, card SD, USB, etc.)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 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
Î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
usb start
?ipaddr
și serverip sunt de forma 10.0.0.x, respectiv 10.0.0.1x, pentru a preveni coliziunile. Fiecare card are un număr scris pe el, lângă numele unic, cu care se formează aceste adrese. (Europa are '5', deci adresa în timpul bootării va fi 10.0.0.5 și va căuta server-ul de TFTP 10.0.0.15)/home/student/tftpboot
uImage-arm
de pe server-ul TFTP și apoi o booteazăuImage
este o denumire dată fișierului zImage
- imaginea kernelului de Linux - modificat pentru a funcționa cu U-Boot. Imaginea se formează prin compilarea cu make uImage
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
.config
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!
Î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