Table of Contents

Segregated Free Lists

Responsabili

Actualizări

Obiective

Introducere

Studenții de anul întâi au aflat de la colegii mai mari de o materie interesantă pe care urmează să o aibă în anul 2, faimoasa Observarea Sistemelor de Operare (cu acronimul OSO). Fiind curioși, au început deja investigația, și pe lângă meme-uri, debate-uri și reminder-uri ale deadline-urilor temelor (“Rețineți termenul. Este fix și strict. Nu mai puteți submite teme după acest termen. Notați-l bine și țineți-vă de el.”), au găsit și concepte inedite, precum alocarea de memorie în cadrul bibliotecilor.

Bineînțeles, studenții sunt niște ființe deloc leneșe, așa că s-au apucat deja de a doua temă (prima era prea simplă), un alocator de memorie, pe care vi-l arată și vouă, extaziați. Scopul vostru este să le mai micșorați avântul și să implementați împreună cu frații voștri de suferință această funcționalitate a unei biblioteci, folosind Segregated Free Lists. Pe parcurs, nu veți avea nevoie de cunoștințe de Sisteme de Operare, dar, dacă vă pasionează subiectul, vizitați pagina oficială a cursului.

Definiție

Segregated Free Lists reprezintă o structură de date formată dintr-un vector, care, pentru fiecare intrare, reține adresa de început a unei liste duble înlănțuite cu blocuri de memorie liberă de aceeași dimensiune. Inițial, dimensiunile blocurilor vor fi puteri ale lui 2, pentru ca în urma alocărilor, să fie fragmentate sau reconstituite, de la caz la caz.

Așa cum îi spune și numele, această structură de date reține doar blocurile nealocate, pentru cele alocate fiind folosită o altă structură de date.

 Img 1

Cerință

Un alocator de memorie are rolul de a rezerva memorie, la nivel de bibliotecă, tradițional prin apeluri precum malloc() sau calloc(). Acestea marchează ca fiind folosite anumite zone de memorie dintr-un pool de bytes prealocat, care, în cazul acestei teme, este organizat sub forma de segregated free lists. De asemenea, alocatorul de memorie se ocupă și cu eliberarea zonelor rezervate, apelul de bibliotecă aferent fiind free().

Alocatorul de memorie pe care îl veți implementa va avea următoarele funcționalități (sau API):

Comenzi posibile (40p)

Input-ul este oferit de la stdin, iar output-ul la stdout, respectând formatul următor:

<adresă_start_heap> reprezintă un număr pozitiv, nenul.

Garantăm faptul că numărul de bytes corespunzător oricărei liste este mai mare sau egal decât dimensiunea asignată acesteia. Cu alte cuvinte, inițial, există cel puțin un bloc pentru fiecare dimensiune de listă în parte.

Fragmentarea reprezintă procesul prin care un bloc se împarte în alte două blocuri mai mici, dintre care primul va fi folosit pentru o alocare, în timp ce al doilea va rămâne liber, fiind plasat în lista corespunzătoare dimensinii sale.

Reconstituirea reprezintă procesul prin care un bloc care a fost fragmentat la alocare își recapătă dimensiunea inițială, prin alipirea tuturor blocurilor care l-au compus. Acest fel de compunere are loc doar atunci când <tip_reconstituire> este egal cu 1, iar fragmentele blocului sunt măcar în parte din nou libere.

Garantăm faptul că fiecare operație de READ are loc pe o zonă de memorie în care s-a efectuat o operație de WRITE prealabil.

+++++DUMP+++++
Total memory: <dimensiune_heap> bytes
Total allocated memory: <nr_bytes_alocați> bytes
Total free memory: <nr_bytes_liberi> bytes
Number of free blocks: <nr_blocuri_libere>
Number of allocated blocks: <nr_blocuri_alocate>
Number of malloc calls: <nr_apeluri_malloc>
Number of fragmentations: <nr_fragmentări>
Number of free calls: <nr_apeluri_free>
Blocks with <x1> bytes - <nr_blocuri_cu_dimensiune_x1> free block(s) : 0x<adresă_start_bloc_1> <0xadresă_start_bloc_2> ... <adresă_start_bloc_n>
Blocks with <x2> bytes - <nr_blocuri_cu_dimensiune_x2> free block(s) : 0x<adresă_start_bloc_1> <0xadresă_start_bloc_2> ... <adresă_start_bloc_n>
.
.
.
Blocks with <xn> bytes - <nr_blocuri_cu_dimensiune_xn> free block(s) : 0x<adresă_start_bloc_1> <0xadresă_start_bloc_2> ... <adresă_start_bloc_n>
Allocated blocks : (0x<adresă_start_bloc_1> - <dimensiune_bloc_1>) (0x<adresă_start_bloc_2> - <dimensiune_bloc_2>) ... (0x<adresă_start_bloc_n> - <dimensiune_n>)
-----DUMP-----

