This is an old revision of the document!
Virtual Memory Allocator
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 cereri de prelungire a deadline-urilor temelor, au găsit și concepte inedite, precum memoria virtuală.
Bineînțeles, studenții sunt niște ființe deloc leneșe, așa că s-au apucat deja de prima temă, un alocator de memorie virtuală, 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ștrii de suferință această funcționalitate a sistemului de operare, folosind exclusiv liste dublu înlănțuite. Pe parcurs, nu veți avea nevoie de cunoștințe de Sisteme de Operare, dar, dacă într-adevăr, vă pasionează subiectul, vizitați pagina oficială a cursului.
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, numit arenă. De asemenea, alocatorul de memorie se ocupă și cu eliberarea zonelor rezervate, apelul de bibliotecă aferent fiind free()
.
Alocatorul de memorie virtuală pe care îl veți implementa va avea următoarele funcționalități (sau API):
un buffer ascuns, pe care operațiile directe sunt interzise, numit kernel buffer sau arena; acesta este un vector generic, ce reține informația alocată de voi
o listă dublu înlănțuită ce reține zonele alocate din arenă; aceasta memorează și zonele alocate adiacent, reprezentate, la rândul lor, ca liste dublu înlănțuite
Astfel, lista dublu înlănțuită principală reține block-urile alocate, pe când listele dublu înlănțuite din aceasta rețin miniblock-urile consecutive. O structură posibilă pentru block ar arăta în felul următor:
{
uint64_t start_address; // adresa de început a zonei, un indice din arenă
size_t size; // dimensiunea totală a zonei, suma size-urilor miniblock-urilor
void* miniblock_list; // lista de miniblock-uri adiacente
} block_t;
O structură posibilă pentru miniblock ar arăta în felul următor:
{
uint64_t start_address; // adresa de început a zonei, un indice din arenă
size_t size; // size-ul miniblock-ului
uint8_t perm; // permisiunile asociate zonei, by default RW-
void* rw_buffer; // buffer-ul de date, folosit pentru opearțiile de read() și write()
} miniblock_t;
Comenzi posibile (40p)
Input-ul este oferit de la stdin, iar output-ul la stdout, respectând formatul următor:
ALLOC_ARENA <dimensiune_aren>
DEALLOC_ARENA
Se eliberează arena alocată la începutul programului. Cum alocarea arenei a fost făcută virtual, această comandă rezultă în eliberarea tuturor resurselor folosite, precum lista de block-uri și listele de miniblock-uri asociate acestora.
ALLOC_BLOCK <adresă_din_arenă> <dimensiune_block>
Se marchează ca fiind rezervată o zonă ce începe la adresa <adresă_din_arenă> în kernel buffer cu dimensiunea de <dimensiune_block>
Dacă nicio adresă din zona de memorie [adresă_din_arenă, adresă_din_arenă + dimensiune) nu a mai fost alocată anterior și nu există alocate zonele de memorie [x, adresă_din_arenă - 1) și [adresă_din_arenă + dimensiune + 1, y], unde x < adresă_din_arenă - 1 și y > adresă_din_arenă + dimensiune + 1, atunci se inserează un nou block în lista de zone alocate.
Dacă nicio adresă din zona de memorie [adresă_din_arenă, adresă_din_arenă + dimensiune) nu a mai fost alocată anterior și există alocate zonele de memorie [x, adresă_din_arenă - 1) sau [adresă_din_arenă + dimensiune + 1, y], unde x < adresă_din_arenă - 1 și y > adresă_din_arenă + dimensiune + 1, atunci se șterge block-ul/block-urile adiacente din lista de zone alocate și se adaugă la lista internă de miniblock-uri a noii zone contigue de memorie. Cu alte cuvinte, zonele adiacente din memorie vor fi mereu în același block.
FREE_BLOCK <adresă_din_arenă>
Se eliberează un miniblock.
Dacă se eliberează unicul miniblock din cadrul unui block, atunci block-ul este la rândul său eliberat.
Dacă se eliberează un miniblock de la începutul/sfârșitul acestuia, atunci structura block-ului nu se modifică.
Dacă se eliberează un miniblock din mijlocul block-ului, atunci acesta va fi împărțit în două block-uri distincte.
READ <adresă_din_arenă> <dimensiune>
Se afișează <dimensiune> bytes începând cu adresa <adresă_din_arenă>, iar la final \n
.
Dacă block-ul nu conține <dimensiune> bytes începând cu adresa <adresă_din_arenă>, se va afișa “Warning: size was bigger than the block size. Reading <size> characters.\n” și se vor afișa datele disponibile.
WRITE <adresă_din_arenă> <dimensiune> <date>
Se scriu <dimensiune> bytes din <date> la adresa <adresă_din_arenă>.
Dacă <date> nu conține <dimensiune> bytes pe același rând, se va citi în continuare, până la atingerea dimensiunii stabilite.
Dacă block-ul nu conține <dimensiune> bytes începând cu adresa <adresă_din_arenă>, se va afișa “Warning: size was bigger than the block size. Writing <size> characters.\n”.
PMAP
Tradițional, apelul
pmap(), este folosit pentru a vizualiza zonele de memorie utilizate de un proces. Printre acestea se numără
.text
,
.bss
,
.data
,
.rodata
, etc, însă voi veți avea de implementat o funcționalitate mai facilă.
Se afișează informații despre block-urile și miniblock-urile existente.
Formatul permisiunilor este RWX. Dacă vreuna dintre aceste permisiuni lipsește, ea va fi înlocuită cu -
.
Dacă nu ați implementat bonusul, permisiunile miniblock-urilor vor fi mereu RW-!
Formatul de afișare pentru PMAP este următorul:
Total memory: x
Free memory: y
Number of allocated blocks: z
Number of allocated miniblocks: q
Block 1 begin
Zone: address1 - address2
Miniblock 1:\t\taddress1\t\t-\t\taddress1a\t\t| permissions
Miniblock 2:\t\taddress1a\t\t-\t\taddress1b\t\t| permissions
.
.
.
Miniblock n:\t\taddress1x\t\t-\t\taddress2\t\t| permissions
Block 1 end
Block 2 begin
Zone: address3 - address4
Miniblock 1:\t\taddress3\t\t-\t\taddress3a\t\t| permissions
Miniblock 2:\t\taddress3a\t\t-\t\taddress3b\t\t| permissions
.
.
.
Miniblock m:\t\taddress3y\t\t-\t\taddress4\t\t| permissions
Block 2 end
.
.
.
Block z begin
.
.
.
Block z end
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:
INVALID_ALLOC_BLOCK
Dacă adresa de început a blocului ce se dorește a fi alocat depășește dimensiunea arenei, se va afișa “The allocated address is outside the size of arena\n”.
Dacă adresa de final a blocului ce se dorește a fi alocat depășește dimensiunea arenei, se va afișa “The end address is past the size of the arena\n”.
Dacă există cel puțin o adresă din zona de memorie [adresă_din_arenă, adresă_din_arenă + dimensiune) care a fost alocată anterior, se va afișa “This zone was already allocated.\n”.
INVALID_ADDRESS_FREE
INVALID_ADDRESS_READ
INVALID_ADDRESS_WRITE
INVALID_COMMAND
Bonus (20p)
Exemplu
Fie input-ul:
ALLOC_ARENA 65536
ALLOC_BLOCK 4096 10
ALLOC_BLOCK 12288 10
ALLOC_BLOCK 12308 10
PMAP
ALLOC_BLOCK 12298 10
PMAP
WRITE 4096 26 Observ sisteme de operare
READ 4096 14
FREE_BLOCK 12298
MPROTECT 12308 PROT_NONE
WRITE 12308 24 Sper să nu iau SEGFAULT
PMAP
DEALLOC_ARENA
Output-ul corespunzător acestei secvențe de operații este:
Total memory: 0x10000 bytes
Free memory: 0xFFE2 bytes
Number of allocated blocks: 3
Number of allocated miniblocks: 3
Block 1 begin
Zone: 0x1000 - 0x100A
Miniblock 1: 0x1000 - 0x100A | RW-
Block 1 end
Block 2 begin
Zone: 0x3000 - 0x300A
Miniblock 1: 0x3000 - 0x300A | RW-
Block 2 end
Block 3 begin
Zone: 0x3014 - 0x301E
Miniblock 1: 0x3014 - 0x301E | RW-
Block 3 end
Total memory: 0x10000 bytes
Free memory: 0xFFD8 bytes
Number of allocated blocks: 2
Number of allocated miniblocks: 4
Block 1 begin
Zone: 0x1000 - 0x100A
Miniblock 1: 0x1000 - 0x100A | RW-
Block 1 end
Block 2 begin
Zone: 0x3000 - 0x301E
Miniblock 1: 0x3000 - 0x300A | RW-
Miniblock 2: 0x300A - 0x3014 | RW-
Miniblock 3: 0x3014 - 0x301E | RW-
Block 2 end
Warning: size was bigger than the block size. Writing 10 characters.
Warning: size was bigger than the block size. Reading 10 characters.
Observ sis
Invalid permissions for write.
Total memory: 0x10000 bytes
Free memory: 0xFFE2 bytes
Number of allocated blocks: 3
Number of allocated miniblocks: 3
Block 1 begin
Zone: 0x1000 - 0x100A
Miniblock 1: 0x1000 - 0x100A | RW-
Block 1 end
Block 2 begin
Zone: 0x3000 - 0x300A
Miniblock 1: 0x3000 - 0x300A | RW-
Block 2 end
Block 3 begin
Zone: 0x3014 - 0x301E
Miniblock 1: 0x3014 - 0x301E | ---
Block 3 end
Acest exemplu corespunde figurii de mai sus. Asigurați-vă că urmăriți exemplul și figura în paralel, pentru o mai bună înțelegere a comenzilor. Astfel:
Primul PMAP afișează 3 block-uri cu câte un miniblock fiecare. Block-urile 2 și 3 nu sunt încă unite deoarece există un spațiu de 10 bytes nealocat între zonele celor două.
Al doilea PMAP este aplicat imediat după alocarea block-ului care ar trebui să unească cele 2 block-uri menționate anterior într-unul singur. Acest lucru se întâmplă și rezultă 2 block-uri, primul cu un singur miniblock, iar al doilea cu trei miniblock-uri.
Se încearcă scrierea a 25 de caractere, dar cum dimensiunea miniblock-ului în care se realizează WRITE este de doar 10, se afișează mesajul de warning și se rețin în buffer-ul miniblock-ului primele 10 caractere.
Se încearcă citirea a 14 caractere din cadrul miniblock-ului în care s-a scris la comanada precedentă, dar cum acesta conține 10 caractere, se afișează mesajul de warning și doar cei 10 bytes reținuți.
Prin MPROTECT, permisiunile ultimului miniblock sunt schimbate în PROT_NONE, așadar operația de WRITE următoare eșuează și se afișează mesajul de eroare.
Al treilea PMAP afișează rezultatul operației de FREE_BLOCK, care a despărțit, din nou, block-ul cu trei miniblock-uri în două. De asemenea, permisiunile ultimului miniblock existent au devenit —, semnificând că pe zona respectivă nu este posibilă nicio operație.
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.
Orice tentativă de alocare a unui vector liniar, contiguu și de simulare a operațiilor aplicate arenei pe acesta este interzisă. Arena există doar la nivel conceptual, task-urile temei aplicându-se pe lista dublu înlănțuită de block-uri, respectiv miniblock-uri.
Checker
Scheletul poate fi găsit aici.
Temele vor fi trimise pe vmchecker.
Atenție! Temele trebuie trimise în secțiunea Structuri de Date (CA).
Arhiva trebuie să conțină:
sursele .c și .h
fișier Makefile cu două reguli:
regula build: în urma căreia se generează un executabil numit vma
regula run_vma: în urma căreia va rula executabilul vma
regula clean care şterge executabilul si fișierele obiect
Compilarea trebuie sa se realizeze cu flagurile -Wall -Wextra -std=c99
fișier README care să conțină detalii despre implementarea temei