top of page

Conceptul de MemMap

Actualizată în: 4 apr. 2018

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:

  1. Cazul fericit, compilatorul va genera cod aditional pentru accesul acestor variabile (read/ write) incat sa compenseze pentru aliniamentul la adresa impara.

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

116 afișări0 comentarii

Postări recente

Afișează-le pe toate

Calea catre main() - intro

In liceu am invatat ca un program incepe de la main(), acest lucru fiind valabil in mai multe limbaje de uz intens. De ce main()? Ce-i...

Comments


bottom of page