Blocurile trebuie afișate în ordinea crescătoare a dimensiunilor, iar în cadrul listelor de aceeași dimensiune, în ordinea crescătoare a adreselor.

  • Toate adresele sunt citite și afișate în format hexa, cu litere mici.
  • Dimensiunea unei linii de input este de maxim 600 de caractere.

Exemplu

Exemplu

Fie următorul input:

INIT_HEAP 0x1 4 256 0
MALLOC 10
MALLOC 16
MALLOC 2000
WRITE 0x111 "GIGEL" 5
DUMP_MEMORY
READ 0x113 2
FREE 0x111
FREE 0x111
DESTROY_HEAP

Output-ul corespunzător acestui input este:

Out of memory
+++++DUMP+++++
Total memory: 1024 bytes
Total allocated memory: 26 bytes
Total free memory: 998 bytes
Free blocks: 59
Number of allocated blocks: 2
Number of malloc calls: 2
Number of fragmentations: 1
Number of free calls: 0
Blocks with 6 bytes - 1 free block(s) : 0x10b
Blocks with 8 bytes - 32 free block(s) : 0x1 0x9 0x11 0x19 0x21 0x29 0x31 0x39 0x41 0x49 0x51 0x59 0x61 0x69 0x71 0x79 0x81 0x89 0x91 0x99 0xa1 0xa9 0xb1 0xb9 0xc1 0xc9 0xd1 0xd9 0xe1 0xe9 0xf1 0xf9
Blocks with 16 bytes - 14 free block(s) : 0x121 0x131 0x141 0x151 0x161 0x171 0x181 0x191 0x1a1 0x1b1 0x1c1 0x1d1 0x1e1 0x1f1
Blocks with 32 bytes - 8 free block(s) : 0x201 0x221 0x241 0x261 0x281 0x2a1 0x2c1 0x2e1
Blocks with 64 bytes - 4 free block(s) : 0x301 0x341 0x381 0x3c1
Allocated blocks : (0x101 - 10) (0x111 - 16)
-----DUMP-----
GE
Invalid free

Explicația este:

  • Inițial, heap-ul conține blocuri de 4 mărimi diferite (8, 16, 32, 64 biți), fiecare listă de blocuri având o lungime totală de 256 biți.
  • În total, avem un spațiu de memorie de 1024 de bytes, împărțit astfel:
    • Pool-ul de blocuri de dimensiune 8 conține adrese de la 0x1 la 0x100. (256 / 8 = 32 blocuri)
    • Pool-ul de blocuri de dimensiune 16 conține adrese de la 0x101 la 0x200. (256 / 16 = 16 blocuri)
    • Pool-ul de blocuri de dimensiune 32 conține adrese de la 0x201 la 0x300. (256 / 32 = 8 blocuri)
    • Pool-ul de blocuri de dimensiune 64 conține adrese de la 0x301 la 0x400. (256 / 64 = 4 blocuri).
  • Se alocă un spațiu de 10 bytes, acesta va fi în pool-ul de 16 bytes. Adresa returnată va fi 0x101. Cum dimensiunea cerută pentru alocare este mai mică decât blocul primit la alocare, acesta se va fragmenta. Vom aloca 10 adrese începând de la adresa 0x101 și restul adreselor (de la 0x10B la la 0x110) vor forma un block liber de 6 octeți.
  • Se alocă un spațiu de 16 bytes, acesta va fi în pool-ul de 16 bytes. Adresa returnată va fi prima disponibilă din pool, adică 0x111. În acest caz nu avem nevoie de fragmentare.
  • Se încearcă alocarea unui spațiu de 2000 bytes, dar acest lucru nu este posibil, deoarece 2000 depășește dimensiunea celui mai mare bloc posibil. Se va afișa mesajul de eroare “Out of memory”.
  • Se scrie string-ul GIGEL la adresa 0x111. La adresa 0x111 va fi litera G, la 0x112 I, la 0x113 G șamd.
  • Se afișează DUMP-ul de memorie, conform specificațiilor menționate în enunț.
  • Se citesc 2 bytes de la adresa 0x113, GE.
  • Se eliberează memoria de la adresa 0x111.
  • Se încearcă eliberarea memoriei de la adresa 0x111, care nu este alocată. Se va afișa “Invalid free”.

