Segregated Free Lists
Actualizări
Eliminare mesaj de eroare INVALID_COMMAND: 22.03.2024
Publicare checker: 22.03.2024
Update checker: 26.03.2024
Mențiune privind obligativitatea eliminării blocurilor la alocare din vectorul de liste: 27.03.2024
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.
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):
un vector de liste dublu înlănțuite, numit segregated free lists; fiecare intrare din acest vector va indica către o listă dublu înlănțuită ce reține zonele de memorie liberă de aceeași dimensiune
o altă structură de date la alegerea voastră care va reține zonele de memorie ocupată, împreună cu conținutul acestora.
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.
READ <adresă> <nr_bytes>
Afișează în format
ASCII size octeți începând de la adresa <adresă>.
Dacă începând cu adresa <adresă> nu a fost alocat niciun bloc, se afișează mesajul de eroare “Segmentation fault (core dumped)\n” și se face un dump al memoriei, după regulile de la funcția DUMP_MEMORY.
Dacă adresa <adresă> a fost alocată, dar există un octet din intervalul [adresă, adresă + nr_bytes) care nu a fost alocat prealabil, se afișează mesajul de eroare “Segmentation fault (core dumped)\n” și se face un dump al memoriei, după regulile de la funcția DUMP_MEMORY.
Dacă toți octeții ce trebuie afișați au fost alocați deja, se afișează conținutul cerut, indiferent dacă acesta face parte din mai multe alocări consecutive.
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.
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ă.
INVALID_FREE
Dacă se dorește eliberarea unei zone de memorie începând cu o adresă care nu a fost alocată deja se afișează mesajul de eroare “Invalid free\n”.
Dacă se dorește eliberarea unei zone de memorie începând cu o adresă care nu reprezintă început de bloc (adică nu a rezultat în urma comenzii MALLOC) se afișează mesajul de eroare “Invalid free\n”.
SEGMENTATION_FAULT
Dacă se dorește citirea de la o adresă care nu a fost alocată, se afișează mesajul de eroare “Segmentation fault (core dumped)\n” și se face un dump al memoriei, după regulile de la funcția DUMP_MEMORY.
Dacă se dorește citirea de la o adresă care a fost alocată, dar care pe parcursul citirii conține zone nealocate, se afișează mesajul de eroare “Segmentation fault (core dumped)\n” și se face un dump al memoriei, după regulile de la funcția DUMP_MEMORY.
Dacă se dorește scrierea începând cu o adresă care nu a fost alocată, se afișează mesajul de eroare “Segmentation fault (core dumped)\n” și se face un dump al memoriei, după regulile de la funcția DUMP_MEMORY.
Dacă se dorește scrierea începând cu o adresă care a fost alocată, dar care pe parcursul scrierii conține zone nealocate, se afișează mesajul de eroare “Segmentation fault (core dumped)\n” și se face un dump al memoriei, după regulile de la funcția DUMP_MEMORY.
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
Se garantează faptul că operația de READ va citi din zone scrise anterior cu WRITE. Cu alte cuvinte o operație validă de READ va avea mereu loc după o operație validă de WRITE.
Indexarea se face de la 0 pentru orice tip de listă.
Alocarea memoriei se va realiza dinamic. Alocarea statică a bufferelor a căror dimensiune nu se cunoaște la compile-time, atrage dupa ea depunctări.
Se cere ca structura de date folosită să fie generică. Pentru implementare lipsită de genericitate, se vor pierde 20p din totalul temei.
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
)
Nu este indicată utilizarea variabilelor globale. Utilizarea acestora aduce pierderea a 20p din totalul temei.
Eliberarea memoriei se va verifica folosind utilitarul Valgrind. O temă ce conține memory leaks va atrage după sine punctaj de 0p pe testul respectiv.
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:
sursele .c si .h
fișier Makefile cu trei reguli:
regula build: în urma căreia se genereaza un executabil numit sfl
regula run_sfl: în urma căreia va rula executabilul sfl
regula clean care șterge executabilul și fișierele obiect
Compilarea trebuie să se realizeze cu flagurile -Wall -Wextra -std=c99
fișier README care să conțină detalii despre implementarea temei.