Gestiunea Memoriei

Introducere

Memoria este considerata una dintre cele mai importante resurse fizice fundamentale ale oricarui sistem de calcul. Aceasta trebuie gestionata foarte atent, deoarece are o deosebita importanta in procesul de functionare al sistemului de calcul.

In prezent, a crescut incredibil de mult necesitatea memoriei a multor aplicatii software. Chiar daca tehnologia a evoluat in acelasi timp cu nevoia acestor aplicatii de memorie, exista insa, fie limitari tehnice, fie financiare, ce nu pot permite tuturor utilizatorilor sa se bucure de aceasta evolutie.

Orice programator si-ar dori ca memoria calculatorului sa fie nelimitata, nevolatila, si mai ales, extrem de rapida.Din pacate, acest lucru inca nu este posibil, insa s-au incercat toate variantele pentru a facilita accesul la memorie. De aceea, echipamentele s-au impartit in doua categorii, astfel incat sa poata inregistra, pastra si reda informatia, pentru a creste eficienta memoriei, si implicit a calculatorului. Astfel, exista memoria interna, ce are rolul de a stoca informatii si programe, inainte ca acestea sa fie utilizate in urma unui proces executat de CPU(Central Processing Unit), si memoria externa, ce este utilizata pentru pastrarea datelor pentru o perioada indelungata.

2.Structura ierarhica de organizare a memoriei

Memoria cache

Informatiile cele mai recent utilizate de CPU(unitatea central de prelucrare) sunt continute de memoria cache.Aceasta are un timp de acces foarte mare,dar are capacitate redusa. CPU, la fiecare accesare verifica mai intai daca datele dorite se afla in memoria cache si abia apoi cauta si in celelalte memorii.

Memoria principala

Aceasta include instructiunile si datele pentru toate procesele sistemului . Cand un proces se termina spatiul memoriei principale va fi eliberat si poate fi ocupat cu alte date. Viteza de acces cu memoria a memoriei principale este destul de mare, insa comparativ cu cea a memoriei cache este mai mica .

Memoria secundara

Aceasta apare la sistemele care au mecanisme de memorie virtuala si poate fi vazuta ca o extensie a memoriei principale , insa datele nu se sterg atunci cand sistemul se opreste , deci nu este volatila. Accesul la memoria secundara se face mai lent.

Memoria de arhivare

Aceasta este gestionata de catre utilizator si detine: baze de date, fisiere.

Figura 1: Organizarea ierarhica a memoriei

Memoria externa este formata din memoria de arhivare si cea secundara. Datele continute de acestea pentru a putea fi accesate de CPU trebuie mutate in memoria interna, pe cand cele din memoria principala si cache sunt accesate in mod direct de catre CPU

3.Memoria virtuala

Memoria virtuala este o metoda ce permite ca totalitatea memoriei ocupate de program sa poata depasi memoria fizica. Cu alte cuvinte, aceasta stocheaza temporar date la care se apeleaza de fiecare data cand un program are nevoie de mai multa memorie RAM, decat poseda calculatorul. Daca calculatorul are memoria RAM mai mare, programele se vor executa mult mai rapid.

Atunci cand programele au nevoie de un spatiu mai mare de stocare, iar memoria RAM poate impiedica rularea acestora, spatial de pe hard disk este folosit de catre sistemul de operare ca o prelungire a memoriei RAM.

Ca si exemplu, putem avea in vedere un sistem de operare pe 32 de biti. Un calculator are in mod normal 64 Mb de RAM, iar daca se foloseste memoria virtuala, programul poate dispune de o memorie de pana la 4 Gb.

Memoria virtuala prezinta ca si avantaj faptul ca toate operatiile sunt facute de catre sistemul de operare. Aceasta poate fi si multifunctionala, adica, de exemplu, in acelasi timp in care un program asteapta o comanda de la tastatura, un alt program deja se executa, fara a deranja si fara a intrerupe niciun alt proces.

Spatiul de adrese este divizat in mai multe parti ce pot fi stocate in memoria interna, atunci cand este nevoie de acestea pentru executarea unui program, iar atunci cand nu mai este nevoie de ele, acestea sunt transferate in cadrul memoriei secundare. Acest spatiu de adrese al unui program prezinta:

Segmentul de cod -> contine codul executabil al unui program si este de tip read only;

Segmentul de date -> contine variabilele programului, precum si tablouri, siruri de caractere etc. Acesta isi poate modifica si continutul si dimensiunea in timpul executiei unui program. Este de asemenea, de tip read only;

