This is an old revision of the document!


Laboratorul 05. The embedded boot process

Atunci când un microprocesor primește semnalul de reset (prin alimentare, transmiterea unor comenzi / întreruperi interne etc.), acesta începe să booteze. Toate sistemele moderne folosesc un proces de boot multi-stagiu, însă primul program este mereu încărcat dintr-un ROM (Read Only Memory) ce este, de cele mai multe ori, integrată în același chip.

Procesul de boot standard ARM

Arhitectura ARMv8 propune următoarea structură:

Se observă numărul mare de pași pe care sistemul trebuie să-i realizeze până să pornească kernelul sistemului de operare. Acest lucru se datorează punerii accentului pe securitatea soluțiilor incorporate (prin tehnologia ARM TrustZone, de care doar vom menționa scurt, pe Raspberry PI nefiind implementată în totalitate).

Așadar, prima dată se începe prin rularea primului stagiu, BL1, stocat în memoria ROM. Acesta va încărca, de pe o memorie flash externă (de obicei, prin SPI: eMMC sau SD), următoarele stagii. Dintre acestea, BL2 este, de obicei, un firmware foarte mic (10-100KB) ce este încărcat în memoria SRAM a SOC-ului (care, uneori, funcționează și pe post de cache) și care, mai departe, inițializează toate perifericele chip-ului (printre care, foare important este DRAM-ul – folosit de următoarele stagii cu consum ridicat de RAM!).

Stagiile BL3x devin opționale, totul depinzând dacă BL2 conține funcționalitatea de a porni și Kernel-ul de Linux și este suficient de configurabil pentru a putea acoperi o mare parte a cazurilor de utilizare ale sistemului (ceea ce este adevărat pentru RPI, însă pentru altele nu prea) sau dezvoltatorul software dorește să beneficieze de funcționalități avansate de boot (aici, BL31 poate oferi partiționare A/B cu toleranță la defecte, actualizări la distanță etc.) sau încărcarea unui sistem de operare securizat (Trusted OS, la pasul BL32).

În final, ultimul pas al bootloaderului va fi să citească configurația (de pe o partiție de boot sau dintr-o altă memorie ROM re-programabilă) și componentele de rulare (kernel, initrd, device tree blob – de cele mai multe ori, toate 3 fiind necesare) ale sistemului de opreare, și să încarce în RAM și să paseze execuția CPU-ului către kernel.

Procesul de boot al Raspberry PI

Deși procesul de boot diferă între Raspberry PI versiuni mai vechi sau egale cu 3 (ce folosesc SoC-ul BCM2837) și cele după 4 (cu BCM2711, și, pe viitor, v5), stagiile pot fi încadrate în arhitectura propusă de ARM.

Toate versiunile procesoarelor Broadcom folosite la RPI încep prin rularea stagiului BL1 pe microcontrollerul de gestionare a GPU-ul integrat (da, pe bune!), care, mai departe, inițializează memoria cache, interfața SPI (pentru accesarea memoriei flash din eMMC / card SD) și, folosind o bibliotecă incorporată de citire de pe partiții FAT32, scanează după existența firmware-ului pentru următorul stagiu, BL2, pe care îl va încărca în cache-ul L2 al procesorului (DRAM-ul încă nu a fost inițializat).

Ordinea de scanare a perifericelor pentru continuarea procesului de boot (e.g., SD Card / eMMC, extenal SPI, USB Mass Storage, LAN boot) diferă în funcție de starea unor GPIO-uri sau a unor regiștri OTP (One Time Programmable).

Așadar, un Raspberry PI are nevoie ca dispozitivul de pe care se efectuează bootarea să conțină o primă partiție FAT32 cu cel puțin firmware-urile de inițializare a platformei (bootcode.bin, start*.elf, config.txt și altele câteva ce depind de modelul efectiv).

Fiecare microprocesor are propriile convenții de stabilire a adreselor de încărcare a stagiului secundar. Cei de la Broadcom au folosit partiții FAT32, însă majoritatea producătorilor de SoC-ul incorporate cu ARM (e.g., Allwinner, NXP) preferă să încarce o imagine specială de la un anumit offset al disk-ului (e.g., la adresa 32KB de la începutul cardului SD). De aici vine și recomandarea de a crea prima partiție abia începând cu offset-ul de 1MB.

Pentru Raspberry PI 3, se va încărca fișierul bootcode.bin ca stagiu secundar, care, după inițializarea RAM-ului, va citi și încărca următorul stagiu (care este, de obicei, sistemul de operare propriu-zis, însă se poate interpune un alt bootloader – BL31, e.g., U-Boot).

La versiunile de Raspberry PI >=4, BL2 poate fi încărcat doar dintr-un EEPROM prin interfață SPI (și NU de pe SD / eMMC-ul extern – însă există procedură de recovery în caz că se strică ceva), fapt ce ușurează aplicațiile care făceau network boot. De asemenea, firmware-ul este open-source (ceea ce nu era adevărat până acum).

Mai departe, Secondary Program Loader-ul va încărca firmware-uri adiționale pentru GPU (pentru a putea afișa text prin HDMI), va analiza conținutul fișierului config.txt și va pune în aplicare procedurile configurate (încărcarea în memorie a fișierelor de kernel / device tree / initramfs).

Componentele pentru boot ale Linux

După cum a fost menționat mai sus, pentru a porni un sistem de operare pe bază de Linux se folosesc, de cele mai multe ori, 3 componente:

  1. Imaginea Kernel-ului (kernel*.img), ce conține codul executabil al nucleului Linux;
  2. Device Tree Blob-ul (*.dtb): conține descrierea și configurația tuturor componentelor specifice platformei hardware (i.e., pentru un anumit model + versiune a unei plăci de bază), însă se pot codifica și anumite setări și meta-informații pentru software (e.g., kernel command line, partiția rădăcină etc.);
  3. RamDisk inițial (initrd*.img): opțional, poate conține o imagine a unui sistem de fișiere minimalist (max. câțiva zeci de MB) cu module și scripturi necesare pentru a monta sistemul de fișiere rădăcină (e.g., dacă se dorește montarea unui sistem la distanță, trebuie mai întâi să se conecteze la rețea și să primească IP prin DHCP).

Bootloaderul (ori BL2-ul integrat, ori BL31 – dacă a fost inclus) va încărca aceste fișiere în DRAM-ul sistemului și va completa anumiți regiștri (cu pointerii la locația de încărcare a tuturor componentelor necesare) și va executa o instrucțiune de branch pentru a lansa kernelul în execuție.

Obiectivul final al componentelor de boot Linux va fi să caute și să monteze sistemul de fișiere rădăcină (roofs-ul) ce conține toată configurația și programele din user-space, de unde se va lansa procesul init care va continua prin pornirea serviciilor predefinite (care pot, la rândul lor, să inițializeze dispozitive hardware noi și să ruleze procese de automatizare).

RootFS

Pentru ca sistemul să fie inițializat corect după pornirea kernel-ului, este necesar ca toate script-urile și executabilele necesare pentru a porni daemon-ul de inițializare, init, și restul proceselor user-space, să existe în anumite locații în sistemul de fișiere. Acest sistem minimal de fișiere necesar la inițializare poartă numele de root file system sau rootfs. În cazul sistemelor Unix, rootfs-ul are ca rădăcină directorul / și înglobează o serie de directoare ce contin restul de fișiere necesare. De obicei, în directorul / nu se află niciun fișier, doar subdirectoare. Aceste subdirectoare sunt organizate în funcție de fișierele pe care le conțin:

Standardul Unix recomandă ca rootfs-ul să aibă dimensiuni relativ mici, deoarece orice problemă sau corupere a acestuia poate împiedica inițializarea corectă a sistemului. Dacă, totuși, rootfs-ul devine corupt, există modalități prin care poate fi remediat. O soluție des folosită este de a monta acest sistem de fișiere într-un alt sistem, funcțional, pentru a putea fi explorat și verificat.

În cazul sistemelor embedded, de multe ori spațiul de stocare pe care îl avem la dispoziție este deja limitat: < 32MB. De cele mai multe ori, o bună parte din acest spațiu este ocupat de imaginea kernel-ului. De aceea, în majoritatea cazurilor, rootfs-ul pentru aceste sisteme este fie compus doar din minimul de programe necesar pentru inițializare, fie este stocat pe un server accesibil prin rețea și montat de către kernel la inițializare.

Exerciții

În laborator, vom folosi echipamente Raspberry PI 4!

Înainte de a începe exercițiile, asigurați-vă că aveți cel puțin 10GB de storage disponibili în mașină virtuala.

1. Găsiți aici o imagine de referință pentru Raspberry PI 4.

  • Aflați dimensiunea în MB a imaginii oficiale (ls -l --block-size=M).
  • Creați un fișier de dimensiunea necesară pentru imaginea completă, folosind dd.
  • Aflați informațiile tabelei de partiții a imaginii oficiale folosind fdisk.
  • Creați tabela de partiții și partițiile necesare pentru imaginea noastră.
  • Partițiile imaginii nou create trebuie să aibă același UUID ca și imaginea veche pentru a o folosi cu succes. Pentru a modifica UUID-ul diskului folosiți comanda x în consola deschisa de fdisk pentru a intra în expert mode. Folosiți comanda m pentru a vedea toate opțiunile și modificați disk identifier încât să corespunda cu cel listat pentru imaginea oficială.

  • Puteți folosi informațiile din tabela de partiții pentru a găsi numărul de blocuri ai fiecărei partiții, precum și offsetul lor în cadrul diskului virtual.
  • Asigurați-vă că tipurile partițiilor sunt aceleași precum în imaginea oficială. Pentru a schimba tipul unei partiții folosiți comanda t în cadrul consolei deschise de fdisk, urmată de codul asociat tipului pe care îl dorim.

2. În acest moment, cele doua partiții nu au niciun sistem de fișiere creat. Creați un loop block device pentru fiecare dintre cele doua imagini. Creați-le sistemele de fișiere, având grija la tipul acestora (revedeți secțiunea Formatul imaginii RaspberryPI). Odată create sistemele de fișiere, montați fiecare partiție într-un director.

In procesul de boot al sistemului, partitiile sunt cautate de kernel dupa label-ul acestora. Pentru a ne asigura ca sistemul booteaza cu succes, va trebui sa denumim partitiile conform imaginii de Debian.

Optiunea -L a comenzii mkfs.ext4, respectiv optiunea -n a comenzii mkfs.vfat ne ofera posibilitatea de a atribui un label atunci cand cream sistemul de fisiere.

Paritia de boot trebuie sa aiba label-ul RPI_BOOT, iar partitia pentru rootfs trebuie sa aiba label-ul RPI_ROOTFS.

3. Ne dorim să copiem conținutul fișierelor din imaginea oficiala în imaginea noastră. Copiați conținutul directoarelor imaginii oficiale în directoarele imaginii noastre. După ce procesul de copiere s-a terminat, demontați device-urile și ștergeți loop device-urile.

Conținutul partiției de boot trebuie copiat în partiția de boot corespondenta din imaginea noastră. În mod similar, copierea trebuie făcută intre partițiile de rootfs.

Resurse

si/laboratoare/05.1699131085.txt.gz · Last modified: 2023/11/04 22:51 by florin.stancu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0