Upload
undergraver
View
118
Download
0
Embed Size (px)
DESCRIPTION
Dizertatie AAC
Citation preview
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
programare, testare, raportare probleme, rezolvare probleme, testare șamd.
Durata în timp a acestor cicli de dezvoltare trebuie să fie cât mai mică pentru
a avea un produs bun din punct de vedere calitativ, în sensul excluderii cât
mai multor defecte. Pentru obținerea acestui lucru se va lua în calcul
procesarea paralelă în cadrul obținerii produsului final, folosirea unui
mecanism de caching, cât și metode specifice pentru îmbunătățirea timpilor
de procesare pe fiecare mașină în parte.
Deși aparent alegerea unei unelte de dezvoltare nu pare un lucru foarte
complicat dacă privim problema din perspectiva schimbarii uneltelor folosite
în dezvoltare complexitatea procedurii crește într-un mod greu de determinat
în primă fază, fiind necesare diverse teste repetate pentru a ajunge la
evaluarea complexității pe care acest proces de înlocuire îl poate avea.
Schimbarea respectiv adoptarea unei metode de dezvoltare va fi privită
doar din punctul de vedere al avantajelor oferite comparativ cu alte metode,
impactul simțit de dezvoltatori, cât și lucrurile necesare pentru ca totul să
decurgă cât mai normal. Acest lucru implică faptul că înlocuirea sau
adoptarea unei metode noi de lucru este condiționată de timpul necesar de
adaptare, timpii morți în cadrul unei firme reprezentând pierderi imediate și
foarte posibil și pe termen lung.
2
Arhitectura ARM
ARM este o tehnologie dezvoltată de compania ARM Limited al cărei
sediu central este în Cambridge, Marea Britanie. Compania s-a fondat în anul
1990, fiind cunoscută în principal pentru designul procesoarelor ARM, deși
totodată mai produce software și unelte de dezvoltare dar și platforme și
infrastructuri de tipul sistem pe chip (System on a Chip – SoC). În ziua de azi
aproximativ 75% din procesoarele RISC pe 32 de biți poartă marca ARM,
astfel încât se poate spune pe bună dreptate că este unul dintre cele mai
folosite procesoare pe 32 de biți din lume. Procesoarele ARM sunt găsite
aproape în toate electronicele de consum găsite azi pe piață, începând cu
PDA-uri, telefoane mobile, aplicații multimedia (ex: iPod), jocuri portabile,
calculatoare de buzunar, și mergând până la periferice precum unități de
stocare sau routere.
După cum se poate vedea cea mai mare răspândire a acestor procesoare
este în domeniul electronicelor mobile, unde consumul este un factor extrem
de important, lucru ușor explicabil datorită opțiunilor pentru conservarea
energiei de care dispun aceste procesoare.
Primul procesor ARM a apărut în anul 1985 după aproximativ doi ani de
cercetare sub denumirea de ARM1 fiind mai degrabă o demonstrație decât un
procesor ce trebuie utilizat în producție. Primul procesor care a ajuns să fie
folosit la scară largă a fost ARM2 care a apărut un an mai târziu. Acesta avea
un spațiu de adrese pe 32 de biți, canal de date pe 32 de biți. Procesorul
ARM2 este considerat ca fiind cel mai simplu procesor pe 32 de biți din istorie
având doar 30 000 de tranzistori față de 70 000 ai procesorului Motorola 68k.
Această simplitate a condus la un consum extrem de redus de energie
totodată având performanțe comparabile la acea vreme cu cele ale unui
procesor Intel 80286. Ulterior s-au introdus și versiuni de procesoare care
conțineau cache ( ARM3 avea 4KB ) performața în acest caz crescând.
Designul folosit este unul curat, fără microcod ci doar circuite
(hardwired). Arhitectura include următoarele caracteristici RISC (pe lângă
cele specifice ARM):
3
– Arhitectură Încarcă/Stochează (Load/Store)
– Nu pot fi accesate zone de memorie nealiniate ( din versiunea ARMv6 acest
lucru e posibil )
– Set de instrucțiuni ortogonal
– 16 regiștri pe 32 de biți
– instrucțiuni de dimensiune fixă (pe 32 de biți) pentru ușurarea decodării,
cu costul creșterii densității codului
– majoritatea instrucțiunilor se execută într-un singur ciclu de procesor
Modul Thumb a fost introdus odată cu versiunea ARM7 a procesorului.
În principal când procesorul este în acest mod fiecare instrucțiune pe 32 de
biți este tratată ca două instrucțiuni pe 16 biți. Acest lucru este necesar
pentru a avea o densitate mare de cod. Aceste instrucțiuni pe 16 biți sunt
mapate direct la instrucțiuni normale, pe 32 de biți. Spațiul salvat vine de la
considerarea unor parametri ai instrucțiunilor impliciți și totodată limitând
numărul de posibilități în comparație cu setul întreg de instrucțiuni. În modul
Thumb unele instrucțiuni au o funcționalitate limitată, de aceea în cazul unor
operații sunt necesare mai multe instrucțiuni. Acest mod de rulare este utlizat
în principal când canalul de transfer dintre memorie și procesor este mai mic
de 32 de biți, având în acest caz un câștig din simplul fapt că la procesor
ajung mai multe instrucțiuni prin acest canal de transfer. Totodată modul
Thumb este utilizat și în cazuri când codul trebuie să aibă o dimensiune
redusă, de exemplul în cazul aplicațiilor de pe telefoanele mobile.
Pentru o performață superioară în zona DSP și totodată și în cadrul
aplicațiilor multimedia, au fost adăugate o serie de instrucțiuni specifice
arhitecturilor DSP (instrucțiuni speciale pentru operații SIMD, procesare
semnal digital convertit din semnal analog etc). Această extensie este
specificată prin litera “E” în denumirea procesorului ( ARMv5TE, ARMv5TEJ ).
4
Jazelle DBX (Direct Byte eXecution) permite platformelor ARM recente
să execute cod Java în hardware existând astfel un al treilea mod de execuție
(pe lângă Thumb și ARM). Utilizarea majoră a acestei opțiuni este atunci când
vine vorba de jocuri și aplicații bazate pe tehnlogia Java (J2ME) scopul
principal fiind mărirea vitezei de execuție. Astfel o mașină virtuală care este
programată având în vedere acest lucru poate rula părți ale codului Java în
hardware, apelând la software pentru pentru operații mai complicate. ARM
pretinde că aproximativ 95% din bytecodul unui program normal ajunge să fie
procesat de hardware. Primul procesor din seria ARM care a suportat acest
lucru este ARM926EJ-S. Extensia Jazelle este specificată de litera “J” care
apare în denumirea procesorului.
În momentul actual ARM a ajuns la versiunea 11 (ARM11) arhitecturile
din această clasă fiind următoarele: ARM1136J(F)-S, ARM1156T2(F)-S,
ARM1176JZ(F)-S, ARM11 MPCore toate având instrucțiuni SIMD, cât și un
coprocesor pentru realizarea de calcule în virgulă mobilă optimizând astfel
codarea și decodarea diverselor formate (MP3, AVI, 3GP ...), cât și
transformările geometrice (2D, 3D). Procesorul ARM1176JZ(F)-S din această
clasă este folosit în iPhone frecvența de lucru a acestuia fiind de 620 MHz.
De menționat este faptul că ARM nu produce efectiv procesoare bazate
pe designul propriu ci mai degrabă vinde licențe pentru arhitectura
procesoarelor celor interesați. Printre firmele care dețin licențe ARM se
numără: Analog Devices, Atmel, Cirrus Logic, Freescale, Intel, Fujitsu, IBM,
Infineon Technologies, Nintendo, Samsung, Sharp, Texas Instruments,
Broadcom șamd. ARM este cunoscut ca fiind un procesor cu un preț extrem de
ridicat, un singur produs pentru client cu un procesor ARM poate avea o
licența de aproximativ 200 000 de dolari, iar unde sunt implicate diverse
modificări arhitecturale, licența poate depăși suma de 10 milioane de dolari.
5
GCC
Când vine vorba de dezvoltare pe sisteme embedded limbajele folosite
pentru realizarea părților de bază și aplicațiilor sunt în mare parte C,C++ și
Limbajul de Asamblare specific mașinii respective. Cum lumea embedded este
dominată în mare parte de procesoare ARM suita de dezvoltare căutată
trebuie să aibă (și) suport pentru diversele tipuri de procesoare din această
clasă. Un studiu referitor la uneltele de dezvoltare pe diverse platforme ne
prezintă compilatorul GCC ca fiind unul dintre cele mai răspândite în acest
domeniu, având suport pentru majoritatea arhitecturilor folosite în dezvoltare:
ARM, PowerPC, Atmel AVR, SPARC, x86-64, IA-32/64 etc. Totodată în favoarea
GCC-ului vin și unele platforme populare pe care acesta este folosit ca unealtă
de dezvoltare, uneori de bază: Sony PlayStation, Symbian, Sega, Brew. Acest
lucru oferă un grad înalt de încredere în acest compilator care există de
aproximativ 20 de ani în peisajul IT.
Avantajele pe care această suită de compilare o are este faptul că se
coformează majorității standardelor existente în domeniu și totodată dispune
de o comunitate extrem de activă dezvoltată în special în jurul unor proiecte
open-source de anvergură cum ar fi Linux. Faptul că această suită este open-
source este un avantaj pentru multe companii a căror politică referitoare la
produse folosite este extrem de strictă și bazată pe o selecție atentă a
uneltelor de dezvoltare, cu cât produsul ascunde mai puține lucruri cu atât
șansele de adoptare sunt mai mari.
În același timp pentru a avea un mediu de dezvoltare competitiv avem
nevoie și de integrare cu uneltele folosite curent în dezvoltarea pe astfel de
platforme, compilatorul GNU având în acest sens opțiunea de a genera
informații ce pot fi folosite de unelte de debug precum Trace32 oferit de cei
de la Lauterbach, care este printre cele mai folosite metode de depanare a
programelor direct pe echipamentul hardware. Un avantaj în acest sens este
și faptul că mediul de dezvoltare bazat pe GCC este open-source, respectând
majoritatea standardelor actuale și documentând destul de bine abaterile de
la standarde și extensiile oferite.
6
Istoria acestui compilator începe cu fondatorul GNU, Richard Stallman,
care a început lucrul la compilatorul de C în anul 1985 și l-a terminat 2 ani
mai târziu. Acest compilator nu știa decât să compileze cod C dar cu timpul s-
au adăugat și diverse opțiuni acestei suite de dezvoltare precum: suport
pentru C++, Fortran, Pascal, Objective-C, Java, Ada. Astfel odată cu apariția
tututor acestor extensii numele GNU C Compiler s-a transformat în GNU
Compiler Collection. Toate pachetele din cadrul GCC sunt distribuite sub
licență GNU de către Free Software Foundation (FSF), o corporație non-profit
fondată tot de Richard Stallman.
Simplul fapt că acest compilator a fost de la început open-source a atras
o mulțime de oameni entuziaști în jurul său, aducând astfel un plus de calitate
și opțiuni noi suitei de dezvoltare existente.
Opțiunile adăugate acestei suite au făcut ca aceasta să poată fi folosită
pentru generara de cod pe diverse platforme. Pentru acest lucru este necesară
compilarea unui compilator cu opțiuni specifice pentru generearea unui
cod dependent de o anumită platformă. Acest procedeu se numește
bootstrapping și este cea mai folosită metodă pentru a crea unelte de
dezvoltare specifice unei platforme.
În cazul portării compilatorului GCC pentru a compila pe platforma ARM
se procedează în următorul mod (indicații de pe site-ul http://gnuarm.org):
1. cd [binutils-build]
2. [binutils-source]/configure --target=arm-elf --prefix=[toolchain-prefix] --
enable-interwork --enable-multilib --with-float=soft
3. make all install
4. export PATH="$PATH:[toolchain-prefix]/bin"
5. cd [gcc-build]
6. [gcc-source]/configure --target=arm-elf --prefix=[toolchain-prefix]
--enable-interwork --enable-multilib --with-float=soft --enable-
languages="c,c++" --with-newlib --with-headers=[newlib-
source]/newlib/libc/include
7. make all-gcc install-gcc
8. cd [newlib-build]
7
9. [newlib-source]/configure --target=arm-elf --prefix=[toolchain-prefix]
--enable-interwork --enable-multilib --with-float=soft
10.make all install
11.cd [gcc-build]
12.make all install
Aici procesul de bootstrapping se face cu ajutorul procedurii de cross-
compiling(cea mai folosită procedură pentru realizarea bootstrapping-ului
pentru compilatoarele de C) Aceasta constă în folosirea unui compilator care
poate genera cod și pentru alte platforme (de unde și denumirea de cross-
compiling). Acest compilator generează în cazul de față pachetul binutils
pentru platforma arm în formatul de binar elf(pașii 1, 2, 3), după aceea
generează un compilator minimal folosit doar pentru compilarea bibliotecilor
(pașii 5, 6, 7). După ce acest compilator intermediar a fost creat vom trece la
compilarea bibliotecilor cu el (pașii 8, 9, 10). După acest lucru urmează pasul
final și anume crearea compilatorului (pașii 11, 12). Datorită faptului că
mediul de dezvoltare este Windows s-a folosit cygwin care vine cu un
compilator disponibil pentru instalare în formă binară. Acest compilator a fost
folosit pentru compilarea inițială și implicit pentru realizarea procesului de
bootstrapping.
Această procedură este foarte asemănătoare pentru TOATE platformele
pentru care compilatorul știe să genereze cod. Având în vedere acest lucru nu
este deloc surprinzător faptul că gcc este suita care dispune de o cea mai
mare răspândire când vine vorba de numărul de arhitecturi suportate:
– Alpha
– ARM
– Atmel AVR
– HC12
– Blackfin
– H8/300
– IA-32, IA-64
– x86-64
– Motorola 68k
8
– PowerPC
– MIPS
– System/390/zSeries
– SPARC
– R8C/M16C/M32C
– Motorola 88k
– NS32K
– AVR32
Este în consecință unul dintre cele mai populare compilatoare fiind
portat pe multe platforme cu posibilități de adaptare extrem de ușoare pe
diverse tipuri de arhitecturi. Referindu-ne la arhitectura ARM putem observa
că versiunile de GCC suportă până și cele mai recente procesoare de la ARM
și anume cele din generația ARM11 ( arm1136j(f)-s, arm1176jz(f)-s ), ceea ce
reprezintă o dovadă a activității din jurul acestui proiect.
Compilatorul folosit poate fi unul din cele disponibile pe site însă
acestea pot fi probabil realizate cu unele opțiuni care nu sunt dorite de
dezvoltator. De aceea este recomandat ca fiecare dezvoltator de aplicații
pentru platforme embedded să analizeze, înainte de a dezvolta, opțiunile
oferite de pachetele disponibile și dacă acestea corespund cerințelor sale să
folosească pachetul respectiv. În cele mai multe cazuri unele funcții din
biblioteca standard trebuie rescrise, datorită faptului că ele nu au cum să fie
conștiente de modul în care informația poate fi obțiunta de la hardware.
Scopul de bază al compilatorului este acela de a genera cod iar biblioteca
standard inclusă trebuie să fie legată cumva de platforma pe care rulează
pentru a oferi informații corecte. În cazul folosirii suitei GCC pe platformele
x86-x64, IA-32/64 biblioteca standard a C-ului (libc) oferă o interfață către
sistemul de operare (Windows, Linux, BSD, Solaris ... ). Multitudinea de
arhitecturi existente nu permite dezvoltarea unei conexiuni cu platforma
pentru fiecare set de bibliotecă standard libc. De aceea de cele mai multe ori
este recomandată obținerea uneltelor de dezvoltare prin bootstrapping din
simplul motiv că există un control mai mare asupra modului în care acestea
funcționează.
9
SCons
SCons este o soluție de build construită în așa fel încât să ofere
utlizatorului mult mai multe opțiuni decât unelte precum suita autotools.
Autotools, deși este printre cele mai folosite unelte, este și printre cele mai
neprietenoase. Din punct de vedere arhitectural SCons separă analiza
dependințelor de celelalte procese implicate într-un proces de build oferind în
acest sens o interfață disponibilă tuturor platformelor pe care poate rula
limbajul Python.
SCons oferă astfel o interfață în care oricine cu un minim de efort își
poate crea un mediu de rulare a scriptului de compilare. Avantajul major este
faptul că având la dispoziție un limbaj de programare, Python în cazul de față,
putem rezolva anumite probleme care cu alte unelte de management ale
procesului de build erau aproape imposibil de rezolvat.
SCons oferă extrem de multe opțiuni fără ajutorul unor utilitare externe
limbajului Python. Un exemplu destul de simplu dar întâlnit în multe cazuri
este schimbarea versiuni de build, sau mai degrabă incrementarea acesteia
când o nouă versiune oficială urmează să fie lansată. Acest lucru se realizează
doar în cadrul scriptului de build, realizat în Python, unde sunt disponibile
unelte de procesare de text, scriere și citire de fișiere cât și posibilitatea de a
executa comenzi specifice sistemului de versionare folosit în dezvoltare. Un
exemplu de rezolvare a incrementării automate a versiunii odată cu lansarea
unui build oficial este prezentat în continuare:f = open('version.txt');
line = f.readline();
f.close();
if line[-1] == '\n': line = line[:-1]
arr = string.split(line,'.')
print line
x = int(arr[0])
y = int(arr[1])
z = int(arr[2])
z+= 1
print "%d.%d.%d" % (x,y,z)
Având la dispoziție un astfel de limbaj putem realiza lucruri care cu
10
unelte precum CMake, qmake sau autotools ne-ar fi imposibil. Toate acestea
datorită faptului că interfața oferită programatorului este aceeași oferită și de
limbajul Python standard cu câteva condiții care trebuiesc respectate în
vederea integrării. Utilizatorul nu trebuie să respecte anumite sintaxe, decât
unele reguli de bază ale funcționării acestui model. Pentru cei care nu sunt
familiari cu Python există destule exemple pe care le pot urma, în cele din
urmă ajungându-se și la folosirea limbajului Python pentru realizarea
anumitor operații (obținerea datei, definirea unor macrouri în opțiunile de
compilare etc).
Un alt avantaj important al acestei soluții este oferirea unui sistem de
caching. Acest lucru înseamnă că dacă am modificat doar o parte a codului și
vrem recrearea aplicației cu noile modificări incluse vom avea cel mult un
timp la fel de mare ca în cazul creării aplicației de la 0. Asta se datorează
faptului ca SCons are opțiunea de a păstrea fișierele intermediare create într-
o locație definită de utilizator. În cazul în care nu avem modificări care ar
putea modifica fișierele acestea, ele sunt luate din cache scurtând astfel timpii
necesari realizării lor. Dezavantajul acestei metode este faptul că este necesar
un spațiu adițional, iar fișierele odată create vor rămâne permanent în acea
locație dacă nu se intervine. De obicei aceasta nu constituie o problemă
majoră, putându-se face curățenie ori de câte ori dimensiunea totală a
fișierelor din locație devine (incomod de) mare.
Dintre opțiunile SCons mai menționăm și posibilitatea rulării de mai
multe sarcini în același timp prin specificarea opțiunii -jN unde N este
numărul de procese care pot fi rulate simultan. Această opțiune este prezentă
și în utilitare precum make. SCons poate totodată lucra cu fișiere proiect ale
altor medii de build precum Visual Studio 6 sau Visual Studio 2003. Acest
lucru permite o mai bună integrare cu uneltele existente.
SCons este nu în ultimul rând folosit de extrem de multe proiecte de
dimensiuni mari, proiecte ai căror timpi de compilare sunt pe măsură.
Principalul avantaj care este invocat este analiza dependințelor, lucru extrem
de greu de menținut pe un sistem precum make. Acest avantaj garantează că
nu va fi niciodată nevoie de un “clean build”, adică pornirea de la 0 a execuției
pașilor de obținere a produsului din surse.
11
Sistemul de caching oferit de SCons se bazează pe o semnătură digitală
asociată fiecărui fișier, semnătură care este calculată cu ajutorului
algoritmului MD5. În funcție de acest lucru fișierul este preluat din cache fără
să fie compilat dacă semnătura corespunde. Deși mecanismul de verificare a
dependințelor cu semnătură digitală este mult mai robust există și unele
lucruri care nu sunt foarte intuitive pentru un utilizator make. De exemplu
comanda următoare:
touch file.c
obligă în cazul folosirii make recompilarea fișierului datorită faptului că make
folosește timpul pentru determinarea necesității ca un fișier să fie recompilat.
Acest lucru în cazul SCons nu se întâmplă datorită faptului că modificarea
datei fișierului nu modifică semnătura digitală asociată.
În principiu SCons este potrivit unor proiecte de dimensiune mare
datorită faptului că elimină toate dificultățile legate de managementul
uneltelor de build.
12
Paralelizare
Odată cu evoluția în domeniul aplicațiilor s-a ajuns la dimensiuni de cod
de neimaginat cu câteva decenii în urmă. Astfel există proiecte open-source a
căror compilare de la zero pe o mașină cu putere medie de procesare durează
una sau mai multe zile. Unele din aceste proiecte sunt: OpenOffice, KDE sau
distribuția de Linux Gentoo.
De cele mai multe ori utilizatorii normali nu sunt cei care compilează
aceste pachete, ele fiind disponibile sub formă binară ca download pe Internet
sau incluse în repository-ul distribuțiilor folosite (debian, ubuntu, suse).
Totodată trebuie să avem în vedere și faptul că există dezvoltatori ale
acestor produse, iar cum timpii de compilare ale surselor sunt foarte mari
trebuie să folosească diverse metode care să îmbunătățească acest lucru. În
caz contrar produsul va avea o evoluție lentă datorită timpilor mari de răspuns
din partea echipei de dezvoltare. Pentru acest lucru s-a luat în considerare și
compilarea distribuită. Avem astfel o întreagă rețea de calculatoare la
dispoziția noastră pentru a rula compilare distribuită. Desigur în acest caz
trebuie să găsim o variantă de distribuire eficientă.
Pentru acest lucru avem mai multe implementări de distribuire a
sarcinilor pe mai multe calculatoare. Cele mai cunoscute sunt MPI,
Mosix/OpenMosix, OpenMP.
MPI prezintă anumite avantaje prin simplul fapt că poate fi integrată
ușor într-un limbaj însă este doar o interfață de programare distribuită,
neoferind soluții specifice unui anumit task. Distribuirea procesului de
compilare cu ajutorul MPI este un lucru destul de greoi care implică printre
altele migrarea unor fișiere pe mașinile care participă la distribuirea
sarcinilor. MPI este mai degrabă adecvat unor operații de calul de dimensiuni
foarte mari, el necesitând o adaptare corespunzătoare fiecărui calcul cerut.
(Open)Mosix reprezintă o altă abordare a calculului paralel și are în
vedere adaptarea la nivel jos (kernel) a unei interfețe astfel încât utilizatorul
să nu modifice deloc aplicația. Nemodificarea aplicației pare din păcate
singurul avantaj, optimizarea pentru anumite sarcini devenind destul de
13
dificilă. Totuși când vine vorba de calcul intensiv devine extrem de benefică
posibilitatea de distribuire a proceselor pe mașini care au instalat
(Open)Mosix. În acest sens este mai puțin eficient decât MPI dar mult mai
ușor de integrat.
OpenMP reprezintă o abordare a calculului paralel care se bazează pe
extensia limbajului C. Acestui limbaj i-au fost adăugate anumite directive pe
care compilatoare capabile să le înțeleagă le interpretează prin crearea de cod
specific ( threaduri ) calculului paralel. Dezavantajul OpenMP este limitarea la
o singură mașină, de obicei un calculator cu putere de procesare extrem de
mare. Scopul principal al OpenMP este realizarea rapidă a treadurilor într-o
manieră ușoară și portabilă. OpenMP este deja standard și este implementat
în diferite compilatoare precum cel de C/C++ și Fortran ( Visual Studio 2005,
GCC >=4.2, Intel Compiler, Sun Studio). Versiunea cea mai recentă a
standardului este 2.5.
Ajungem să vedem că soluțiile aparent generice pentru calcul paralel
sau distribuit nu pot face față cu brio unei sarcini precum cel de distribuire a
compilării, datorită dezavantajelor reprezentate de transferul fișierelor.
Totodată și integrarea acestor soluții ar necesita modificări substanțiale în
structura metodei de build, lucru care de cele mai multe ori nu este dorit.
Concluzia rămâne că este nevoie de o soluție specifică procesului de
compilare, o soluție care să se integreze extrem de ușor cu uneltele existente.
Astfel trebuie studiați pașii necesari pentru compilarea fișierelor de tip C sau
C++ pentru observarea părților care pot fi paralelizate.
În principiu în cazul limbajului C++ există mai multe etape din
momentul finisării editării unui fișier. Acesta este dat compilatorului, care în
primă fază îl preprocesează, după aceea urmând procesul de compilare
efectivă. O parte interesantă a acestui proces este faptul că fișierul
preprocesat nu are nici o dependință față de platforma pe care se află. De
exemplu dacă avem un proiect cu multiple fișiere header, după preprocesare
ele sunt incluse în fișierul preprocesat acesta putând fi compilat pe orice
platformă. Conform Legii lui Amdahl o sarcină poate fi împărțită în două parți,
una paralelizabilă și una neparalelizabilă. În cazul de față, preprocesarea este
un proces care nu poate fi paralelizat. În schimb compilarea este o sarcină în
14
totalitate paralelizabilă astfel ea putând fi distribuită pe mai multe mașini.Pe
acest principiu de bazează unelte precum distcc sau IncrediBuild.
IncrediBuild este un sistem de build pentru Windows care se
integrează cu Visual Studio 6.0, Visual Studio 2003, Visual Studio 2005. Timpii
de compilare realizați de IncrediBuild sunt cam de 6-7 ori mai mici atunci
când sunt folosite aproximativ 10-12 mașini. Avem astfel o eficiență sporită,
timpii de așteptare mult mai mici fiind un avantaj în procesul de depanare al
produselor software. Din păcate singurul mediu de dezvoltare pentru care
IncrediBuild are suport este Microsoft Visual Studio. Compania Xoreax cea
care dezvoltă acest produs este totodată dezvoltatoarea unei soluții de calcul
paralel numită XGE (Xoreax Grid Engine) care este folosită în diverse domenii
unde este necesară putere de procesare care depășește capacitățile unui
singur calculator.
Din domeniul open-source o soluție asemănătoare este distcc. Această
unealtă se integrează ușor cu compilatorul gcc dar poate suporta teoretic și
alte compilatoare (foarte greu de integrat pe windows datorită diferențelor
dintre o platformă Unix (Posix) și una Windows). Principiul după care
funcționează această aplicație este următorul: pe fiecare mașină implicată în
procesul de compilare există un demon (server) care așteaptă sarcini (fișiere
de compilat). Când acestea vin el le compilează și le trimite înapoi ca răspuns.
Serverul poate executa mai multe procese de compilare simultan în funcție de
opțiunile cu care este pornit (îi sunt specificate numărul maxim de procese
care se pot executa în paralel). La capătul opus clientul înglobează opțiunile
tipice unui compilator cu puține modificări. Clientul preia fișierul, îl
preprocesează, apoi trimite rezultatul preprocesării către un server care
compilează și trimite rezultatul înapoi. Paralelizarea se face prin simplul fapt
că clientul trece mai departe și nu așteaptă fișierul continuând preprocesarea
și trimiterea către un server a sursei preprocesate. Felul în care aplicația
client așteaptă în cazul operațiilor de linking sau de realizare a bibliotecilor e
prin folosirea unui mecanism de locking pe fișiere, mecanism care nu permite
accesul decât după terminarea procedurii de locking. Un exemplu simplu ar fi
realizarea unei biblioteci statice. Această bibliotecă se realizează cu ajutorul
unor fișiere obiect intermediare. Procedeul de realizare al fișierelor obiect se
15
face în paralel însă realizarea bibliotecii statice necesită ca toate fișierele să
fie gata. Astfel avem fișierele obiect aflate în procesul de locking până când
datele de la servere sunt obținute. După ridicarea lock-ului de pe fișier
acestea pot fi deschise ( funcțiile sunt deblocate astfel ) putându-se accesa
pentru task-ul de creare al bibliotecii.
Unelte precum distcc pot fi integrate în cadrul oricărui toolchain de
dezvoltare C/C++ care suportă opțiunea de preprocesare a surselor. Odată
având fișierul preprocesat el poate fi trimis mai departe către mașini a căror
putere de procesare este oferită. Datorită nevoii de viteză este de preferat ca
mașinile să fie cele din rețeaua locală.
16
Integrare și Probleme
Folosirea uneltelor de mai sus pentru crearea unui sistem de dezvoltare
poate fi o simplă alegere la începerea unui proiect. În cazul de față adoptarea
noii metode de dezvoltare a însemnat înlocuirea unei metode existente, mai
exact bazată pe uneltele comerciale distribuite de Texas Instruments
(TMS470). Astfel problemele apărute au fost în mare parte de
incompatibilități între cele două platforme. Datorită faptului că GCC-ul este o
suită generică de dezvoltare nu se puteau folosi majoritatea funcțiilor de
bibliotecă care se refereau la componente precum memorie, sistem de fișiere (
apeluri malloc, fopen, open etc ). Aceste funcții sunt implementate doar pe
platformele mai des folosite precum Windows, Linux și unde există un anumit
standard.
Primele incompatibilități au fost cele legate de sintaxă, mai exact de
lipsa de toleranță din partea compilatorului gcc care este mai conform
standardului decât suita de la Texas Instruments.
Incompatibilități care au apărut ulterior au fost cele legate de tipuri de
date cât și de comportamentul diferit al unor funcții.
Ca orice soluție embedded nu poate fi testată în totalitate pe platforma
hardware. În acest sens este folosit un simulator care rulează în mediul
Windows și este integrat cu suita de dezvoltare Microsoft Visual Studio.
Incompatibilități ale tipurilor de date comparative între platforme:
Problemă GCC TMS470 Soluție
sizeof(wchar_t) 4 2 -fshort-wchar
time_t long unsigned int adaptare cod
Totodată funcțiile de timp aveau comportamente diferite. Funcțiile de
timp din cadrul suitei TMS470 aveau ca referință data de 1 Ianuarie 1900 pe
când în GCC referința era conform standardului de la 1 Ianuarie 1970. Soluția
a fost convertirea codului la standard.
17
Pentru contracararea dimensiunii tipului wchar_t, folosit la afișarea
textelor în diverse limbi s-a folosit opțiunea de compilator -fshort-wchar.
Totodată pentru a genera cod pentru procesorul arm folosit se folosește
opțiunea -mcpu=TIP_PROCESOR unde TIP_PROCESOR poate fi unul din
valorile suportate de compilator. În cazul arhitecturii ARM opțiunile pentru
compilatorul GCC 4.2.2 sunt: ‘arm2‘, ‘arm250‘, ‘arm3’, ‘arm6’, ‘arm60’,
‘arm600’, ‘arm610’, ‘arm620’, ‘arm7’, ‘arm7m’, ‘arm7d’, ‘arm7dm’, ‘arm7di’,
‘arm7dmi’, ‘arm70’, ‘arm700’, ‘arm700i’, ‘arm710’, ‘arm710c’, ‘arm7100’,
‘arm7500’, ‘arm7500fe’, ‘arm7tdmi’, ‘arm7tdmi-s’, ‘arm8’, ‘strongarm’,
‘strongarm110’, ‘strongarm1100’, ‘arm8’, ‘arm810’, ‘arm9’, ‘arm9e’,
‘arm920’, ‘arm920t’, ‘arm922t’, ‘arm946e-s’, ‘arm966e-s’, ‘arm968e-s’,
‘arm926ej-s’, ‘arm940t’, ‘arm9tdmi’, ‘arm10tdmi’, ‘arm1020t’, ‘arm1026ej-s’,
‘arm10e’, ‘arm1020e’, ‘arm1022e’, ‘arm1136j-s’, ‘arm1136jf-s’, ‘mpcore’,
‘mpcorenovfp’, ‘arm1176jz-s’, ‘arm1176jzf-s’, ‘xscale’, ‘iwmmxt’, ‘ep9312’.
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
calculatoare și un server de source-control.
27
Bibliografie
http://www.arm.com/
http://en.wikipedia.org/
http://scons.org/
http://distcc.samba.org/
http://gnuarm.org/
http://gnuarm.com/
http://gcc.gnu.org/
http://www.lauterbach.com/
http://www.bluewatersys.com/corporate/uni/unikit/sw/gnutoolchain.php
http://cygwin.com/
http://distcc.samba.org/doc.html
http://en.wikipedia.org/wiki/ARM_architecture
http://en.wikipedia.org/wiki/GNU_Compiler_Collection
http://gnuarm.org/pdf/gccint.pdf
http://gnuarm.org/pdf/ld.pdf
http://gnuarm.org/pdf/gcc.pdf
http://scons.org/doc/production/PS/scons-design.ps
http://openmosix.sourceforge.net/
http://en.wikipedia.org/wiki/Parallel_computing
http://www.mosix.org/
http://www.openmp.org/drupal/mp-documents/spec25.pdf
http://www.xoreax.com/
http://www.embeddedrelated.com/groups/lpc2000/show/21464.php
http://brew.wardco.com/
http://brew.wardco.com/global_static_objects/
http://www.embedded.com/design/201000339
http://git.or.cz/
http://www.selenic.com/mercurial/wiki/
28