Segmentul de stiva -> incepe sa creasca de la cea mai mare adresa a spatiului de adrese a procesului catre adresele inferioare ale acestuia. Stiva contine variabilele de mediu precum si linia de comanda cu care a fost lansat in executie.

De exemplu, in figura 2, spatiul de adrese este divizat in 5 parti:

Memoria virtuala admite urmatoarele doua optiuni:

Spatial virtual poate fi utilizat;

Zona de memorie este plasata pe un dispozitv secundar de memorare(extern), ca de exemplu, pe hard disk.

In urmatoare figura este reprezentata legatura dintre cele doua memorii, cea fizica si cea virtuala:

4.Segmentarea

Un segment este o secventa liniara de adrese care incepe de la o adresa minima ( de regula zero ) pana la o valoare maxima.Acestea pot avea dimensiuni distincte ,se pot modifica in functie de nevoile aplicatiilor si pot fi mutate intre memoria interna si cea externa.

Utilizarea memoriei segmentate simplifica manipularea structurilor de date care au dimensiuni variabile. Segmentarea poate oferi si posibilitatea de a partaja proceduri sau date în mai multe procese.Intr-un spatiu bidimensional reprezentarea adreselor presupune existenta numarului segmentului si a adresei interne.

Segmentarea poate fi inlocuita cu unul din urmatorii algoritmi:

Algoritmul prima potrivire(first fit) presupune cautarea in lista de segmente prima zona libera suficient de mare. Spatiul gasit va fi spart in doua parti : prima va fi pentru proces, iar cealalta va ramane ca memorie nefolosita.Aceasta tehnica are o viteza mare, dar strica zonele mari de memorie si produce fragmentare.

De exemplu:Avem lista de spatii libere cu urmatoarele dimensiuni : 10,40,50,30,5 si un proces care are nevoie de o dimensiune egala cu 3. Pentru a afla care este spatiul liber folosit mai intai alegem primul spatiu liber suficient de mare (pentru exemplul ales va fi 10) apoi se va sparge spatiul in unul utilizat si altul de dimensiune 10-3=7.

Algoritmul cea mai buna potrivire (best fit) cauta in toata lista si alege cel mai mic spatiu liber care este potrivit.Acesta are o viteza mai redusa, dar nu distruge zonele mari de memorie.Se produce fragmentare.

Algoritmul Garbage Collector(algoritmul de colectare a gunoiului)

Expresia de colectere a gunoiului se refera la colectarea zonelor de memorie care nu mai pot fi acceste. Zonele care pot fi eliberate sunt parti ale memoriei la care nu se mai poate ajunge prin intermediul pointerilor.Acestea sunt zonele despre care spunem ca sunt inaccesibile.

Un algoritm de garbage collection este implementat in biblioteca si apelat in momentul in care se cere alocarea atunci cand nu mai exista memorie disponibila, sau este incrementat, cand programul ruleaza, pentru a diminua costul colectarii si a reduce durata in care executia programului este intrerupta.

Algoritmul pentru colectarea memoriei disponibile trebuie să gaseasca solutii pentru două problem si anume :identificarea zonelor de memorie care nu mai sunt accesibile si eliberarea spațiului ocupat de zonele inaccesibile.

Algoritmul poate fi exprimat astfel:

initialize() =

tospace = 0

fromspace = N/2

allocPtr = tospace

allocate(n) =

If allocPtr + n > tospace + N/2

collect()

EndIf

If allocPtr + n > tospace + N/2

fail “insufficient memory”

EndIf

o = allocPtr

allocPtr = allocPtr + n

return o

collect() =

swap(fromspace, tospace)

allocPtr = tospace

scanPtr = tospace

– scan every root you've got

ForEach root in the stack – or elsewhere

root = copy(root)

EndForEach

– scan objects in the heap (including objects added by this loop)

While scanPtr < allocPtr

ForEach reference r from o (pointed to by scanPtr)

r = copy(r)

EndForEach

scanPtr = scanPtr + o.size() – points to the next object in the heap, if any

EndWhile

copy(o) =

If o has no forwarding address

o' = allocPtr

allocPtr = allocPtr + size(o)

copy the contents of o to o'

forwarding-address(o) = o'

EndIf

return forwarding-address(o)

Algoritmul trenului