Tratarea erorilor (40p)

La primirea comenzilor pot apărea erori de input. Pe acestea, voi trebuie să le tratați corespunzător, prin afișarea unui mesaj sugestiv:

E posibil ca, însumând blocuri de dimensiune mai mică, să existe suficientă memorie pentru apelul de MALLOC, totuși, dacă aceasta nu se găsește în totalitate în același bloc, alocarea eșuează.

Bonus (20p)

În cadrul bonusului, veți trata cazul în care parametrul <tip_reconstituire> din cadrul INIT_HEAP este egal cu 1, cu alte cuvinte reconstituirea la FREE a blocurilor care au provenit din altul mai mare, în urma unei fragmentări. Reconstituirea are loc și pentru fragmentări intermediare: un bloc uriaș a fost fragmentat, blocul fragment a fost fragmentat la rândul său și așa mai departe. În acest caz, reconstituirea ar trebui să fie recursivă.

Un exemplu sumar ar fi

Fie situația în care avem un singur bloc liber, de dimensiune 16 bytes,
care începe de la adresa 0x1.

MALLOC 6
---> Se fragmentează blocul de 16 bytes într-o parte de 6 bytes
     ce va fi alocată și o parte de 10 bytes ce rămâne liberă
     Se întoarce adresa 0x1.
MALLOC 5
---> Se fragmentează blocul de 10 bytes într-o parte de 5 bytes
     ce va fi alocată și o parte de 5 bytes ce rămâne liberă
     Se întoarce adresa 0x7.
FREE 0x7
---> Se alipește fragmentul de 5 bytes cu fragmentul liber de
     5 bytes.
     Rezultă un bloc de dimensiune 10 bytes.
FREE 0x1
---> Se alipește fragmentul de 6 bytes cu fragmentul liber de
     10 bytes.
     Rezultă un bloc de dimensiune 16 bytes.

Un alt exemplu ar fi

Fie situația în care avem un singur bloc liber, de dimensiune 16 bytes,
care începe de la adresa 0x1.

MALLOC 6
---> Se fragmentează blocul de 16 bytes într-o parte de 6 bytes
     ce va fi alocată și o parte de 10 bytes ce rămâne liberă
     Se întoarce adresa 0x1.
MALLOC 5
---> Se fragmentează blocul de 10 bytes într-o parte de 5 bytes
     ce va fi alocată și o parte de 5 bytes ce rămâne liberă
     Se întoarce adresa 0x7.
FREE 0x1
---> Se creează un bloc nou de dimensiune 6 bytes.
     Există 2 liste cu câte un bloc fiecare, unul de 5 bytes și unul de 6 bytes.
FREE 0x7
---> Se alipește fragmentul de 5 bytes cu fragmentele de 5 și 6 bytes.
     Există o singură listă, cu un singur bloc, de dimensiune 16

README (10p)

Coding style (10p)

Mențiuni

  • O structură de date generică are rolul de a nu duplica (pe cât posibil) cod permițând adăugarea în cadrul ei a oricărui tip de date. (hint: void *)
  • Exemplu: Vedeți în cadrul scheletului de lab unde a trebuit să implementați exact această situație. (hint: data_size)

  • Este obligatorie construirea unei implementări care folosește conceptul de vector de liste dublu înlănțuite.
  • Este obligatoriu să realizați eliminarea blocurilor din vectorul de liste atunci când acestea sunt alocate.
  • Nerespectarea acestor constrângeri va conduce la notarea temei cu 0 puncte.

Checker

Arhivă checker: sfl_checker.zip

Pentru a utiliza checker-ul:

  • Dezarhivați fișierul .zip în folderul in care se află implementarea voastră.
  • Asigurați-vă că Makefile-ul vostru conține regulile build, run_sfl și clean.
  • Rulați folosind comanda ”./check”.

Temele vor fi trimise pe Moodle.

Arhiva trebuie sa contina: