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:

  • Identificare procesor
  • Inițializare cache, flush TLB, inițializare MMU
  • Inițializare periferice: memorii Flash, interfețe de rețea, USB

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:

  • runlevel 0 - shutdown
  • runlevel 1 - configurație single-user folosită pentru mentenanță.
  • runlevel 3 - configurație multi-user în linie de comandă.
  • runlevel 5 - configurație multi-user cu interfață grafică.
  • runlevel 6 - reboot

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
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:

  • La pornire, core-ul ARM este oprit, dar pornește GPU (Graphical Processing Unit)
  • GPU-ul pornește execuția dintr-o memorie ROM proprie (lucru des întâlnit) - Acesta este un first stage bootloader
  • Primul bootloader inițializează cardul SD, de pe care încarcă fișierul bootcode.bin, care este un second stage bootloader
  • Al doilea bootloader inițializează memoria SDRAM și încarcă start.elf de pe cardul SD
  • start.elf încarcă în memoria RAM la adresa 0 imaginea kernel.img și dă drumul la procesor
  • procesorul începe execuția cu RAM-ul inițializat și de la adresa 0 direct cu kernel-ul

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

  • În cadrul exercițiilor vom intercala U-Boot între stagii, astfel încat 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

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:

  • Comunicație Serială, Rețea, Adrese luate prin DHCP
  • Display informații pe serială sau lcd (chiar și hdmi)
  • Încărcare kernel de pe card SD, din flash sau de pe rețea (TFTP)
  • Posibilitatea de scripting la boot

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:

  • un server TFTP
  • Raspberrypi cu rețeaua legată direct la calculator

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
  • De ce este nevoie de 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)
  • server-ul de tftp este deja instalat pe calculatoarele voastre, folder-ul exportat este /home/student/tftpboot
  • Script-ul încarcă imaginea 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
  • Obținerea surselor de kernel: Pentru raspberrypi puteți obține o versiune stabilă executând comenzile:
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
  • În folder-ul cu kernel-ul, faceți un clean
make mrproper
  • Copiați fișierul '.config' în folder-ul vostru (care conține configurația de kernel pe care am folosit-o până acum). config.txt Redenumiți-l în .config
  • Copiați utilitarul mkimage în directorul unde se află cross-compiler-ul și dați-i același prefix. Mkimage este un utilitar folosit în proiectul U-Boot pentru a transforma imagini executabile sau scripturi în cod binar compatibil U-Boot. În cazul de față, el este produs o dată cu compilarea U-Boot, care a fost făcută în prealabil.
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

si/lab/2020/hidden/lab2.txt · Last modified: 2021/08/10 18:32 (external edit)
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