Ori de câte ori obiectele sunt distribuite între diferite sisteme, algoritmi de colectare a gunoiului ar face ca toate acestea să se oprească la fiecare cursă.Pentru a rezolva aceasta problema folosim algoritmul trenului.

Colectarea gunoiului in cadrul acestui algoritm este formata din doua sarcini :

a)Detectia: Colectarea trebuie sa deosebeasca obiectele care sunt in viata(active) de cele nefolositoare. In loc sa caute obiectele nefolositoare , de cele mai multe ori algoritmii folosesc o alta metoda : aceea de a taga si a salva obiectele care sunt in viata.

Referintele facute din afara spatiului obiectului catre obiect se numesc "referinte radacina" . Toate obiectele care au o referinta radacjna trebuie tagate ca obiecte active. Atunci cand algoritmul nu mai gaseste obiecte referentiate active se opreste si are grija ca toate obiectele nefolositoare sa fie nemarcate .

b)Eliberarea memoriei: Există două posibilități diferite de ștergere a gunoiului:

– Salvarea tuturor obiectelor care sunt încă în viață(active, care inca sunt utilizate in programe) prin copierea lor într-un loc sigur și eliberarea întregului bloc umplut cu obiecte gunoi;

– Eliberarea spațiului fiecărui obiect care nu este marcat ca fiind inca in viata.

Figura 3.Gasirea obiectelor care sunt inca in viata (active).

In figura de mai sus este vizualizata detectarea obiectelor care sunt inca active. In memorie intalnim mai multe obiecte. In primul rand A si G care sunt legate direct la pointerii radacina (root) vor fi marcate, dar si B , care poate fi apelat prin A, trebuie sa fie salvat. Cercul format din obiectele C,D si E este tratat ca obiect gunoi.

Ideea de a salva toate obiectele prin copierea lor pare a fi o pierdere de performanță la prima vedere, dar există, un mare avantaj al acestei tehnici: după fiecare colectare a gunoiului memoria este compacta.

In figura de mai jos se prezinta eliberarea memoriei prin salvarea tututor obiectelor care sunt active. Termenul "din spațiu"(from space) este folosit pentru zona de memorie utilizata în prezent de program înainte de colectare a gunoiului, în timp ce "în spațiu"(to space) reprezintă zona de salvare.

Cand trece colectarea gunoiului nu trebuie sa intrerupa pentru perioade mai lungi de timp, partea de detectare deoarece aceasta este principala problema. Este dificil sa gasim un algoritm bun pentru a face acest lucru în pași mici.Acesta este ccazul in care algoritmul trenului ofera o solutie.

Figura 4: Salvarea obiectelor

Ideea principală a algoritmului trenului este de a împărți memoria în blocuri mici și apoi de a marca si a sterge gunoiui pentru fiecare bloc separat. Programul principal este oprit doar în timpul procesării unui singur bloc, iar întreruperile sunt mult mai scurte, în funcție de mărimea pieselor de memorie.

Algoritmul trenului are o soluție pentru problema de blocuri de dimensiuni arbitrare și această soluție este motivul pentru care are acest nume. Pentru o mai bună înțelegere a algoritmului spatial obiect (object space) poate fi văzut ca o stație de cale ferată de mare. Blocurile au o dimensiune fixa si sunt numite acum "mașini". Acestea sunt grupate și formează "trenuri" care pot avea oricate mașini. Există un set amintit pentru fiecare masina si, de asemenea, un set amintit pentru fiecare tren care conține doar de referințe între trenuri.

Figura 5 se bazează pe aceeași structură ca și figura 3, dar acum spațiul obiect este organizat ca o stație de cale ferată. Se compune dintr-un număr ordonat de trenuri, care pot avea un număr arbitrar de mașini, care sunt de asemenea comandate. În acest exemplu, există două trenuri. Pot exista un număr maxim de trei obiecte într-o singură mașină, dar, desigur, un tren poate consta în orice număr de mașini.

Figura 5: Statia de cale ferata

În figura 5, E este, de exemplu, un membru al referinței masinii 1.1, dar nu este un membru al trenului numarul 1. Deoarece algoritmul proceseaza întotdeauna masina cu cel mai mic număr întâi, numai referințele de la mașinile mai mari trebuie să fie luate în considerare la actualizarea seturilor.

Aici si C este o referinta catre D dar pentru ca esteintr-o masina cu numar de ordin mai mic nu este trecut in set. În cazul în care colectorul de gunoi procesează prima masina, obiectul A este salvat și copiat pe un tren cu totul nou, deoarece acesta este referit de un pointer rădăcină. B se face referire numai prin A și, prin urmare, copiat în același tren ca și A.

Acest lucru este foarte important, deoarece structurile de gunoi “mort” acest mod ciclic ajung într-un singur tren. Deoarece C este referit de un obiect al aceluiași tren este copiat la capătul trenului. Acum, prima mașină este goală și poate fi eliberata. Starea de stația de cale ferată, după această primă trecere este prezentată în figura 6.

Figura 6.

Seturile au fost actualizate în mod corespunzător. Acum, primul tren nu este referit de niciunde din afară și, astfel în timpul următoarei iteratii colectorul de gunoi va elibera în condiții de siguranță întregului tren, rezultând într-un spațiu de obiect ca în figura 7.

Figura 7.

Figura 8 prezintă exemplul unui ciclu format din patru obiecte A, B, C și D. După prima invocare a algoritmului trenul 1 este eliberat și obiectul A este mutat în tren 2.Data viitoare A și B sunt mutate pentru a instrui 3 și în timpul următoarei etape A, B și C sunt mutate pentru a instrui 3. Acum, toți membrii ciclului sunt într-un tren, iar următoarea invocarea va elibera structura.

Figura 8.

Următorii pași descriu algoritmul trenului :

Selectați trenul cu cel mai mic număr.

Stergeti intregul tren in cazul in care setul trenului este gol

Selectați masina cu numărul cel mai mic din interiorul trenului. Pentru fiecare element al mașinii:

a) În cazul în care obiectul nu a fost procesat mai devreme va fi salvat intr-un nou tren în cazul în care acesta este o referință radacina .

b) Mutați toate obiectele din aceeași mașină, care sunt accesibile din obiectul salvat în același tren .În această etapă, este necesar să se actualizeze seturile de referință afectate în mod corespunzător.

– Eliberarea masinii si incheierea procesului

Algoritmi de gestiune a memoriei

In continuare vom detalia cativa algoritmi care furnizeaza un nou page frame atunci cand este nevoie. Notam cu “N” numarul de pagini de memorat.

Bitmap

Bitmap-ul reprezinta o matrice de N/8 bytes avand rolul unei harti pentru indicarea folosirii memoriei. Intrarile in Bitmap contin o valoare pe minim un bit ce reprezinta starea de alocare a blocului de memorie corespunzator acelei intrari.

O forma simpla este intrarea este pe un bit cu valoarea 0, daca blocul de memorie este liber si 1 daca blocul de memorie este folosit de un proces. De exemplu, la un sistem u memorie de 256 MB avem nevoie de un Bitmap de dimensiune 8 192 Bytes pentru a gestiona toate cele 65 536 de pagini.

O forma mai avansata este intrarea pe doi biti, reprezentand una din cele 3 stari posibile:

– „liber” – starea corespunzatoare blocului de memorie nealocat (nefolosit);

– „sub-alocat” – starea corespunzatoare blocului de memorie care este el insusi un set alocat de mai multe blocuri;

– „alocat” – starea corespunzand blocului de memorie folosit de un proces.

Avantajul folosirii Bitmap-ului este reprezentat de implementarea sa simpla (alocarea spatiului pentru o intrare , parcurgerea si modificarea usoara a valorii starii blocului de memorie, de ex. : “aaaaaaaabbbbbbbb” = „a8b8” ).

Dezavantajele sunt urmatoarele:

– pentru a aduce spre folosinta un numar K de blocuri alaturate de memorie, manager-ul de memorie, trebuie sa gaseasca o sectiune continua de K intrari in Bitmap. Pentru a imbunatati performantele se foloseste un pointer care sa indice ultma intrare a unui bloc alocat, astfel cunoastem faptul ca intrarile anterioare au fost deja cautate si alocate.

– La fiecare alocare a unui bloctrebuie parcurs intreg Bitmap-ul

Stack/List of pages

Folosirea stivei (o structura de tip last in – first out) reprezinta o alternativa la Bitmap.

Stiva este o regiune dinamică în cadrul unui proces. Aceasta este folosită pentru a stoca “stack frame-urile” în cazul apelurilor de funcții și variabilele locale.

Stiva este gestionată automat de compilator. La fiecare revenire din funcție stiva este golită.

