Introducere Una din consecințele legii lui Moore este faptul că în timp vom putea pune cât mai multă putere de procesare într-un spațiu din ce în ce mai mic. S- a ajuns din acest motiv la o dezvoltare extraordinară în domeniul dispozitivelor mobile. În momentul actual putem ține în mână tehnologie cu putere de procesare mai mare decât calculatoarele personale pe care lucram acum nici zece ani. Evoluția hardware-ului a fost de neoprit și specialiștii se arată extrem de optimiști pentru evoluția ulterioară. Totodată această putere este inutilă fără un set de aplicații care să profite din plin de capacitățile de procesare existente. Avem astfel o legătură strânsă între hardware și software. Există un strat de mijloc reprezentat de drivere care ușurează în cele mai multe situații accesul la periferice (ecran, sistem audio, tastatură. modem ...). Totuși, doar gândindu-ne la diferențele de instrucțiuni între diversele arhitecturi de procesoare, acest lucru nu este de ajuns. Avem astfel câteva probleme rezolvabile doar prin adaptarea codului nostru la noua platformă pentru a profita din plin de viteza unor instrucțiuni. Cum majoritatea codului este scris într-un limbaj de nivel înalt devine sarcina compilatorului să optimizeze codul pentru diversele platforme existente. Avantajele scrierii codului într-un limbaj de nivel înalt sunt multiple, mai ales când ne gândim la aspecte precum portabilitatea și reutilizabilitatea codului. Astfel alegerea uneltelor de dezvoltare reprezintă un lucru extrem de important atunci când vine vorba de crearea de aplicații pentru diverse platforme. Aceste unelte trebuie să fie conștiente de modul de operare al mașinii respective, de setul de instrucțiuni disponibil cât și de metode de optimizare a codului. În același timp pentru a avea un mediu de dezvoltare competitiv avem nevoie și de integrare cu uneltele folosite în mod curent curent în dezvoltarea pe astfel de platforme; de exemplu opțiunea de a genera informații ce pot fi folosite de unelte dedicate dezvoltării pe diverse arhitecturi. Respectarea unor standarde actuale și documentarea destul de bună a tehnicilor ce trebuiesc folosite reprezintă un lucru extrem de dorit, majoritatea companiilor 1
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Introducere
Una din consecințele legii lui Moore este faptul că în timp vom putea
pune cât mai multă putere de procesare într-un spațiu din ce în ce mai mic. S-
a ajuns din acest motiv la o dezvoltare extraordinară în domeniul
dispozitivelor mobile. În momentul actual putem ține în mână tehnologie cu
putere de procesare mai mare decât calculatoarele personale pe care lucram
acum nici zece ani. Evoluția hardware-ului a fost de neoprit și specialiștii se
arată extrem de optimiști pentru evoluția ulterioară. Totodată această putere
este inutilă fără un set de aplicații care să profite din plin de capacitățile de
procesare existente. Avem astfel o legătură strânsă între hardware și
software. Există un strat de mijloc reprezentat de drivere care ușurează în
cele mai multe situații accesul la periferice (ecran, sistem audio, tastatură.
modem ...). Totuși, doar gândindu-ne la diferențele de instrucțiuni între
diversele arhitecturi de procesoare, acest lucru nu este de ajuns. Avem astfel
câteva probleme rezolvabile doar prin adaptarea codului nostru la noua
platformă pentru a profita din plin de viteza unor instrucțiuni. Cum
majoritatea codului este scris într-un limbaj de nivel înalt devine sarcina
compilatorului să optimizeze codul pentru diversele platforme existente.
Avantajele scrierii codului într-un limbaj de nivel înalt sunt multiple, mai ales
când ne gândim la aspecte precum portabilitatea și reutilizabilitatea codului.
Astfel alegerea uneltelor de dezvoltare reprezintă un lucru extrem de
important atunci când vine vorba de crearea de aplicații pentru diverse
platforme. Aceste unelte trebuie să fie conștiente de modul de operare al
mașinii respective, de setul de instrucțiuni disponibil cât și de metode de
optimizare a codului.
În același timp pentru a avea un mediu de dezvoltare competitiv avem
nevoie și de integrare cu uneltele folosite în mod curent curent în dezvoltarea
pe astfel de platforme; de exemplu opțiunea de a genera informații ce pot fi
folosite de unelte dedicate dezvoltării pe diverse arhitecturi. Respectarea unor
standarde actuale și documentarea destul de bună a tehnicilor ce trebuiesc
folosite reprezintă un lucru extrem de dorit, majoritatea companiilor
1
încercând pe cât posibil să evite eventualele surprize neplăcute care pot
apărea datorită lipsei de informație.
Un alt lucru extrem de important sunt și ciclii de dezvoltare, precum
De exemplu pentru un procesor arm1176jzf compilarea cu optimizări
nivelul 2 în modul ARM (care este implicit) cu informații de debug este
următoarea:
arm-elf-gcc -g -c -mcpu=arm1176jzf -O2 file.c
Devreme ce mediul de dezvoltare a fost în principiu Windows s-a optat
pentru o soluție bazată pe cygwin, care oferă o emulare a mediului de pe
platformele Linux. În acest fel se pot rula programe care sunt scrise folosind
apeluri specifice Linux, după ce sunt în prealabil compilate pe această
platformă. Folosing cygwin se puteau obține, cel puțin teoretic, avantajele
unui mediu Linux. Imediat a fost luată în considerare folosirea distcc pentru
distribuirea compilării. Maturitatea suitei cygwin s-a dovedit în acest caz
insuficientă pentru a rula aplicații precum demonul distccd:
fork: child -1 - died waiting for longjmp before initialization
O soluție acceptabilă nu a fost găsită pentru rezolvarea acestei
probleme. În acest caz a fost abandonată ideea de a folosi distcc pe platforma
18
Windows și a fost luată în considerare o soluție care să folosească apeluri
native Windows, nu cele emulate în cadrul cygwin. Totodată s-a luat în
considerație faptul că soluția nu va necesita configurare precum suita distcc.
Modelul de bază a rămas același: un demon local care rulează pe fiecare din
mașinile implicate ca voluntari în procesul de compilare.
Diferența este faptul că fiecare mașină va crea lista de mașini implicate
în procesul de build dinamic, după ce a a fost pornită aplicația. Va trimite un
semnal de broadcast în rețeaua locală în așteptarea unui răspuns de la
mașinile disponibile.
Lista mașinilor disponibile se poate modifica prin pornirea sau oprirea
unora. În acest fel pornirea unei mașini corespunde cu trimiterea unui mesaj
cu dublă semnificație: cererea unui răspuns de la mașinile existente cât și
atenționarea intrării unei mașini în lista celor implicate în procesul de
compilare distribuită. Pentru că unele mașini pot fi oprite acest lucru trebuie
să fie semnalat celorlalte mașini pentru a avea o listă corectă a mașinilor
disponibile. Acest lucru se realizează prin trimiterea unui mesaj tuturor
calculatoarelor din rețea, prin broadcast. Există desigur și cazuri în care
calculatorul este închis fără ca mesajul să fie trimis (pană de curent, erori,
etc). Pentru acest lucru există semnale periodice trimise fiecăror stații pentru
a ne asigura de statusul stației respective.
Pe lângă aceste probleme au mai existat și probleme referitoare la codul
generat cât și modul intern de lucru al compilatorului gcc. În primă fază a fost
generat codul însă încercarea de a rula a scos la iveală diverse probleme care
19
nici nu au fost luate în calcul:
1. Constructorii obiectelor statice și globale nu erau chemați la pornirea
aplicației
2. Unele funcții specifice bibliotecii standard de lucru cu caracterele
alocau bucăți de memorie folosind funcțiile standard bibliotecii (care în
cazul nostru nu erau implementate)
Problema constructorilor a apărut din cauza faptului că compilatorul nu
este gândit să funcționeze din prima pentru toate platformele ci doar pe cele
mai folsite, precum mediile existente pe PC. În cazul PC-ului funcția main este
cea care dă startul unui program. Înainte de această funcție există proceduri
de inițializare a mai multor variabile globale care pot fi referențiate ulterior in
orice context. Neavând o configurație standard acest lucru nu putea să fie
integrat decât manual. Soluția a fost modificarea scriptului folosit de linker
( în cazul gnu acesta este ld iar în cazul nostru acesta este arm-elf-ld ) prin
declararea unor variabile care vor puncta către funcțiile de inițializare:
.text
{............. __CTOR_LIST__ = .; /* constructuri globali */LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2) /* nr. De constructor ( "/4" pentru ca sunt stocate adrese pe 32 de biti, "-2" pentru a ignora aceasta lungime si the terminatorul NULL) */ *(.ctors) /* toti constructorii globali */ LONG(0) /* NULL e la capatul listei */ __CTOR_END__ = .;/* capatul listei constructorilor */.......}
Avem astfel adresele unde sunt stocate funcțiile de inițializare
constructorilor. Aceste funcții sunt specifice fiecărui fișier unde sunt declarate
date globale sau statice. Din cod C apelarea constructorilor se face în modul
următor:
int callGlobalConstructors( void )
{
typedef void (*tfvCtor)(void);
extern long __CTOR_LIST__; //din linker script
�
tfvCtor *pfvCtor;
20
DBGPRINTF( "__CTOR_LIST__=%u", __CTOR_LIST__ );
//obtinerea adresei de inceput a constructorilor
pfvCtor = (tfvCtor *)(&__CTOR_LIST__ + 1); //+1 pentru a sari peste lungime
//vedem daca este lista terminata cu NULL
if( pfvCtor[__CTOR_LIST__] )
{
DBGPRINTF_FATAL( "no NULL terminator" );
return( 1 ); //eroare
}
//chemarea fiecarui constructor in parte
for( ; *pfvCtor; pfvCtor++ )
//apel initializare constructor
(*pfvCtor)();
//ok
return( 0 );
}
Același preocedeu se va urma dacă se dorește chemarea destructorilor
la ieșirea din aplicație. Acest lucru explică de ce pe anumite platforme precum
Brew declararea globală sau statică claselor nu este recomandată.
Referindu-ne la a doua problemă, cea cu alocarea de memorie această
problemă a apare în unele cazuri speciale. Când ne gandim la funcții de
prelucrare a șirurilor, de formatare (sprintf, vsprintf, snprintf ... ), ne gândim
că ele nu alocă memorie. Acest lucru este adevărat în majoritatea cazurilor.
Pentru tipuri de date intregi (specificator %d, %i sau %u) sau șiruri
( specificator %s ) nu există nici o problemă în acest sens, deoarece
dimensiunea acestora este cunoscută și poate fi ușor încadrată în anumite
limite. De exemplu un număr pe 32 de biți poate avea o valoare maximă de
4294967295 astfel încât numărul maxim de caractere pe care acesta poate să
îl ocupe este 10. Situația se schimbă însă în cazul numerelor cu virgulă mobilă
(float sau double) unde nu se poate ști cu certitudine de câte caractere este
nevoie pentru afișarea acelui număr ca text. În acest caz se folosesc funcțiile
de alocare a memoriei. Deși nu este necesară multă memorie probleme pot
apărea dacă această memorie nu este alocată corect. Totul se rezumă la
maparea zonei de heap tot în cadrul scriptului de linker. Acest lucru se face
21
prin declararea variabilei end (sau o variabilă asemănătoare, _end, __end)
astfel încât să puncteze într-o zonă care să poată fi folosită pentru alocări de
dimensiune mică necesare acestor funcții. Acest lucru NU înseamnă că vom
folosi funcții de alocare specifice bibliotecii standard.
Există totodată și probleme legate de alinierea datelor. De exemplu
structura următoare poate ocupa 5 bytes pe anumite arhitecturi pe când în
GCC ea ocupă 8 bytes datorită alinierii. Soluția pentru ca structura să nu fie
alininată este folosirea directivei __attribute__((packed)):
typedef struct tagTest {
char c; // 1 byte
int i; // 4 bytes
}TestS;
typedef struct tagTest {
char c; // 1 byte
int i; // 4 bytes
}__attribute__((packed))TestS;
sizeof(TestS) = 8 (GCC) sizeof(TestS) = 5 (GCC)
22
Structura sistemului
Întregul sistem reprezintă îmbinarea tuturor componentelor anterior
prezentate. La baza acestui sistem este sistemul de versionare folosit care
depinde de alegerea fiecărui dezvoltator. În acest loc sunt păstrate sursele
necesare realizării aplicației. Excluzând etapa de dezvoltare și testare pentru
realizarea produsului final (aplicația) avem următorii pași:
1. Obținerea surselor din sistemul de versionare folosit (specific fiecărei
soluții de versionare)
2. Incrementarea versiunii
3. Savlarea versiunii în repository (specific soluției de versionare)
4. Lansarea sistemului de build
Executabilul obținut este distribuit mai departe către alte departamente
(testare, vânzări, etc).
Acești pași sunt doar orientativi și se pot modifica în funcție de sistemul
folosit. De exemplu de cele mai multe ori pentru incrementarea versiunii se
folosește un script sau executabil extern. Datorită faptului că folosim un
sistem de build care permite integrarea ușoară cu Python vom avea acest
lucru gata rezolvat (cum am văzut și în exemplu) fără a avea nevoie de alte
utilitare.
Tot datorită faptului că avem la dispoziție un limbaj de scripting extrem
de puternic precum Python avem posibilitatea de a lansa comenzi externe cu
un efort minim. De exemplu pentru rularea unei comenzi este de ajuns
executarea codului de mai jos:
import os
os.system(“dir”);
Așadar ținând cont de utilitățile oferite de limbajul Python putem trage
concluzia că pentru realizarea produsului final avem doar nevoie de o singură
componentă care în cazul nostru este SCons.
SCons odată lansat poate obține cele mai recente surse ale proiectului
prin rularea unor comenzi de genul:
os.system(“svn update”); # in cazul sistemului de control SVN
23
Următorul pas este incrementarea versiunii care se face tot în cadrul
limbajului Python. După ce a fost modificat fișierul în care este reținută
versiunea acesta se poate introduce în sistemul de versionare printr-o
comandă specifică. De exemplu în cazul SVN:
os.system(“svn commit”);
Aceste avantaje simplifică acest proces la un singur pas:
1. Lansarea sistemului de build
Flexibilitatea oferită de un limbaj de nivel superior este un lucru extrem
de important. Scripting-ul de bază din Windows este extrem de sărac în
opțiuni iar bazarea sistemului de build pe această metodă este o alegere
greșită când vine vorba de operații care nu pot fi oferite cu ușurință decât
într-un limbaj de nivel înalt.
În cadrul sistemului de build este integrată soluția de compilare, soluție
bazată pe colecția GNU. Integrarea acesteia cu SCons este una mult mai
ușoară din simplul motiv că nu mai este nevoie de realizarea manuală a
dependințelor (chiar dacă există și utilitare care fac acest lucru ușor și în alte
sisteme build). Toate dependințele sunt calculate automat, fără nevoia de
rulării unor utilitare externe. Din acest punct de vedere se câștigă și viteză de
procesare, lucru extrem de important într-un sistem de build.
Integrarea soluției de distribuire a compilării reprezintă înlocuirea
comenzii de compilare, ceva asemănător integrării distcc într-un sistem
bazate pe fișiere makefile: “make CC=distcc”.
Avem astfel în cadrul întregului sistem următoarele entități de bază:
1. Sistemul de build (bazat pe SCons)
2. Suita de compilare (GNU Compiler Collection)
3. Suite de distribuire a compilării ( asemănătoare ca design aplicației
distcc)
4. Soluția de versionare (poate fi SVN, Perforce, CVS, Mercurial, Git,
ClearCase șamd)
Interacțiunea între aceste entități este în principiu implementată în
sistemul de build SCons. Schema următoare ne prezintă componentele
24
principale cât și componente anexe cât și interacțiunile dintre ele:
Coordonatorul întregului proces este după cum se vede SCons. El este
cel care ia cele mai recente surse din source control (1), incrementează
versiunea și updatează repository-ul cu noua versiune (2). Următorul pas
important este lansarea procesului de compilare a surselor. Compilarea se
poate face fie direct cu compilatorul (9) fie prin sistemul de distribuire al
compilării (3). Pentru că SCons oferă și posibilitatea stocării fișierelor în
cache (4) pentru evitarea pe cât posibil a recompilării se folosește un spațiu
adițional. Este recomandat ca accesul la acel mediu de stocare să fie destul de
rapid, o viteză lentă mărind considerabil timpul necesar pentru obținerea
produsului final. Mediul de stocare este opțional, putându-se dezactiva prin
specificarea opțiunii “--cache-disable”. Totodată spațiul din cache poate fi
folosit și pentru obținerea fișierelor deja compilate (5). Produsul final se
stochează și el într-un mediu (6) specific. Sistemul de distribuire apelează la
compilatorul GNUC (8) pentru obținerea datelor ce vor fi trimise pe rețea la
25
stații(7), dar și la compilarea fișierelor preprocesate rezultate.
Din schemă se poate observa importanța unui sistem de build. Fără
acesta schema ar fi fost mai complicată datorită necesității integrării mai
multor soluții pentru realizarea unor lucruri mai speciale, care nu puteau fi
rezolvate doar prin rularea de comenzi precum cele din fișierele .bat sau .cmd.
Alegerea făcută în acest sens ne garantează că o eventuală integrare cu alte
unelte ce vor apărea se va putea realiza cu mai multă ușurință. Limbajul
Python ne oferă pe lângă posibilitatea de a rula comenzi într-un mod
asemănător fișierelor .bat sau .cmd și utilitățile unui limbaj de nivel înalt
precum expresii regulate, citire din fișiere, parsare de stringuri, biblioteci
pentru formatul XML, suport pentru majoritatea protocoalelor de comuncație
folosite, posibilitatea creării de interfețe grafice șamd.
26
Concluzii
Avem așadar o soluție în majoritate open-source pe care o putem folosi
pentru dezvoltarea de aplicații pe platforme ARM. Suita de dezvoltare se
poate folosi și în mediul universitar, nu neapărat pe arhitecturi ARM oferind o
multitudine de facilități față de alte soluții.
Totodată există anumite părți care nu au fost luate în considerare și care
ar putea aduce îmbunătățiri ale timpului de realizare a produsului final
pornind de la surse și resurse. Orice produs necesită anumite resurse, precum
imagini, sunete sau diverse fișiere cu format binar care trebuie incluse cumva
în executabil sau într-un sistem de fișiere care poate fi accesat. De obicei
realizarea resurselor este o etapă greu paralelizabilă, de accea un pas
important ar fi optimizarea acestui timp.
Pe de altă parte și în cadrul compilării se pot face îmbunătățiri.
Majoritatea compilatoarelor noi, printre care și GCC, dispun de opțiunea de
creare a headerelor precompilate. Performanțele aduse prin folosirea acestei
metode sunt extrem de importante. Un dezavantaj ale acestei metode este
faptul că ea nu poate fi folosită decât pentru un build local.
Totuși trebuie avut în vedere faptul că cu puțin efort se poate construi o
arhitectură eficientă ce ar putea însemna că viteza de realizare a produsului
final nu este condiționată de existența unei rețele, dezvoltatorul putând astfel
lucra deconectat. Acest avantaj împreună cu folosirea unor unelte de source
control precum git sau Mercurial oferă utilizatorului opțiunea de a avea
toate avantajele de care se bucură un dezvoltator are acces la o rețea de