Table of Contents

Tools

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 emebdded (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.

Prefixul unui cross compiler se termină întotdeaduna cu -. El va fi concatenat la numele utilitarelor (ex: gcc) pentru a obține numele complet (ex: arm-bcm2708hardfp-linux-gnueabi-gcc)

Prin convenție, majoritatea sistemelor de build folosite în lumea Linux acceptă variabila de mediu CROSS_COMPILE pentru specificarea prefixului care trebuie folosit atunci când se dorește o cross-compilare.

Toolchain

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:

make

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.

Makefile
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

bash

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:

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ă.

În dezvoltarea unui sistem embedded este deseori utilă adăugarea în variabila $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.

<hidden Informații istorice despre Intel Galileo>

Intel Galileo

Intel Galileo (<imgref galileo>) este prima placă bazată pe arhitectura x86 compatibilă cu Arduino. Ea foloșeste un procesor cu un singur nucleu și consum redus, Intel Quark SoC X1000, ce rulează la o viteză de 400MHz. Specificațiile complete sunt:

<imgcaption galileo center| Intel Galileo> Intel Galileo</imgcaption>

Conectare

Conectarea la target-ul Intel Galileo se poate face atât prin SSH, folosind interfața Ethernet, cât și prin intermediul unei console seriale. În cadrul acestui laborator vom avea nevoie de această a doua metodă pentru a putea afla configurările de rețea ale target-ului. După aceasta vom putea schimba pe conexiunea SSH ca și până acum.

Există mai multe programe care permit comunicarea printr-un port serial de pe un host Linux. Unul dintre acestea este screen, utilitar pe care-l vom folosi și la laborator. O invocare simplă a screen se face în felul următor:

$ screen <device> <baud rate>

unde <device> reprezintă calea către port-ul serial de pe host, conectat fizic la target, iar <baud rate> reprezintă un număr care specifică viteza care va fi folosită pentru comunicație. Pentru a ieși din screen folosiți combinația de taste Ctrl+A urmată de k.

Ambele capete ale unei conexiuni seriale (host și target) trebuie să folosească aceeași viteză de comunicație. Aceasta nu este negociată automat de către dispozitive, ci trebuie configurată manual.

Pe Linux, device-urile seriale se găsesc în /dev și pot fi recunoscute după prefixul tty. Spre exemplu /dev/ttyS0 reprezintă primul port serial fizic al host-ului, iar /dev/ttyUSB0 reprezintă primul port serial oferit de un adaptor USB-to-serial conectat la host. Desktop-urile sau laptop-urile cu porturi seriale fizice sunt rare, astfel că în cele mai multe cazuri conectarea prin interfața serială la un target se face printr-un adaptor USB-to-serial conectat la USB-ul host-ului.

Vitezele obișnuite folosite pentru interfața serială sunt: 9600bps, 19200bps, 38400bps, 57600bps și 115200bps.

Viteza conexiunii seriale folosită de placa Intel Galileo este 115200bps. Utilizatorul este root, fără parolă.

</hidden>

Exerciții

1. (1p) Conectați-vă la Raspberry Pi. Asistentul vă va oferi IPul & parola pe care vă puteți conecta. Creati un cont unic pentru fiecare calculator care se va conecta la RPi. De asemenea o să vreti să îi stabiliți o parolă, pentru a vă conecta prin SSH.

  • Folosiți scriptul adduser pentru a crea conturile. Luati nota de parametrul -p.
  • Pentru schimbarea parolei unui user puteți folosi utilitarul passwd.

2. (2p) Scrieți și compilați un program hello world. Rulați acest executabil atât pe host, cât și pe target-ul RaspberyPi (rpi), pe con. Salvați comanda folosită pentru compilare și output-ul programului pentru cele 2 rulări.

  • Folosiți scp <sursa> <destinatie> pentru a copia executabilul pe target. Calea remote este de forma <username>@<hostname>:<cale remote>.

3. (2p) Compilați program-ul hello world pentru a putea rula pe target-ul rpi. Salvați comanda folosită pentru compilare.

  • Prefixul cross-compiler-ului este arm-none-eabi-. Compilatorul este arm-none-eabi-gcc.

4. (1p) Configurați shell-ul astfel încât la pornirea unui terminal nou să puteți apela cross-compiler-ul direct prin numele executabilului.

  • Modificările $PATH făcute anterior pot fi executate automat la pornirea unui shell.

5. (2p) 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 RaspberryPi și salvați executabilele generate. Salvați de asemenea și comenzile folosite pentru compilare.

  • Dacă o variabilă nu este setată, construcția $(<variabilă>) într-un Makefile va fi echivalentă cu șirul vid.
  • Prin convenție se folosește variabila CFLAGS pentru a specifca flag-urile care vor fi folosite de un compilator C. Această variabilă nu este preluată automat de compilator.

6. (2p) Ce puteți spune despre conținutul celor 2 fișiere executabile create la exercițiul anterior?

  • Conținutul unui fișier executabil poate fi inspectat cu utilitarul objdump.

Resurse

Referințe