Până acum ați compilat programe pe același sistem pe care le-ați și rulat. Multe programe însă necesită un proces complex de compilare, care intern execută mulți pași și, în funcție de configurarea aleasă, poate dura un timp îndelungat. Pentru a reduce acest timp se dorește bineînțeles folosirea unei mașini cât mai puternice. În cazul sistemelor embedded este puțin probabil ca procesorul folosit să fie unul performant (în comparație cu un sistem desktop) sau ca memoria RAM disponibilă să fie mare. De altfel, nu de puține ori, avem de-a face cu un sistem care nici măcar nu are destul spațiu de stocare pentru sursele și fișierele binare generate în timpul compilării.
Pentru a elimina inconvenientele compilării pe sistemul embedded (target-ul) compilarea se face de obicei pe un sistem desktop (host-ul). Bineînțeles, acum pot apărea probleme dacă target-ul și host-ul folosesc procesoare cu arhitecturi diferite (executabilul generat de host nu va fi înțeles de procesorul target-ului). Aceste probleme apar deoarece compilarea va folosi în mod implicit compilatorul host-ului: host-compiler-ul (ex: gcc). Rezolvarea constă în instalarea pe host a unui compilator care poate genera executabile înțelese de target. Acest compilator poartă denumirea de cross-compiler, el rulând pe arhitectura host-ului, dar generând cod pentru arhitectura target-ului. Procesul prin care un program este compilat pe un alt sistem față de sistemul target se numește cross-compilare.
Această tehnică este folosită pentru a separa mediul de dezvoltare de mediul în care programul trebuie să ruleze. Acest lucru ne este util în cazul:
Diferențierea între host-compiler și cross-compiler se face prin prefixarea acestuia din urmă cu un string, denumit prefix, care, prin convenție, conține o serie de informații despre arhitectura target (ex: arm-bcm2708hardfp-linux-gnueabi-
). De asemenea, și restul utilitarelor folosite pentru compilare (ex: as, ld, objcopy etc.) vor avea același prefix. Tot ce trebuie să facem este să instruim sistemul de build să folosească cross-complier-ul pentru compilare.
-
. El va fi concatenat la numele utilitarelor (ex: gcc
) pentru a obține numele complet (ex: arm-bcm2708hardfp-linux-gnueabi-gcc
)
CROSS_COMPILE
pentru specificarea prefixului care trebuie folosit atunci când se dorește o cross-compilare.
Toolchain-ul reprezintă colecția de programe de dezvoltare software care sunt folosite pentru a compila și a obține un program executabil.
Programele care în majoritatea cazurilor sunt incluse în toolchain sunt:
Un alt program important pentru dezvoltarea unui sistem embedded, și nu numai, îl reprezintă make. Acest utilitar ne permite automatizarea și eficientizarea procesului de compilare prin intermediul fișierelor Makefile. Pentru o reamintire a modului de scriere a unui Makefile revedeți laboratorul de USO - Dezvoltarea programelor în C sub mediul Linux.
Pentru ușurarea dezvoltării pe multiple sisteme embedded, fiecare având toolchain-ul lui propriu, vom dori să scriem Makefile-uri generice, care pot fi refolosite atunci când prefixul cross-compiler-ului se schimbă. Pentru aceasta va trebui să parametrizăm numele utilitarelor apelate în Makefile. Putem folosi în acest caz variabile de mediu în cadrul Makefile-ului. Acestea pot fi configurate apoi din exterior în funcție de sistemul target pentru care compilăm la un moment dat, fără a mai fi necesară editarea Makefile-urilor.
Cel mai simplu mod de a face acest lucru este să urmăm convenția deja stabilită pentru variabila de mediu care conține prefixul cross-compiler-ului: CROSS_COMPILE
. Putem folosi această variabilă de mediu în cadrul Makefile-ului nostru utilizând sintaxa de expandare unei variabile, $(<variabila>)
, și prefixând numele utilitarului cu variabila pentru prefix.
hello: hello.c $(CROSS_COMPILE)gcc hello.c -o hello
Orice variabilă exportată în shell-ul curent va fi disponibilă și în fișierul Makefile. Putem de asemenea pasa variabile utilitarului make și sub formă de parametri, astfel:
$ make CROSS_COMPILE=arm-bcm2708hardfp-linux-gnueabi- hello
O mare parte din dezvoltarea unui sistem embedded se face prin intermediul terminalului. Shell-ul care rulează în terminal permite personalizarea unor aspecte utile pentru dezvoltare precum variabilele de mediu încărcate la fiecare rulare. Aceste personalizări se fac însă în fișiere de configurare specifice fiecărui shell.
Pentru bash aceste fișiere reprezintă niste script-uri care sunt rulate automat și se găsesc în /etc
(afectează toți utilizatorii) și în $HOME
(afectează un singur utilizator). Prin intermediul fișierelor din $HOME
fiecare utilizator își poate personaliza shell-urile pentru propriile nevoi. Aceste fișiere sunt:
.bash_profile
- este executat când se pornește un shell de login (ex: primul shell după logare);.bashrc
- este executat cand se pornește orice shell interactiv (ex: orice terminal deschis);.bash_logout
- este executat când shell-ul de login se închide.
Un alt fișier util folosit de bash este .bash_history
, care memorează un istoric al comenzilor interactive rulate. Istoricul comenzilor este salvat în acest fișier la închiderea unui shell. Pentru o reamintire a unor comenzi utile în linia de comandă puteți revizita laboratorul de USO - Automatizare în linia de comandă.
$PATH
a căilor către diferitele tool-uri folosite, pentru ca acestea să poată fi accesate direct prin numele executabilului. Modificarea variabilei $PATH
pentru fiecare shell pornit se poate face ușor prin intermediul fișierelor de personalizare a shell-ului.
0. Vom configura accesul target-ului la Internet.
Întâi vom dezactiva conectarea automată din setările sistemului de operare ale maşinii virtuale Ubuntu 18.04: Settings → Network → Wired → Connect Automatically (off).
Închidem maşina virtuală. Înainte de a o porni, vom pune placa de rețea a mașinii virtuale Ubuntu in modul de NAT: Click dreapta pe maşina virtuală în VirtualBox → Settings → Network → Network Settings.
Pornim maşina virtuală (de acum la următoarele porniri, conexiunea la internet nu se va mai realiza automat). Vom crea bridge-ul virbr0 care să ofere target-ului accesul la rețeaua fizică a host-ului:
sudo brctl addbr virbr0 # creăm bridge-ul sudo brctl addif virbr0 <interfata fizica> # adăugam interfața fizică a host-ului la bridge sudo ip address flush dev <interfata fizica> # ștergem adresa IP de pe interfața fizică, doar dacă avem o adresă # IP pe interfață. Va șterge și ruta default automat sudo dhclient virbr0 # obținem adresa IP pentru bridge și ruta default prin DHCP
Un exemplu de rulare ale comenzilor de deasupra:
Pentru ca bridge-ul să fie acceptat de QEMU el trebuie configurat și în fișierul /etc/qemu/bridge.conf
sub forma:
allow virbr0
Rulăm distribuția Raspbian folosind QEMU în modul system emulation:
sudo qemu-system-arm -machine versatilepb -cpu arm1176 -kernel <kernel file> -append 'root=/dev/sda2' -drive file=<rootfs file>,index=0,media=disk,format=raw -net nic,model=smc91c111,netdev=bridge -netdev bridge,br=virbr0,id=bridge -serial stdio
Logarea pe target se face cu credentialele:
Verificăm conectivitatea la internet:
ping 8.8.8.8
În continuare vom lucra cu target-ul pornit şi ne vom conecta la el prin SSH.
1. Ca să nu fiti nevoiţi să introduceţi de fiecare dată IP-ul target-ului, configuraţi pe host în /etc/hosts maparea dintre adresa IP a target-ului şi numele pe care doriţi să îl asociaţi (ex: raspberry).
ip address show
ping raspberry
2. Configuraţi conexiunea SSH către target astfel încât aceasta să se efectueze fără să mai fiţi nevoiţi să introduceţi parola de fiecare dată.
ssh pi@raspberry
3. Scrieți și compilați pe host un program hello world. Rulați acest executabil atât pe host, cât și pe target-ul RaspberyPi. Ce observaţi când rulaţi pe target?
scp <sursă> <destinaţie>
pentru a copia executabilul pe target, unde <sursă>
este calea către fişierul local (de pe host), iar <destinaţie>
este calea remote către directorul de pe target unde se va salva (<username>@<hostname>:<cale pe target>
). De exemplu: scp hello pi@raspberry:.
4. Compilați program-ul hello world pentru a putea rula pe target-ul RaspberryPi. Salvați comanda folosită pentru compilare. Rulaţi programul compilat pe target.
$PATH
calea către directorul ce conține executabilele necesare: git clone --depth=1 https://github.com/raspberrypi/tools.git export PATH="/home/osboxes/tools/arm-bcm2708/arm-linux-gnueabihf/bin:$PATH"
arm-linux-gnueabihf-
. Compilatorul este arm-linux-gnueabihf-gcc
.
5. Creați un Makefile generic pentru programul hello world care poate compila pentru orice sistem target în funcție de variabilele primite (convenția CROSS_COMPILE
). Compilați programul pentru host și pentru target-ul RaspberryPi, apoi salvați executabilele generate.
$(<variabilă>)
într-un Makefile va fi echivalentă cu șirul vid.
6. Ce puteți spune despre conținutul celor 2 fișiere executabile create la exercițiul anterior?