Pe marea majoritate a arhitecturilor moderne stiva crește în jos și heap-ul crește în sus. Adresele (fizice) ale paginilor libere sunt puse in stiva, iar cand o pagina trebuie alocata adresa urmatoare este scoasa din stiva si folosita. Cand o pagina este eliberata, adresa sa este pusa inapoi in stiva, si tot asa. Astfel, o alocare (sau dealocare) devine o problema de incrementare (sau decrementare) a unui pointer.

Desi multe alocari nu necesita ca adresele fizice sa fie alaturate, exista si astfel de cazuri cum ar fi DMA. Daca este nevoie ca adresele sa fie alaturate, atunci managerul de memorie trebuie sa scoata adresele din mijlocul stivei, operatie ceva mai complicata. Avantajul acestei metode este ca alocarea si de-alocarea sunt rapide, in schimb verificarea starii unei pagini nu este deloc practica. In plus, daca toata memoria ar fi libera ar fi nevoie de N x 4 bytes pentru a stoca toate starile.

Scheme hibride

O alta alternativa ar fi folosirea unor scheme hibride dupa cum urmeaza:

1. Alocatorii (cei care aloca memoria) pot fi inlaintuiti astfel incat in stiva sa fie retinute doar ultimele operatii, iar sfarsitul ei sa fie legat de Bitmap, pentru o buna compacitate. Daca stivei ii lipsesc pagini, se poate scana Bitmap-ul pentru a le gasi.

2. A doua varianta ar fi ca in loc sa memoram biti reprezentand pagina, sau numere intr-o stiva, sa folosim o matrice de structuri pentru a reprezenta memoria. Aceste structuri de page frame-uri stocheaza o singura legatura catre o pagina urmatoare (de marimea unui pointer) si un block de informatie de 8-16 biti, care sa indice starea paginii.

Algoritmul include, de asemenea, un pointer catre o pagina virtuala si TCB-ul fiecarui numar de pagina. Se pastreaza pointeri pentru fiecare tip de pagina, care sa indice atat inceputul cat si sfarsitul listelor. In acest fel se poate afisa cu usurinta informatia despre continutul lor, numarul de pagini pentru fiecare tip, tipuri mixe.

De asemenea, se permite o curatare dinamica, se permite un „copy-on write” destul de usor si se pastreaza clar si concis overviewurile paginilor. Este la fel ca la un tabel de mapare invers, care listeaza si tipurile de pagina.

4.4.4 Flat List

Spatiul adreselor poate fi foarte bine gestionat prin folosirea unei liste inlantuite – „linked-list”. Fiecare regiune „libera” (nealocata) este asociata cu un descriptor ce indica marimea si adresa ei de baza (inceput). Cand un anumit spatiu trebuie alocat, lista este scanata dupa un algortim „first fit”sau „best fit” sau un alt algoritm corespunzator.

Cand memoria este alocata, intrarea respectiva este ori inlaturata (daca intreaga regiune a fost alocata), ori redimensionata (numai o poritune din regiune a fost alocata). Un dezavantaj ar fi ca in cazul „linked-lists”,trebuie parcursa complet lista pentru a gasi cea mai potrivita locatie.

O alta problema o constituie situatia in care memoria este fragmentata, iar lista de dimensiune mare. In acest caz trebuiesc identificate doua zone libere care se unesc si formeaza o zona mai mare.

Exemplu de lista inlantuita :

O soluție este de a menține o listă a segmentelor alocate și libere, unde un segment este fie un proces fie un spațiu între procese. Fiecare element al listei conține un câmp care specifică dacă este un spațiu (S) sau un proces (P), adresa de unde începe segmentul, lungimea sa și un pointer spre următorul element. Lista se poate menține sortată după câmpul de adresă ceea ce determină ca actualizarea listei să se facă foarte ușor în cazul terminării unui proces sau mutării lui pe disc.

Un proces din interiorul memoriei (nu de la capete) are doi vecini. După terminarea lui, ne putem găsi în una din următoarele situații :

Cheltuielile de regie ar fi mai mici dacă s-ar folosi o listă dublu înlănțuită.

În cazul păstrării listei ordonate după adresă, avem următorii algoritmi de alocare a memoriei pentru un proces nou creat sau adus de pe disc :

Arborele AVL

Din moment ce a devenit frecventa cautarea in lista a unei anumite adrese sau a unui spatiu de memorie de o anumita dimensiune, o solutie ar fi utilizarea unor structuri de date mai eficiente. Una din structuri care pastreaza inca parcurgerea intregii liste este „ Tree” („Arborele ”).

