Differences

This shows you the differences between two versions of the page.

Link to this comparison view

sde:laboratoare:07_ro_python [2020/03/30 15:29]
ioana_maria.culic
— (current)
Line 1: Line 1:
-====== Laborator 05 - Gestiunea memoriei ====== 
-===== Gestiunea memoriei ===== 
- 
-Subsistemul de gestiune a memoriei din cadrul unui sistem de operare este folosit de toate celelalte subsisteme: planificator,​ I/O, sistemul de fișiere, gestiunea proceselor, networking. Memoria este o resursă importantă,​ de aceea sunt necesari algoritmi eficienți de utilizare și gestiune a acesteia. 
- 
-Rolul subsistemului de gestiune a memoriei este de: 
-  * a ține evidența zonelor de memorie fizică (ocupate sau libere) 
-  * a oferi proceselor sau celorlalte subsisteme acces la memorie 
-  * a mapa paginile de memorie virtuală ale unui proces (pages) peste paginile fizice (frames). 
- 
-Nucleul sistemului de operare oferă un set de interfețe (apeluri de sistem) care permit alocarea/​dealocarea de memorie, maparea unor regiuni de memorie virtuală peste fișiere, partajarea zonelor de memorie. 
- 
-Din păcate, nivelul limitat de înțelegere a acestor interfețe și a acțiunilor ce se petrec în spate conduc la o serie de probleme foarte des întâlnite în aplicațiile software: memory leak-uri, accese nevalide, suprascrieri,​ buffer overflow, corupere de zone de memorie. 
- 
-Este, în consecință,​ fundamentală cunoașterea contextului în care acționează subsistemul de gestiune a memoriei și înțelegerea interfeței puse la dispoziție programatorului de către sistemul de operare. 
- 
-===== Spațiul de adresă al unui proces ===== 
- 
-{{ so:​laboratoare-2013:​process_address_space.jpg|Spațiul de adresă al unui proces}} 
- 
-Spațiul de adresă al unui proces, sau, mai bine spus, spațiul virtual de adresă al unui proces reprezintă zona de memorie virtuală utilizabilă de un proces. Fiecare proces are un spațiu de adresă propriu. Chiar în situațiile în care două procese partajează o zonă de memorie, spațiul virtual este distinct, dar se mapează peste aceeași zonă de memorie fizică. 
- 
-În figura alăturată este prezentat un spațiu de adresă tipic pentru un proces. În sistemele de operare moderne, în spațiul virtual al fiecărui proces se mapează memoria nucleului, aceasta poate fi mapată fie la început, fie la sfârșitul spațiului de adresă. În continuare, ne vom referi numai la spațiul de adresă din user-space pentru un proces. 
- 
-Cele 4 zone importante din spațiul de adresă al unui proces sunt zona de date, zona de cod, stiva și heap-ul. După cum se observă și din figură, stiva și heap-ul sunt zonele care pot crește. De fapt, aceste două zone sunt dinamice și au sens doar în contextul unui proces. De partea cealaltă, informațiile din zona de date și din zona de cod sunt descrise în executabil. 
- 
- 
-==== Zona de cod ==== 
- 
-Segmentul de cod (denumit și ''​text segment''​) reprezintă instrucțiunile în limbaj mașină ale programului. Registrul de tip ''​instruction pointer''​ (IP) va referi adrese din zona de cod. Se citește instrucțiunea indicată de către IP, se decodifică și se interpretează,​ după care se incrementează contorul programului și se trece la următoarea instrucțiune. ​ 
-Zona de cod este, de obicei, o zonă read-only pentru ca procesul să nu poată modifica propriile instrucțiuni prin folosirea greșită a unui pointer. Zona de cod este partajată între toate procesele care rulează același program. Astfel, o singură copie a codului este mapată în spațiul de adresă virtual al tuturor proceselor. 
- 
-==== Zone de date ==== 
- 
-Zonele de date conțin variabilele globale definite într-un program și variabilele de tipul read-only. În funcție de tipul de date există mai multe subtipuri de zone de date. 
- 
-=== .data === 
- 
-Zona ''​.data''​ conține variabilele globale și variabilele statice __inițializate__ la valori nenule ale unui program. De exemplu: 
- 
-<code c> 
-static int a = 3; 
-char b = '​a';​ 
-</​code>​ 
- 
-=== .bss === 
- 
-Zona ''​.bss''​ conține variabilele globale și variabilele statice __neinițializate__ ale unui program. Înainte de execuția codului, acest segment este inițializat cu 0. De exemplu: 
- 
-<code c> 
-static int a; 
-char b; 
-</​code>​ 
- 
-În general aceste variabile nu vor fi prealocate în executabil, ci în momentul creării procesului. Alocarea zonei ''​.bss''​ se face peste pagini fizice zero (zeroed frames). 
- 
-=== .rodata === 
- 
-Zona ''​.rodata''​ conține informație care poate fi doar citită, nu și modificată. Aici sunt stocate __literalii__:​ 
-<code c> 
-"​Hello,​ World!"​ 
-"En Taro Adun!" 
-</​code>​ 
- 
-și __constantele__. Toate variabilele globale declarate cu keyword-ul ''​const''​ vor fi puse în ''​.rodata''​. Variabilele locale declarate ca fiind ''​const''​ vor fi puse pe stivă, deci într-o zonă de memorie nemarcată ca //​read-only//​ și vor putea fi modificate prin intermediul unui pointer către acestea. Un caz aparte îl reprezintă variabilele locale constante declarate cu keyword-ul ''​static''​ care vor fi puse în ''​.rodata'':​ 
-<code c> 
-const int a;           /* în .rodata */ 
-const char ptr[]; ​     /* în .rodata */ 
- 
-void myfunc(void) 
-{ 
-   int x;              /* pe stivă */ 
-   const int y;        /* pe stivă */ 
-    
-   ​static const int z; /* în .rodata */ 
-    
-   ​static int p = 8;   /* în .data */ 
-   ​static int q;       /* în .bss */ 
-   ... 
-</​code>​ 
- 
-==== Stiva ==== 
- 
-Stiva este o regiune dinamică în cadrul unui proces, fiind gestionată automat de compilator. ​ 
- 
-Stiva este folosită pentru a stoca "stack frame-uri"​. Pentru fiecare apel de funcție se va crea un nou "stack frame"​. 
- 
-Un "stack frame" conține: 
-  *variabile locale ​ 
-  *argumentele funcției ​ 
-  *adresa de retur 
- 
-Pe marea majoritate a arhitecturilor moderne stiva crește în jos (de la adrese mari la adrese mici) și heap-ul crește în sus. Stiva crește la fiecare apel de funcție și scade la fiecare revenire din funcție. 
- 
-În figura de mai jos este prezentată o vedere conceptuală asupra stivei in momentul apelului unei funcții. 
- 
-{{ so:​laboratoare-2013:​call_stack.png }} 
- 
-==== Heap-ul ==== 
- 
-Heap-ul este zona de memorie dedicată alocării dinamice a memoriei. Heap-ul este folosit pentru alocarea de regiuni de memorie a căror dimensiune este determinată la runtime. 
- 
-La fel ca și stiva, heap-ul este o regiune dinamică care își modifică dimensiunea. Spre deosebire de stivă, însă, heap-ul nu este gestionat de compilator. Este de datoria programatorului să știe câtă memorie trebuie să aloce și să rețină cât a alocat și când trebuie să dealoce. Problemele frecvente în majoritatea programelor țin de pierderea referințelor la zonele alocate (memory leaks) sau referirea de zone nealocate sau insuficient alocate (accese nevalide). 
- 
-În limbaje precum Java, Lisp etc. unde nu există "​pointer freedom",​ eliberarea spațiului alocat se face automat prin intermediul unui garbage collector. Pe aceste sisteme se previne problema pierderii referințelor,​ dar încă rămâne activă problema referirii zonelor nealocate. 
-  
-  
- 
-===== Alocarea/​Dealocarea memoriei ===== 
- 
-**Alocarea memoriei** este realizată static de compilator sau dinamic, în timpul execuției. //Alocarea statică// e realizată în segmentele de date pentru variabilele globale sau pentru literali. 
- 
-În timpul execuției, variabilele se alocă pe stivă sau în heap. Alocarea pe stivă se realizează automat de compilator pentru variabilele locale unei funcții (mai puțin variabilele locale prefixate de identificatorul **static**). 
- 
-//Alocarea dinamică// se realizează în heap. Alocarea dinamică are loc atunci când nu se știe, în momentul compilării,​ câtă memorie va fi necesară pentru o variabilă, o structură, un vector. Dacă se știe din momentul compilării cât spațiu va ocupa o variabilă, se recomandă alocarea ei statică, pentru a preveni erorile frecvent apărute în contextul alocării dinamice. 
- 
-Pentru a fragmenta cât mai puțin spațiul de adrese al procesului, ca urmare a alocărilor și dealocărilor unor zone de dimensiuni variate, alocatorul de memorie va organiza segmentul de date alocate dinamic sub formă de //heap//, de unde și numele segmentului. 
- 
-**Dealocarea memoriei** înseamnă eliberarea zonei de memorie (este marcată ca fiind liberă) alocate dinamic anterior. ​ 
- 
-Dacă se omite dealocarea unei zone de memorie, aceasta va rămâne alocată pe întreaga durata de rulare a procesului. Ori de câte ori nu mai este nevoie de o zonă de memorie, aceasta trebuie dealocată pentru eficiența utilizării spațiului de memorie. 
- 
-Nu trebuie neapărat realizată dealocarea diverselor zone înainte de un apel [[http://​linux.die.net/​man/​3/​exit|exit]] sau înainte de încheierea programului pentru că acestea sunt automat eliberate de sistemul de operare. 
- 
-<​note>​In majoritatea cazurilor, apar probleme dacă se încearcă dealocarea aceleiași zone de memorie, de două ori. Aceasta e din cauza ca se corup datele interne de management al zonelor alocate. De obicei, kernelul semnalizeaza problema cu o intrerupere sincrona.</​note>​ 
-==== Alocarea memoriei în Linux ==== 
- 
-În Linux, alocarea memoriei pentru procesele utilizator se realizează prin intermediul funcțiilor de bibliotecă [[http://​linux.die.net/​man/​3/​malloc|malloc]],​ [[http://​linux.die.net/​man/​3/​calloc|calloc]] și [[http://​linux.die.net/​man/​3/​realloc|realloc]],​ iar dealocarea ei prin intermediul funcției [[http://​linux.die.net/​man/​3/​free|free]]. Aceste funcții reprezintă apeluri de bibliotecă și rezolvă cererile de alocare și dealocare de memorie, pe cât posibil, în user space. 
- 
-<spoiler Implementarea funcției malloc> 
-\\  
-Implementarea funcției [[http://​linux.die.net/​man/​3/​malloc|malloc]] depinde de sistemul de operare. 
- 
-Există implementări care țin niște tabele care specifică zonele de memorie alocate în heap. Dacă există zone libere pe heap, un apel [[http://​linux.die.net/​man/​3/​malloc|malloc]] care cere o zonă de memorie care poate fi încadrată într-o zonă liberă din heap va fi satisfăcut imediat, marcând în tabel zona respectivă ca fiind alocată și întorcând programului apelant un pointer spre ea.  
- 
-Dacă, în schimb, se cere o zonă care nu încape în nici o zonă liberă din heap, [[http://​linux.die.net/​man/​3/​malloc|malloc]] va încerca extinderea heap-ului prin apelul de sistem [[http://​linux.die.net/​man/​2/​brk|brk]] sau [[http://​linux.die.net/​man/​3/​mmap|mmap]]. 
- 
-{{so:​laboratoare-2013:​heap_bounds.gif|}} 
- 
-Există implementări care pentru fiecare zonă de memorie cerută cu [[http://​linux.die.net/​man/​3/​malloc|malloc]] adaugă un header în care sunt trecute informații utile - dimensiunea unei zone, pointer la următoarea zonă, dacă zonă a fost eliberată sau nu.  
- 
-{{so:​laboratoare-2013:​heap_chunks.gif|}} 
- 
-Mai multe detalii despre malloc și modul de organizare al heap-ului [[http://​academicearth.org/​lectures/​heap-management|aici]] și [[https://​docs.google.com/​viewer?​url=http://​wiki-prog.kh405.net/​images/​0/​04/​Malloc_tutorial.pdf|aici]]. 
-</​spoiler>​ 
- 
-\\  
-<code c> 
-void *malloc(size_t size); 
-void *calloc(size_t nmemb, size_t size); 
-void *realloc(void *ptr, size_t size); 
-void free(void *ptr); 
-</​code>​ 
- 
-Întotdeauna eliberați ([[http://​linux.die.net/​man/​3/​free|free]]) memoria alocată. Memoria alocată de proces este eliberată automat la terminarea procesului, însă, de exemplu în cazul unui proces server care rulează foarte mult timp și nu eliberează memoria alocată acesta va ajunge să ocupe toată memoria disponibilă în sistem, având astfel consecințe nefaste. 
- 
-**Atenție!** Nu eliberați de două ori aceeași zonă de memorie întrucât acest lucru va avea drept urmare coruperea tabelelor ținute de [[http://​linux.die.net/​man/​3/​malloc|malloc]] ceea ce va avea din nou consecințe nefaste. Întrucât funcția [[http://​linux.die.net/​man/​3/​free|free]] se întoarce imediat dacă primește ca parametru un  pointer ''​NULL'',​ este recomandat ca după un apel [[http://​linux.die.net/​man/​3/​free|free]],​ pointer-ul să fie resetat la ''​NULL''​. 
- 
-În continuare, sunt prezentate câteva exemple de alocare a memoriei folosind [[http://​linux.die.net/​man/​3/​malloc|malloc]]:​ 
- 
-<code c> 
-int n = atoi(argv[1]);​ 
-char *str; 
- 
-/* usually malloc receives the size argument like: 
-   ​num_elements * size_of_element */ 
-str = malloc((n + 1) * sizeof(char));​ 
-if (NULL == str) { 
-perror("​malloc"​);​ 
-exit(EXIT_FAILURE);​ 
-} 
- 
-[...] 
- 
-free(str); 
-str = NULL; 
- 
-</​code>​ 
- 
-<code c> 
- 
-/* Creating an array of references to the arguments received by a program */ 
-char **argv_no_exec;​ 
- 
-/* allocate space for the array */ 
-argv_no_exec = malloc((argc - 1) * sizeof(char*));​ 
-if (NULL == argv_no_exec) { 
-perror("​malloc"​);​ 
-exit(EXIT_FAILURE);​ 
-} 
- 
-/* set references to the program arguments */ 
-for (i = 1; i < argc; i++) 
-argv_no_exec[i-1] = argv[i]; 
- 
-[...] 
- 
-free(argv_no_exec);​ 
-argv_no_exec = NULL; 
-</​code>​ 
- 
-Apelul [[http://​linux.die.net/​man/​3/​realloc|realloc]] este folosit pentru modificarea spațiului de memorie alocat cu un apel [[http://​linux.die.net/​man/​3/​malloc|malloc]] sau [[http://​linux.die.net/​man/​3/​calloc|calloc]]:​ 
- 
-<code c> 
-int *p; 
- 
-p = malloc(n * sizeof(int));​ 
-if (NULL == p) { 
-perror("​malloc"​);​ 
-exit(EXIT_FAILURE);​ 
-} 
- 
-[...] 
- 
-p = realloc(p, (n + extra) * sizeof(int));​ 
- 
-[...] 
- 
-free(p); 
-p = NULL; 
-</​code>​ 
- 
-Apelul [[http://​linux.die.net/​man/​3/​calloc|calloc]] este folosit pentru alocarea de zone de memorie al căror conținut este nul (plin de valori de zero). Spre deosebire de [[http://​linux.die.net/​man/​3/​malloc|malloc]],​ apelul va primi două argumente: numărul de elemente și dimensiunea unui element. 
- 
-<code c> 
-list_t *list_v; /* list_t could be any C type ( except void ) */ 
- 
-list_v = calloc(n, sizeof(list_t));​ 
-if (NULL == list_v) { 
-perror("​calloc"​);​ 
-exit(EXIT_FAILURE);​ 
-} 
- 
-[...] 
- 
-free(list_v);​ 
-list_v = NULL; 
-</​code>​ 
- 
-**Atenție** Conform standardului C, este redundant (și considerat bad practice) să faceți //cast// la valoarea întoarsă de [[http://​linux.die.net/​man/​3/​malloc|malloc]]. 
-<code C> 
-int *p = (int *)malloc(10 * sizeof(int));​ 
-</​code>​ 
-malloc întoarce //void *// care în C este automat convertit la tipul dorit. Mai mult, dacă se face cast, iar headerul ''​stdlib.h''​ necesar pentru funcția malloc nu este inclus, nu se va genera eroare! Pe anumite arhitecturi,​ acest caz poate conduce la un comportament nedefinit. Spre deosebire de C, în C++ este nevoie de cast. Discutia este elaborata ​ [[http://​www.cprogramming.com/​faq/​cgi-bin/​smartfaq.cgi?​answer=1047673478&​id=1043284351 | aici ]]. 
- 
-Mai multe informații despre funcțiile de alocare găsiți în [[http://​www.gnu.org/​software/​libc/​manual/​html_node/​Unconstrained-Allocation.html#​Unconstrained-Allocationl| manualul bibliotecii standard C]] și în pagina de manual **man malloc**. 
- 
-===== Lucru cu memoria - Probleme ===== 
- 
-Lucrul cu heap-ul este una dintre cauzele principale ale aparițiilor problemelor de programare. Lucrul cu pointerii, necesitatea folosirii unor apeluri de sistem/​bibliotecă pentru alocare/​dealocare,​ pot conduce la o serie de probleme care afectează (de multe ori fatal) funcționarea unui program. 
- 
-Problemele cele mai des întâlnite în lucrul cu memoria sunt: 
-  * accesul nevalid la memorie - ce prespune accesarea unor zone care nu au fost alocate sau au fost eliberate. ​ 
-  * leak-urile de memorie ​     - situațiile în care se pierde referința la o zonă alocată anterior. Acea zonă va rămâne ocupată până la încheierea procesului. ​ 
- 
-Ambele probleme și utilitarele care pot fi folosite pentru combaterea acestora vor fi prezentate în continuare. 
-==== Acces nevalid ==== 
- 
-De obicei, accesarea unei zone de memorie nevalide rezultă într-o eroare de pagină (page fault) și terminarea procesului (în Unix înseamnă trimiterea semnalului SIGSEGV -> afișarea mesajului '​Segmentation fault'​). Totuși, dacă eroarea apare la o adresă nevalidă, dar într-o pagină validă, hardware-ul și sistemul de operare nu vor putea sesiza acțiunea ca fiind nevalidă. Acest lucru este din cauza faptului că alocarea memoriei se face la nivel de pagină. Spre exemplu, pot exista situații în care să fie folosită doar jumătate din pagină. Deși cealaltă jumătate conține adrese nevalide, sistemul de operare nu va putea detecta accesele nevalide la acea zonă. Mai multe detalii în laboratorul de [[so:​laboratoare-2013:​laborator-07|Memorie virtuală]]. 
- 
-Asemenea accese pot duce la coruperea heap-ului și la pierderea consistenței memoriei alocate. După cum se va vedea în continuare, există utilitare care ajută la detectarea acestor situații. 
- 
-Un tip special de acces nevalid este [[http://​en.wikipedia.org/​wiki/​Buffer_overflow|buffer overflow]]. Acest tip de atac presupune referirea unor regiuni valide din spațiul de adresă al unui proces prin intermediul unei variabile care nu ar trebui să poată referenția aceste adrese. De obicei, un atac de tip buffer overflow rezultă în rularea de cod nesigur. Protejarea împotriva atacurilor de tip buffer overflow se realizează prin verificarea limitelor unui buffer/​vector fie la compilare, fie la rulare. 
-==== Leak-uri de memorie ==== 
- 
-Un [[http://​en.wikipedia.org/​wiki/​Memory_leak|leak de memorie]] apare în două situații: 
-  *un program omite să elibereze o zonă de memorie 
-  *un program pierde referința la o zonă de memorie alocată și, drept consecință,​ nu o poate elibera 
- 
-Memory leak-urile au ca efect reducerea cantității de memorie existentă în sistem. Se poate ajunge, în situații extreme, la consumarea întregii memorii a sistemului și la imposibilitatea de funcționare a diverselor aplicații ale acestuia. 
- 
-Ca și în cazul problemei accesului nevalid la memorie, [[#​Valgrind|utilitarul Valgrind]] este foarte util în detectarea leak-urilor de memorie ale unui program. 
- 
-==== Dublă dealocare ==== 
- 
-Denumirea de "​dublă dealocare"​ oferă o bună intuiție asupra cauzei: eliberarea de două ori a aceluiași spațiu de memorie. Dubla dealocare poate avea efecte negative deoarece afectează structurile interne folosite pentru a gestiona memoria ocupată. 
- 
-În ultimele versiuni ale bibliotecii standard C, se detectează automat cazurile de dublă dealocare. Fie exemplul de mai jos: 
- 
-<code c dubla_dealocare.c>​ 
-#include <​stdlib.h>​ 
- 
-int main(void) 
-{ 
-char *p; 
- 
-p = malloc(10); 
-free(p); 
-free(p); 
- 
-return 0; 
-} 
-</​code>​ 
- 
-Rularea executabilului obținut din programul de mai sus duce la afișarea unui mesaj specific al glibc de eliberare dublă a unei regiuni de memorie și terminare a programului:​ 
- 
-<code bash> 
- ​so@spook$ make 
- cc -Wall -g    dfree.c ​  -o dfree 
- ​so@spook$ ./​dfree ​ 
-  *** glibc detected *** ./dfree: double free or corruption (fasttop): 0x0000000000601010 *** 
- ​======= Backtrace: ========= 
- /​lib/​libc.so.6[0x2b675fdd502a] 
- /​lib/​libc.so.6(cfree+0x8c)[0x2b675fdd8bbc] 
- ​./​dfree[0x400510] 
- /​lib/​libc.so.6(__libc_start_main+0xf4)[0x2b675fd7f1c4] 
- ​./​dfree[0x400459] 
- 
-</​code>​ 
- 
-Situațiile de dublă dealocare sunt, de asemenea, detectate de Valgrind. 
- 
-==== Alte utilitare pentru depanarea problemelor de lucru cu memoria ==== 
- 
-Utilitarele prezentate mai sus nu sunt singurele folosite pentru detectarea problemelor apărute in [[http://​en.wikipedia.org/​wiki/​Category:​Memory_management_software|lucrul cu memoria]]. Alte utilitare sunt: 
- 
-  *[[http://​dmalloc.com/​|dmalloc]] 
-  *[[http://​mpatrol.sourceforge.net/​|mpatrol]] 
-  *[[http://​duma.sourceforge.net|DUMA]] 
-  *[[http://​perens.com/​works/​software/​ElectricFence/​|Electric Fence]], prezentat în laboratorul de [[http://​elf.cs.pub.ro/​so/​wiki/​laboratoare/​laborator-07#​electricfence|Memorie virtuală]] 
- 
-===== Memoria virtuală ===== 
- 
-Mecanismul de memorie virtuală este folosit de către nucleul sistemului de operare pentru a implementa o politică eficientă de gestiune a memoriei. Astfel, cu toate că aplicațiile folosesc în mod curent memoria virtuală, ele nu fac acest lucru în mod explicit. Există însă câteva cazuri în care aplicațiile folosesc memoria virtuală în mod explicit. 
- 
-Sistemul de operare oferă primitive de mapare a fișierelor,​ a memoriei sau a dispozitivelor în spațiul de adresă al unui proces. 
-  ***Maparea fișierelor** în memorie este folosită în unele sisteme de operare pentru a implementa mecanisme de memorie partajată. De asemenea, acest mecanism face posibilă implementarea paginării la cerere și a bibliotecilor partajate. 
-  ***Maparea memoriei** în spațiul de adresă este folositoare atunci când un proces dorește să aloce o cantitate mare de memorie. 
-  ***Maparea dispozitivelor** este folositoare atunci când un proces dorește să folosească direct memoria unui dispozitiv (precum placa video). 
- 
-==== Concepte teoretice ====  ​ 
- 
-Dimensiunea spațiului de adresă virtual al unui proces depinde de dimensiunea registrelor procesorului. Astfel, pe un sistem de 32 biți un proces va putea accesa 2^32 = 4GB spațiu de memorie (pe de altă parte, pe un sistem de 64 biți va accesa teoretic 2^64 B). Spațiul de memorie al procesului este împărțit în spațiu rezervat pentru adresele virtuale de kernel - acest spațiu este comun tuturor proceselor - și spațiul virtual (propriu) de adrese al procesului. De cele mai multe ori, împărțirea între cele două este de 3/1 (3GB user space vs 1GB kernel space). 
- 
-Memoria fizică (RAM) este împărțită între procesele active în momentul respectiv și sistemul de operare. Astfel că, în funcție de câtă memorie avem pe mașina fizică, este posibil să epuizăm toate resursele și să nu mai putem porni un proces nou. Pentru a evita acest scenariu s-a introdus mecanismul de memorie virtuală. În felul acesta, chiar dacă spațiul virtual (compus din segmentul de text, data, heap, stivă) al unui proces este mai mare decât memoria fizică disponibilă pe sistem, procesul va putea rula încărcându-și în memorie doar paginile de care are nevoie în timpul execuției (on demand paging). 
- 
-Spațiul virtual de adrese este împărțit în //pagini virtuale// (page). Corespondentul pentru memoria fizică este //pagina fizică// (frame). Dimensiunea unei pagini virtuale este egală cu cea a unei pagini fizice. Dimensiunea este dată de hardware (în majoritatea cazurilor o pagină are 4KB pe un sistem de 32 biți sau 64 biți). 
- 
-Atât timp cât un proces în timpul rulării accesează numai pagini rezidente în memorie, se execută ca și când ar avea tot spațiul mapat în memoria fizică. În momentul în care un proces va dori să acceseze o anumită pagină virtuală, care nu este mapată în memorie, se va genera un //page fault//, iar în urma acestui page fault pagina virtuală va fi mapată la o pagină fizică. Două procese diferite au spațiu virtual diferit, însă anumite pagini virtuale din aceste procese se pot mapa la aceeași pagină fizică. Astfel că, două procese diferite pot partaja o aceeași pagină fizică, dar nu partajează pagini virtuale. 
- 
-=== malloc === 
- 
-Așa cum am aflat in sectiunea [[#Alocarea memoriei în Linux|Alocarea memoriei în Linux]], ''​malloc''​ alocă memorie pe heap, deci în spațiul virtual al procesului. ​ 
- 
-Alocarea memoriei virtuale se face la nivel de pagină, astfel că ''​malloc''​ va aloca de fapt cel mai mic număr de pagini virtuale ce cuprinde spațiul de memorie cerut. Fie următorul cod:<​code c> 
-char *p = malloc(4150);​ 
-DIE(p == NULL, "​malloc failed"​);​ 
-</​code>​ 
- 
-Considerând că o pagină virtuală are 4KB = 4096 octeți, atunci apelul ''​malloc''​ va aloca 4096 octeți + 54 octeți = 4KB + 54 octeți, spațiu care nu este cuprins într-o singură pagină virtuală, astfel că se vor aloca 2 pagini virtuale. În momentul alocării cu ''​malloc''​ nu se vor aloca (tot timpul) și pagini fizice; acestea vor fi alocate doar atunci când sunt accesate datele din zona de memorie alocată cu ''​malloc''​. De exemplu, în momentul accesării unui element din p se va genera un //page fault//, iar pagina virtuală ce cuprinde acel element va fi mapată la o pagină fizică. 
- 
-<​note>​ 
-În general, la apelul malloc de dimensiuni mici (când se apelează în spate apelul de sistem ''​brk''​) biblioteca standard C parcurge paginile alocate, se generează page fault-uri, iar la revenirea din apel paginile fizice vor fi deja alocate. Putem spune că pentru dimensiuni mici, apelul ''​malloc'',​ așa cum este văzut el din aplicație (din afara bibliotecii standard C), alocă și pagini fizice și pagini virtuale. 
- 
-Mai mult, alocarea efectivă de pagini virtuale și fizice are loc în momentul apelului de sistem ''​brk''​. Acesta prealocă un spațiu mai mare, iar viitoarele apeluri ''​malloc''​ vor folosi acest spațiu. În acest fel, următoarele apeluri ''​malloc''​ vor fi eficiente: nu vor face apel de sistem, nu vor face alocare efectivă de spațiu virtual sau fizic, nu vor genera //page fault-uri//​. 
- 
-Apelul ''​malloc''​ este mai eficient decât apelul ''​calloc''​ pentru că nu parcurge spațiul alocat pentru a-l umple cu zero-uri. Acest lucru înseamnă că ''​malloc''​ va întoarce zona alocată cu informațiile de acolo; în anumite situații, acest lucru poate fi un risc de securitate - dacă datele de acolo sunt private. 
-</​note>​ 
- 
-==== Maparea fișierelor ==== 
- 
-În urma mapării unui fișier în spațiul de adresă al unui proces, accesul la acest fișier se poate face similar cu accesarea datelor dintr-un vector. Eficiența metodei vine din faptul că zona de memorie este gestionată similar cu memoria virtuală, supunându-se regulilor de evacuare pe disc atunci când memoria devine insuficientă (în felul acesta se poate lucra cu mapări care depășesc dimensiunea efectivă a memoriei fizice). Mai mult, partea de ''​I/​O''​ este realizată de către kernel, programatorul scriind cod care doar preia/​stochează valori din/în regiunea mapată. Astfel nu se mai apelează ''​read'',​ ''​write'',​ ''​lseek''​ - ceea ce adesea simplifică scrierea codului. 
- 
-<note important>​ Nu orice descriptor de fișier poate fi mapat în memorie. Socket-urile,​ pipe-urile, dispozitivele care nu permit decât accesul secvențial (ex. char device) sunt incompatibile cu conceptele de mapare. Există cazuri în care fișiere obișnuite nu pot fi mapate (spre exemplu, dacă nu au fost deschise pentru a putea fi citite; pentru mai multe informații:​ **man mmap**). </​note>​ 
- 
-=== mmap === 
- 
-Prototipul funcției [[http://​linux.die.net/​man/​2/​mmap|mmap]] ce permite maparea unui fișier în spațiul de adresă al unui proces este următorul: 
- 
-<code cpp> 
-void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); 
-</​code>​ 
- 
-Funcția va întoarce în caz de eroare ''​MAP_FAILED''​. Dacă maparea s-a făcut cu succes, va întoarce un pointer spre o zonă de memorie din spațiul de adresă al procesului, zonă în care a fost mapat fișierul descris de descriptorul ''​fd'',​ începând cu offset-ul ''​offset''​. Folosirea parametrului ''​start''​ permite propunerea unei anumite zone de memorie la care să se facă maparea. Folosirea valorii ''​NULL''​ pentru parametrul ''​start''​ indică lipsa vreunei preferințe în ceea ce privește zona în care se va face alocarea. Adresa precizată prin parametrul ''​start''​ trebuie să fie multiplu de //​dimensiunea unei pagini//. Dacă sistemul de operare nu poate să mapeze fișierul la adresa cerută, atunci îl va mapa la o adresă apropiată și multiplu de dimensiunea unei pagini. Cea mai corespunzatoare adresa este si intoarsa. ​ 
- 
-Parametrul ''​prot''​ specifică tipul de acces care se dorește: ​ 
-  *''​PROT_READ''​ (citire) 
-  *''​PROT_WRITE''​ (scriere) ​ 
-  *''​PROT_EXEC''​ (execuție) 
-  *''​PROT_NONE''​. ​ 
-Cand zona e folosită altfel decât s-a declarat, este generat un semnal ''​SIGSEGV''​. 
- 
-Parametrul ''​flags''​ permite stabilirea tipului de mapare ce se dorește; poate lua următoarele valori (combinate prin SAU pe biți; trebuie să existe cel puțin ''​MAP_PRIVATE''​ sau ''​MAP_SHARED''​):​ 
-  *''​MAP_PRIVATE'' ​  - Se folosește o politică de tip //​copy-on-write//​. Zona va conține inițial o copie a fișierului,​ dar scrierile nu sunt făcute în fișier. Modificările //nu vor fi vizibile în alte procese//, dacă există mai multe procese care au făcut ''​mmap''​ pe aceeași zonă din același fișier. 
-  *''​MAP_SHARED'' ​   - Scrierile sunt actualizate imediat în toate mapările existente. In acest fel toate procesele care au realizat mapări vor vedea modificările. Lucru datorat faptului că mapările ''​MAP_SHARED''​ se fac peste paginile fizice din page cache, iar apelurile r/w folosesc paginile fizice din page cache pentru a reduce numărul de citiri/​scrieri de pe disc. Actualizările pe disc vor avea loc la un moment de timp ulterior, nespecificat. 
-  *''​MAP_FIXED'' ​    - Dacă nu se poate face alocarea la adresa specificată de ''​start'',​ apelul va eșua. 
-  *''​MAP_LOCKED'' ​   - Se va bloca paginarea pe această zonă, în maniera [[http://​linux.die.net/​man/​2/​mlock|mlock]]. 
-  *''​MAP_ANONYMOUS''​ - Se mapează memorie RAM (argumentele ''​fd''​ și ''​offset''​ sunt ignorate). 
- 
- Este de remarcat faptul că folosirea ''​MAP_SHARED''​ permite partajarea memoriei între procese care nu sunt înrudite. În acest caz, conținutul fișierului devine conținutul inițial al memoriei partajate și orice modificare făcută de procese în această zonă este copiată apoi în fișier, asigurând persistență prin sistemul de fișiere.  ​ 
- 
-=== msync === 
- 
-Pentru a declanșa în mod explicit sincronizarea fișierului cu maparea din memorie este disponibilă funcția [[http://​linux.die.net/​man/​2/​msync|msync]]:​ 
- 
-<code c> 
-int msync(void *start, size_t length, int flags); 
-</​code>​ 
- 
-unde ''​flags''​ poate fi: 
-  *''​MS_SYNC''​ - Datele vor fi scrise în fișier și se așteaptă până se termină. 
-  *''​MS_ASYNC''​ - Este inițiată secvența de salvare, dar nu se așteaptă terminarea ei. 
-  *''​MS_INVALIDATE''​ - Se invalidează mapările zonei din alte procese, astfel incât procesele își vor face update cu datele noi înscrise. 
- 
-Apelul msync este util pentru a face scrierea paginilor modificate din page cache pe disc, cu scopul de a evita pierderea modificărilor în cazul unei căderi a sistemului. 
-==== Alocare de memorie în spațiul de adresă al procesului ==== 
- 
-În UNIX, tradițional,​ pentru alocarea //memoriei dinamice//, se folosește apelul de sistem [[http://​linux.die.net/​man/​2/​brk|brk]]. Acest apel crește sau descrește zona de heap asociată procesului. Odată cu oferirea către aplicații a unor apeluri de sistem de gestiune a memoriei virtuale ([[http://​linux.die.net/​man/​2/​mmap|mmap]]),​ a existat posibilitatea ca procesele să aloce memorie folosind aceste noi apeluri de sistem. Practic, procesele pot mapa memorie în spațiul de adresă, nu fișiere. 
- 
-Procesele pot cere alocarea unei zone de memorie de la o anumită adresă din spațiul de adresare, chiar și cu o anumită politică de acces (citire, scriere sau execuție). În UNIX, acest lucru se face tot prin intermediul funcției [[http://​linux.die.net/​man/​2/​mmap|mmap]]. Pentru acest lucru parametrul ''​flags''​ trebuie să conțină flag-ul ''​MAP_ANONYMOUS''​. 
- 
-==== Maparea dispozitivelor ==== 
- 
-Există chiar și posibilitatea ca aplicațiile să mapeze în spațiul de adresă al unui proces un dispozitiv de intrare-ieșire. Acest lucru este util, de exemplu, pentru plăcile video: o aplicație poate mapa în spațiul de adresă memoria fizica a plăcii video. În UNIX, dispozitivele fiind reprezentate prin fișiere, pentru a realiza acest lucru nu trebuie decât să deschidem fișierul asociat dispozitivului și să-l folosim într-un apel ''​mmap''​. 
- 
-<note important>​ Nu toate dispozitivele pot fi mapate în memorie, însă atunci când pot fi mapate, semnificația acestei mapări depinde strict de dispozitiv. </​note>​ 
- 
-Un alt exemplu de dispozitiv care poate fi mapat este chiar memoria. În Linux se poate folosi fișierul ''/​dev/​zero''​ pentru a face mapări de memorie, ca și când s-ar folosi flag-ul ''​MAP_ANONYMOUS''​. 
- 
-==== Demaparea unei zone din spațiul de adresă ==== 
- 
-Dacă se dorește demaparea unei zone din spațiul de adresă al procesului se poate folosi funcția [[http://​linux.die.net/​man/​3/​munmap|munmap]]:​ 
- 
-<code c> 
- int munmap(void *start, size_t length); 
-</​code>​ 
- 
-''​start''​ reprezintă adresa primei pagini ce va fi demapată (trebuie să fie multiplu de //​dimensiunea unei pagini//). Dacă ''​length''​ nu este o dimensiune care reprezintă un număr întreg de pagini, va fi rotunjit superior. Zona poate să conțină bucăți deja demapate. Se pot astfel demapa mai multe zone în același timp. 
- 
-==== Redimensionarea unei zone mapate ==== 
- 
-Pentru a executa operații de redimensionare a zonei mapate se poate utiliza funcția [[http://​linux.die.net/​man/​2/​mremap|mremap]]:​ 
- 
-<code c> 
-void *mremap(void *old_address,​ size_t old_size, size_t new_size, unsigned long flags); 
-</​code>​ 
- 
-Zona pe care ''​old_address''​ și ''​old_size''​ o descriu trebuie să aparțină unei singure mapări. O singură opțiune este disponibilă pentru ''​flags'':​ ''​MREMAP_MAYMOVE''​ care arată că este în regulă ca pentru obținerea noii mapări să se realizeze o nouă mapare într-o altă zonă de memorie (vechea zona fiind dealocată). 
-==== Schimbarea protecției unei zone mapate ==== 
- 
-Uneori este nevoie ca modul (drepturile de acces) în care a fost mapată o zonă să fie schimbat. Pentru acest lucru se poate folosi funcția [[http://​linux.die.net/​man/​2/​mprotect|mprotect]]:​ 
- 
-<code c> 
-int mprotect(const void *addr, size_t len, int prot); 
-</​code>​ 
- 
-Funcția primește ca parametri intervalul de adrese [''​addr'',​ ''​addr''​ + ''​len''​ - 1] și noile drepturi de access (''​PROT_READ'',​ ''​PROT_WRITE'',​ ''​PROT_EXEC'',​ ''​PROT_NONE''​). Ca și  la [[http://​linux.die.net/​man/​2/​munmap|munmap]],​ ''​addr''​ trebuie să fie multiplu de //​dimensiunea unei pagini//. Funcția va schimba protecția pentru toate paginile care conțin cel puțin un octet în intervalul specificat. 
- 
-==== Exemplu ==== 
-<code cpp> 
-int fd = open("​fisier",​ O_RDWR); 
-void *p = mmap(NULL, 2*getpagesize(),​ PROT_NONE, MAP_SHARED, fd, 0); 
-// *(char*)p = '​a';​ // segv fault 
-mprotect(p, 2*getpagesize(),​ PROT_WRITE);​ 
-*(char*)p = '​a';​ 
-munmap(p, 2*getpagesize());​ 
-</​code>​ 
- 
-Apelul ''​getpagesize''​ va returna dimensiunea unei pagini in bytes. 
- 
- 
---------------------- 
-In python totul e pe heap, pt ca e intepretat. Merge doar cu limbaje compilate, noi luam exemplul C. 
- 
- 
-Le afisez pid-ul pt mmap 
- 
-Bss sir 1024x1024 
-1,.. aici creste cu un MB memoria sau 0,.. 
- 
-Malloc de 4MB si se vede ca nu e nimic si apoi scriu ceva in pagina si o sa vada ca apare pagina 
- 
-Mmap in python pt memorie virtuala. 
- 
- 
- 
  
sde/laboratoare/07_ro_python.1585571387.txt.gz · Last modified: 2020/03/30 15:29 by ioana_maria.culic
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0