Prima data cand am citit un document oficial cu referire la aceasta terminologie a fost cand am deschis documentatia de AUTOSAR.
(https://www.autosar.org/fileadmin/user_upload/standards/classic/4-3/AUTOSAR_SWS_MemoryMapping.pdf)
Initial am gasit foarte greoaie aceasta documentatie fiindca implica a cunoaste destul de multe detalii despre:
Procesul de compilare
Memorie
Alocarea de variabile
Accesul variabilelor
Scripturi de linkare
Menirea acestui articol este de a face mai inteles acest aspect al managementului de date ce ajung in memorie.
Ce este memory map?
Dupa cum ii spune si numele, Memory Map reprezinta harta de memorie a programului ce ajunge sa fie rulat, fie ca vorbim de embedded sau de programare de nivel inalt. Indiferent de deomeniu, un lucru este cert si anume ca, un proiect va beneficia din folosirea acestui concept.
Motivele care fac justificata implementarea unui astfel de concept sunt multe , dar cele mai importante sunt urmatoarele:
Memorie compacta
Acces mai rapid si mai controlat
Date organizate
Siguranta sporita
Optimizare mai eficienta (spatiu consumat)
Debug mai eficient
Se pot implementa mai usor mecanisme de control si test pentru memorie
Control asupra initializarii proiectului
Pentru a intelege indeplin beneficiile MemMap, trebuie sa facem un ocol incat sa intelegem cum se stocheaza datele in memorie.
Segmente de memorie
Procesul de compilare implica o serie de pasi pana ce datele procesate ajung in binarul final. Fara a intra in amanunt, principalii pasi in constructia unui binar de C sunt urmatorii:
Preprocesare
Compilare
Asamblare
Link-Editare
Daca primii 3 pasi se ocupa de construirea fisierelor obiect care vor contine codul masina, pasul final reprezinta operatiunea care “leaga” toate sursele compilate si aseaza datele in memorie, dupa reguli standardizate si optional, pe baza unui script definit de programator.
Intr-un cod C, datele sunt organizate pe segmente de memorie dupa cum urmeaza:
.rodata - read-only data - segmentul de constante
static const unsigned char module_const = 0xAA;
const unsigned short arr_thresholds[4] = {12000, 12500, 14000, 14500};
.data - segmentul de date initializate
static int variabila = 0xDEADBEAF;
int global_var = 0;
.bss - “better save space” - segment de date neinitializate
static char test_c;
unsigned long s_counter;
const char my_symbol;
De menetionat ca acest segment nu ocupa spatiu in fisierul .o generat dupa compilare.
.text - stocheaza instructiunile/ functiile
mov ebp, esp
sub esp, 0x20
int main(void){}
Optional (depinde de arhitectura)
.sdata - small data segment - date mici initializate
.sbss - small bss segment - date mici neinitializate
- declarari asemenea segmentului .data si .bss insa unele arhitecturi stocheaza date “mici” - care se incadreaza intr-un prag (ex: max 32 bytes) - incat compilatorul sa genereze instructiuni mai rapide pentru accesul la aceste variabile.
Linker si segmente
Scopul linkerului este sa adune toate sursele asamblate si sa stocheze fiecare bucata de data, in segmentul asociat. Regulile standard pe care le urmeaza, fara ca un programator sa intervine, sunt cele prezentate mai sus.
In acest punct, linkerul poate primi un set de reguli aditionale, din exterior. Poate va intrebati ce reguli mai pot fi aduse pe langa cele definite prin segmentele standard? Nu sunt suficiente acestea? Raspunsul este nu. Si cel mai bine reiese acesta dintr-o alta intrebare: ce se intampla daca datele sunt aranjate in memorie dupa cum urmeaza?
unsigned char a[3]={1,2,3};
unsigned long b=4;
unsigned char c=5;
unsigned short d=6;
unsigned char e=7;
unsigned long f=8;
Sa construim zona de .data in care se vor aloca aceste variabile, considerand o arhitectura unde datele circula pe bus pe 8 bytes, iar organizarea in memorie este big-endian.
Aliniamentul de mai sus este dat de regula urmatoare:
Variabila stocata pe 1 byte -> nu necesita aliniament
Variabile stocata pe 4 bytes -> necesita aliniament de 4
Variabile stocata pe 8 bytes -> necesita aliniament de 8
Este evident ca organizarea in memorie nu este cea mai ordonata. Se pot observa cu usurinta “gaurile” lasate de constrangerile de aliniament. Aditional, data fiind arhitectura pe 8 bytes, implicit compilatorul a considerat zona de .data sa fie aliniata la 8 bytes (aceasta implica inca o gaura de memorie inevitabila intre zona definita anterior si .data).
Compilatorul va plasa aceste date incat sa faciliteze accesul prin instructiuni specifice dimensiunilor ocupate de variabile. Dar ce se intampla daca datele nu ar fi stocate astfel?
Sunt doua posibilitati in acest caz:
Cazul fericit, compilatorul va genera cod aditional pentru accesul acestor variabile (read/ write) incat sa compenseze pentru aliniamentul la adresa impara.
Cazul nefericit, codul compilat va genera o exceptie la intalnirea primului access de date nealiniate. Din acest punct, fie se trateaza exceptia, fie codul va ramane “agatat” si va astepta un reset - watchdog
Dupa ce am inteles de ce datele noastre trebuie sa respecte aliniamentul la adresa para in memorie, putem merge la pasul urmator si sa intelegem cum putem optimiza fragmentarea memoriei.
Putina curatenie...
Datele noastre ajung sa fie organizate in memorie prin intermediul linkerului. Pentru segmentele standard, regulile sunt clare si sunt date de arhitectura insa nu exista reguli care sa faciliteze fragmentarea minima a memoriei.
Revenind la exemplul anterior de definire a variabilelor:
unsigned char a[3]={1,2,3};
unsigned long b=4;
unsigned char c=5;
unsigned short d=6;
unsigned char e=7;
unsigned long f=8;
Daca am discuta despre acelasi fisier sursa, datele ar putea fi reordonate incat gaurile de memorie sa fie date doar de constrangerea arhitecturala de aliniere a segmentului .data. Ne-am dori in cele din urma sa avem organizarea astfel:
unsigned long b=4;
unsigned long f=8;
unsigned short d=6;
unsigned char e=7;
unsigned char c=5;
unsigned char a[3]={1,2,3};
Prin aceasta simpla tehnica de reordonare a datelor, am obtinut 15 bytes de memorie disponibila. Desigur, aceasta se preteaza asupra unui singur fisier. Nu putem aplica aceiasi tehnica asupra celorlalte surse? Raspunsul este da, insa, efortul necesar nu este justificabil.
Pe langa aceasta, devine complicat cand dorim sa folosim structuri de date sau sa adaugam o variabila noua fiindca trebuie sa avem mare grija cum ordonam toate datele.
Pentru a evita munca de “chinez batran”, avem la dispozitie urmatoarea metoda.
Alocarea de date in segmente specifice…
O foarte mare proportie de linkere ofera posibilitatea definirii unor zone de memorie, care sa respecte anumite reguli.
Cum ar fi sa spunem linkerului, sa ne stocheze datele conform urmatoarelor reguli:
Toate variabilele de 1 byte le vei pune in zona VAR_8BIT
Toate variabilele de 4 bytes le vei pune in zona VAR_32BIT
Toti pointerii ii vei pune in zona de VAR_PTR
Toate constantele de 8 bytes le vei pune in zona de CONST_64BIT
Samd.
?
In procesul de linkare, datele sunt fortate sa stea in zonele specificate de noi, daca utilizan directivele de linkare in cod.
Astfel, considerand o variabila “loc_Counter_32bit”, putem forta plasarea ei in zona “SEG_VAR_32BIT” astfel:
Pentru GreenHills:
#pragma aling(4) - aliniaza zona la 4 bytes
#pragma alignvar (4) - aliniaza prima variabila la 4 bytes
#pragma ghs section sect=”SEG_VAR_32BIT” - plaseaza variabilele ce urmeaza in zona specificata
unsigned short loc_Counter_32bit = 60000u;
#pragma ghs section sect = default - revenire la modul implicit de alocare al datelor
Pentru Metrowerks:
#pragma DATA_SEG (DPAGE SEG_VAR_32BIT | “Default”)
unsigned short loc_Counter_32bit = 60000u;
Pentru Gcc:
unsigned short loc_Counter_32bit __attribute__ ((section ("SEG_VAR_32BIT"))) = 60000u;
Se pot observa mai multe detalii urmaring linkul catre specificatia de AUTOSAR si accesand capitolul 11.
Un lucru este cert: fiecare linker are propria sintaxa.
Avantajul acestei abordari este dat de organizarea in memorie a datelor. Ipotetic folosind 3 segmente specifice de 8BIT, 32BIT si 128BIT pentru variabile, constante, pointeri samd, memoria noastra va contine fragmente doar intre aceste zone.
Se poate observa cum datele sunt alocate continuu. Avantajele prezentate la inceputul
capitolului reies din aceasta poza.
In tutorialul cu privire la configurarea scriptului de linker, respectiv a memory mapului, voi arata concret cum se poate face configuratia pentru a beneficia de Memory Map.
Comments