Fiecare nod din acest arbore va descrie o regiune de memorie si are un pointer catre subarborii (subtrees) nodurilor inferioare si catre subarborii (subtrees) nodurilor superioare.

Un arbore binar (Adelson-Velskii si Landis) este un arbore binar ordonat avand proprietatea ca pentru fiecare nod al sau inaltimea subarborelui stang difera de inaltimea subarborelui drept cu cel mult 1.

Cu alte cuvinte, intr-un arbore pentru orice nod R avand subarborii Ss si Sd de inaltime hs, respectiv hd, |hs-hd| <= 1. Inaltimea unui arbore vid este 0, iar inaltimea unui arbore nevid este 1 plus maximul dintre inaltimea subarborelui stang si inaltimea subarborelui drept.

Operatia de cautare a unui nod cu cheia data in arbori echilibrati se face cu un efort de calcul de O(log n) unitati de timp, consecinta directa a teoremei demonstrate de Adelson-Velskii si Landis ce garanteaza ca un arbore echilibrat nu va fi niciodata cu mai mult de 45% mai inalt decat omologul sau perfect echilibrat, oricare ar fi numarul sau de noduri.

Desi inserarea sau stergerea intr-un astfel de arbore necesita operatii mai complexe decat o simpla manipulare a listei, parcurgerea arborelui se face cu log2(N) iteratii, in loc de N iteratii la liste inlantuite – asa ca daca avem 100 de intrari este nevoie doar de 10 iteratii, in loc de 100 pentru a gasi un interval cautat.

Alocarea memoriei

Alocarea memoriei este realizata static de compilator sau dinamic, în timpul execuiei. Alocarea statica este realizata în segmentele de date pentru variabilele locale. În timpul executiei, variabilele se aloca pe stiva sau în heap. Alocarea pe stiva se realizeaza automat de compilator pentru variabilele locale unei functii (mai putin variabilele locale prefixate de identificatorul static).

Alocarea dinamica se realizeaza în heap. Alocarea dinamica are loc atunci când nu se stie în momentul compilarii câta memorie va fi necesara pentru o variabila, o structura, un vector. Daca se stie din momentul compilarii cat spaiu va ocupa o varibila, se recomanda alocarea ei statica, pentru a preveni erorile frecvente aparute în contextul alocarii dinamice.

Pentru a fragmenta cât mai puin spatiul de adrese al procesului, ca urmare a alocarilor si dezalocarilor unor zone de dimensiuni variate, alocatorul de memorie va organiza segmentul de date alocate dinamic sub forma de heap, de unde si numele segmentului.

Alocarea memoriei în Linux

În Linux alocarea memoriei pentru procesele utilizator se realizeaza prin intermediul functiilor de biblioteca malloc, calloc si realloc iar dezalocarea ei prin intermediul functiei free.

Aceste functii reprezinta apeluri de biblioteca si rezolva cererile de alocare si dezalocare de memorie pe cât posibil în user space. Asadar, se tin niste tabele care specifica zonele de memorie alocate în heap.

Daca exista zone libere pe heap, un apel malloc care cere o zona de memorie care poate fi încadrata într-o zona libera din heap va fi satisfacut imediat marcând în tabel zona respectiva ca fiind alocata si întorcând programului apelant un pointer spre ea.

Daca în schimb se cere o zona care nu încape în nicio zona libera din heap, malloc va încerca extinderea heap-ului prin apelul de sistem brk sau mmap.

void *calloc(size_t nmemb, size_t size);

void *malloc(size_t size);

void *realloc(void *ptr, size_t size);

void free(void *ptr);

Memoria alocata de proces este eliberata automat la terminarea procesului, însa în cazul unui proces server, de exemplu, care ruleaza foarte mult timp si nu elibereaza memoria alocata acesta va ajunge sa ocupe toata memoria disponibila în sistem cauzând astfel consecinte nefaste.

O zona de memorie nu trebuie eliberata de doua ori întrucât acest lucru va avea drept urmare coruperea tabelelor create de malloc ceea ce va duce din nou la consecinte nefaste. Întrucât functia free se întoarce imediat daca primeste ca parametru un pointer NULL, este recomandat ca dupa un apel free, pointer-ul sa fie resetat la NULL.

Câteva exemple de alocare a memorieie:

int n = atoi(argv[1]);

char *str;

/* de obicei mallocul primete argumentul de spaiu în forma size_elems * num_elems */

str = (char *) malloc((n + 1) * sizeof(char));

if (NULL == str) {

perror("malloc");

exit(EXIT_FAILURE);

}

[…]

free(str);

str = NULL;

/* crearea unui vector de siruri ce contine doar argumentele unui program */

char **argv_no_exec;

Alocarea memoriei în Linux 5

/* se aloca spatiu pentru argumente */

argv_no_exec = (char **) malloc((argc – 1) * sizeof(char*));

if (NULL == argv_no_exec) {

perror("malloc");

exit(EXIT_FAILURE);

}

/* se creeaza referinte catre argumente */

for (i = 1; i < argc; i++)

argv[in-o1]e x=e cargv[i];

[…]

free(argv_no_exec);

argv_no_exec = NULL;

Apelul realloc este folosit pentru modificarea spatiului de memorie alocat dupa un apel malloc:

int *p;

p = (int *) malloc(n * sizeof(int));

if (NULL == p) {

perror("malloc");

exit(EXIT_FAILURE);

}

[…]

p = (int *) realloc(p, (n + extra) * sizeof(int));

[…]

free(p);

p = NULL;

Apelul calloc este folosit pentru alocarea de zone de memorie al caror coninut este nul (plin de valori de zero). Spre deosebire de malloc, apelul va primi doua argumente: numarul de elemente i dimensiunea unui element.

list_t *list_v; /* list_t poate fi orice tip de date din C (mai putin void) */

list_v = (list_t *) calloc(n, sizeof(list_t));

if (NULL == list_v) {

perror("calloc");

exit(EXIT_FAILURE);

}

[…]

free(p);

p = NULL;

Stiva

Stiva este un mecanism de utilizare a memoriei. Zona de memorie utilizata in acest scop poate fi gestionata de sistemul de operare (stiva sistem) sau de catre utilizator (stiva utilizator). Aceasta zona de memorie este folosita pentru diverse manevre ca de exemplu:

·        salvare/restaurare proces intrerupt;

·        pasare de parametrii in diverse situatii.

Stiva reprezinta un depozit de diverse elemente memorate, la care accesul este bazat de disciplina LIFO (Last In First Out). Figurativ stiva poate fi asimilata unei tevi inchise la un capat, in care se introduc si extrag elemente, de diametrul egal cu cel al tevi, pe la capatul ramas liber.

Pasarea parametrilor catre procedure si functii

Dupa cum este cunoscut, acestea reprezinta zone de cod (subprograme), care identifica printr-un nume o secventa de instructiuni specializate in a executa anumite actiuni, cu grad mare de repetabilitate in cadru unui program de mari dimensiuni. Pentru a fi executata secventa de instructiuni specifica unui subprogam acesta trebuie apelat prin identificator. Apelul inseamna un salt la secventa de instructiuni, sintaxa de apel permitand pasarea unor parametrii catre zona de cod reprezentand subprogramul.

Exista doua moduri de pasare a parametrilor:

·        prin valoare – cand pe stiva este transmisa valoarea efectiva a parametrului, acesta fiind echivalent cu o variabila interna a subprogramului;

·        prin variabila (referinta) – cand pe stiva este transmisa adresa la care se gaseste valoarea efectiva a parametrului, acesta fiind echivalent cu o variabila externa a subprogramului;

Alt element important la pasarea parametrilor este reprezentat de ordinea de asezare a parametrilor in stiva, existand in acest sens doua conventii:

·        parametrii sunt asezati in stiva de la stanga la dreapta (conventia Pascal);

·        parametrii sunt asezati in stiva de la dreapta la stanga (conventia C);

Primul element care se aseaza in stiva este reprezentat de adresa de intoarcere. Urmatoarele elemenete asezate in stiva sunt reprezentate de parametrii de tip valoare sau referinta.

Dupa executarea zonei de cod corespunzatoare subprogramului apelat, parametrii pasati prin stiva sunt eliberati (stersi), pentru a se putea ajunge la adresa de intoarcere in programul apelant.

Avand in vedere cele prezentate pana acum, se poate concluziona ca rezultatul prelucrarilor unui subprogram, nu poate fi facut cunoscut programului apelant prin intermediul parametrilor de tip valoare, ci doar prin cel al parametrilor de tip referinta.

Alocarea memoriei în Windows

În Windows un proces poate sa creeze mai multe obiecte Heap pe lânga Heap-ul cu care este creat procesul. Acest lucru este foarte util în momentul în care o aplicatie aloca si dezaloca foarte multe zone de memorie cu câteva dimensiuni fixe. Aplicaia poate sa creeze câte un Heap pentru fiecare dimensiune si, în cadrul fiecarui Heap, sa aloce zone de aceeasi dimensiune reducând astfel la maxim fragmentarea heapului.

Pentru crearea, respectiv distrugerea, unui Heap se vor folosi functiile HeapCreate si HeapDestroy:

HANDLE HeapCreate(

DWORD flOptions,

SIZE_T dwInitialSize,

SIZE_T dwMaximumSize

);

BOOL HeapDestroy(

HANDLE hHeap

);

Pentru a obtine un descriptor al heapului cu care a fost creat procesul (în cazul în care nu dorim crearea altor heapuri) se va apela functia GetProcessHeap. Pentru a obtine descriptorii tuturor heapurilor procesului se va apela GetProcessHeaps.

Exista, de asemenea functii care enumera toate blocurile alocate într-un heap, valideaza unul sau toate blocurile sau întorc dimensiunea unui bloc pe baza descriptorului de heap si a adresei blocului: HeapWalk, HeapSize, HeapValidate.

Pentru alocarea, dezalocarea, redimensionarea unui bloc de memorie din Heap, Windows pune la dispozitia programatorului functiile HeapAlloc, HeapFree, respectiv HeapReAlloc, cu signaturile de mai jos:

LPVOID HeapAlloc(

HANDLE hHeap,

DWORD dwFlags,

SIZE_T dwBytes

);

BOOL HeapFree(

HANDLE hHeap,

DWORD dwFlags,

LPVOID lpMem

);

LPVOID HeapReAlloc(

HANDLE hHeap,

DWORD dwFlags,

LPVOID lpMem,

SIZE_T dwBytes

);

/* alocarea unui vector de intregi */

HANDLE processHeap;

DWORD *data;

processHeap = GetProcessHeap();

if (NULL == processHeap) {

fprintf(stderr, "GetProcessHeap failed with error %ud.\n", GetLastError());

Exi(t-P1r)o;cess

Alocarea memoriei în Windows 7

}

data = HeapAlloc(processHeap, HEAP_ZERO_MEMORY, count * sizeof(DWORD));

if (NULL == data) {

fprintf(stderr, "HeapAlloc failed with error %ud.\n", GetLastError());

Exi(t-P1r)o;cess

}

[…]

if (HeapFree(processHeap, 0, data) == FALSE) {

fprintf(stderr, "HeapFree failed with error %ud.\n", GetLastError());

Exi(t-P1r)o;cess

}

/* alocarea unei matrice */

HANDLE processHeap;

DWORD **mat;

INT m, n;

INT i;

processHeap = GetProcessHeap();

if (NULL == processHeap) {

fprintf(stderr, "GetProcessHeap failed with error %ud.\n", GetLastError());

Exi(t-P1r)o;cess

}

mat = HeapAlloc(processHeap, 0, m * sizeof(*mat));

if (NULL == mat) {

fprintf(stderr, "HeapAlloc failed with error %ud.\n", GetLastError());

Exi(t-P1r)o;cess

}

for (i = 0; i < n; i++) {

[ i ] = mHaetapAlloc(processHeap, 0, n * sizeof(**mat));

if (NULL == mat) {

fprintf(stderr, "HeapAlloc failed with error %ud.\n", GetLastError());

goto freeMem;

}

}

[…]

freeMem:

for (j = 0; j < i; j++)

(HperaopcFerseseHeap, 0, mat[j]);

HeapFree(processHeap, 0, mat);

Pe sistemele Windows se pot folosi si funciile bibliotecii standard C pentru gestiunea memoriei: malloc, realloc, calloc, free, dar apelurile de sistem specifice Windows ofera funcionalitati suplimentare si nu implica legarea bibliotecii standard C în executabil.

Dezalocarea memoriei

Pentru dezalocarea memoriei, se folosesc functiile free, respectiv HeapFree. Functiile primesc ca argument un pointer la un spatiu de memorie alocat anterior cu o functie de alocare. Daca se omite dezalocarea unei zone de memorie, aceasta va ramâne alocata pe întreaga durata de rulare a procesului. Ori de câte ori nu mai este nevoie de o zona de memorie, aceasta trebuie dezalocata pentru eficienta utilizarii spaiului de memorie.

Nu trebuie neaparat realizata dezalocarea diverselor zone înainte de un apel exit sau ExitProcess sau înainte de încheierea programului pentru ca acestea sunt automat eliberate de sistemul de operare.

Similar Posts