Differences

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

Link to this comparison view

cpl:labs:12 [2016/01/11 22:04]
sorin.baltateanu [JAVA]
cpl:labs:12 [2017/01/11 01:06] (current)
bogdan.nitulescu [Exerciții de laborator (15p)]
Line 1: Line 1:
-======= 12. Garbage Collection =======+====== 12. Linkers and Loaders ​======
  
-Termenul de **Garbage Collection** (gc) se referă la algoritmii de eliberare implicită a memoriei dinamice sau, altfel +===== Nice to read =====
-spus, de colectare a zonelor de memorie devenite inaccesibile.+
  
-Zonele care pot să fie eliberate (garbage) sunt zone de memorie la care nu se mai poate ajunge prin intermediul unui pointer sau eventual a unei succesiuni de pointeri accesibiliDespre aceste zone se spune că sunt inaccesibile spre deosebire de zonele care sunt accesibile şi despre care se spune că sunt în viaţă.+  * [[http://​www.iecc.com/​linker/​|Linkers and Loaders]], ​ John R. Levine
  
-Iniţial aceste tehnici au apărut în legătură cu limbajele ​de tip Lisp pentru care alocarea memoriei se face implicit. În prezent +<​note>​ 
-se încearcă utilizarea acestor tehnici ​și pentru limbajele care utilizează alocarea explicită a memoriei dinamice (C, C++). Limbaje mai noi, precum Java, au fost proiectate pentru a putea să utilizeze această tehnică. ​+Acest laborator va abunda de rom-englezăîn special ​de cuvintele **linkăre** și **loadăre**. :) 
 +</​note>​ 
 +===== Prezentare teoretică =====
  
-===== Principii de funcționare =====+**Linkerele** unesc mai multe fișiere compilate (obiect) pentru a genera un singur fișier executabil.
  
-**GC** se execută, de regulă, când nu mai este memorie ​liberă disponibilă. Trebuie să rezolve două probleme: să identifice zonele nefolosite într-un mod conservativ și să elibereze zonele identificate+**Loaderele** încarcă un program executabil la o anumită adresă în memorie.
  
-Identificarea zonelor de memorie ​în viață se face pornind ​de la variabilele accesibile ​(mulțime rădăcinăatunci când se execută colectarea memoriei. Mulțimea rădăcină este formată din variabilele globale, variabilele locale din stiva curentă șregistre. Pornind ​de la această mulțime și parcurgând obiectele accesibile prin intermediul unor pointeri se pot identifica obiectele accesibile. Tot ce nu este accesibil în acest fel reprezintă zona inaccesibilă (garbage).+Acestea permit construirea programelor din module, ​în locul unui program mare și monolitic. Astfel, una din principalele sarcini ale linkerelor și loaderelor este să lege numele abstracte folosite ​de programatori ​(ex: numele funcțiilor din biblioteciși să le transforme în nume concrete (ex: locația deplasată cu 512 octeți de la începutul secvenței codului executabil din memorie sau o adresă numerică).
  
-Pentru a identifica aceste zone, trebuie să existe o strategie pentru a răspunde la două întrebări:​ +==== Asambloare ====
-  * dându-se un obiect, acesta conține pointeri? +
-  * dându-se un pointer, unde este începutul și sfârșitul obiectului spre care indică pointerul?+
  
-===== Algoritmi ​de Garbage Collection =====+La începuturile erei calculatoarelor,​ programarea se făcea în totalitate în cod mașină. Programatorii scriau programele simbolice pe hârtie și le transformau //de mână// în cod mașină. Dacă programatorul folosea nume simbolice (de exemplu un nume de procedură) acestea trebuiau transformate //de mână// în adrese și, dacă apoi se descoperea că o instrucțiune trebuie adăugată într-un anumit loc în program, acesta trebuia verificat pentru a fi modificate toate adresele afectate de acea instrucțiune.
  
-Există mai multe tipuri ​de astfel de algoritmi:​ +Problema în aceasta abordare era că numele erau legate ​de o adresă fixă prea devreme în procesul ​de dezvoltare. Această problemă ​fost rezolvată ​cu ajutorul asambloarelor ce permiteau folosirea de nume simbolice. Dacă programul trebuia schimbat, programatorul trebuia ​să îl reasambleze,​ dar această muncă ​de a atribui adresele fizice nu mai era făcută de el.
-  * secvențiali +
-    * un singur thread  +
-  * paraleli +
-    * mai multe thread-uri; posibilitatea ​de a rula pe mai multe core-uri simultan +
-  * incrementali +
-    * în paralel ​cu execuția programului +
-    * trebuie ​să limiteze timpul petrecut într-un pas de GC +
-  * cu compactare/​copiere +
-    * reduc fragmentarea memoriei +
-    * cresc gradul de localitate ​datelor +
-    * alocare rapidă (incrementare ​de pointeri)+
  
-Mai multe detalii despre diversele tipuri de algoritmi în [[http://​ocw.cs.pub.ro/​courses/​cpl/​courses/​11|cursul de Garbage Collection]].+==== De ce linkere? ====
  
-===== Mark & Sweep =====+Bibliotecile complică această problemă (existau biblioteci de cod încă înaintea apariției asambloarelor). Aceste biblioteci ar trebui să fie încărcate în memorie și să poată fi accesate din programul care le apelează (eventual să fie încărcate în același loc cu celelalte metode ale programului). În general aceste biblioteci vin sub o formă binară (compilată) și ar trebui ca adresele lor sa poată fi atribuite în funcție de adresa efectivă la care sunt încărcate.
  
-Algoritmii din această ​clasă presupun parcurgerea tuturor lanțurilor posibile ​de pointeri accesibili și marcarea zonelor de memorie indicate de acestea (**mark**)Este ca și cum s-ar turna vopsea prin pointeri, iar zonele ​de memorie utilizate ​(accesibiledevin colorate.+Pentru a rezolva ​această ​problemă a fost introdusă noțiunea de **cod relocabil**. Programatorii ​și asamblorul scriau module care începeau atribuirea adreselor relative ​de la 0 și amânau atribuirea adreselor absolute ​(adrese fizicepână în **momentul linkeditării**.
  
-După ce se realizează această operație, se parcurge întreaga zonă heap și se realizează înlănțuirea zonelor de memorie nemarcate care vor forma spațiul disponibil (**sweep**).+==== De ce loadere? ====
  
-== pseudocod ==+Odată cu apariția sistemelor de operare, a devenit necesară o **separare a linkerelor de loadere**. Înainte programele puteau fi asamblate și link-ate cu adrese fixe pentru că programele aveau toată memoria la dispoziție,​ dar în contextul sistemelor de operare, în momentul în care modulele erau legate, programele nu știau la ce adresă vor fi încărcate.
  
-^ ^ ^  +Astfel linkerele făceau partea de agregare a modulelor și foloseau adrese relative, în timp ce loaderele le transformau în adrese absolute odată cu încărcarea programelor în memorie.
-|<code c> +
-new(A) { +
-  if (freeList este goala) { +
-    mark&​sweep() +
-    if (freeList este goala) +
-      return (“out of memory”) +
-  } +
-   +
-  pointer = allocate(A) +
-  return pointer +
-}+
  
-mark&​sweep() +Această evoluție a continuat și a mai parcurs o serie de stagii marcate în principal de apariția blocurilor de date partajate de mai multe programe ​(linkerul trebuia să asigure suportul pentru accesul la aceste date), împărțirea programelor în secțiuni ​(linkerul trebuie să combine toate secțiunile de fiecare tip din fiecare modul), apariția bibliotecilor dinamice etc.
-  for p in root +
-    mark(p) +
-  sweep() +
-+
-</​code>​ | <code c> +
-mark(Obiect) { +
-  if (marc(Obiect) == nemarcat) { +
-    marcheaza Obiect +
-    for d in descendentii (Obiect) +
-      mark(d) +
-  } +
-}+
  
-sweep() { +===== Linkere vs. Loadere =====
-  p bazaHeap +
-  while (p < topHeap) { +
-    if (marc(p) ​== nemarcat) +
-      free(p) +
-    else { +
-      sterge marcaj p +
-      p p + size(obiect p) +
-    } +
-  } +
-+
-</​code>​ |+
  
-==== Probleme algoritmi ====+Atât linkerele cât și loaderele lucrează (sau modifică) cod obiect și sunt cam singurele instrumente de largă răspândire care lucrează cu acest tip de cod (în afara debuggerelor).
  
-Dacă obiectele alocate sunt de dimensiuni foarte diferite și alocarea se face într-o secvență nefavorabilă,​ se poate ajunge în situația ca deși spațiul total disponibil este suficient pentru o cerere de alocare, aceasta să nu poată fi satisfăcută datorită ​**fragmentării memoriei dinamice**.+Există trei mari acțiuni pe care le îndeplinesc:​ 
 +  ​relocarea 
 +  ​rezolvarea simbolurilor 
 +  * încărcarea
  
-Deoarece operația **sweep** presupune parcurgerea întregii zone heap, durata execuției algoritmului depinde de dimensiunea zonei de memorie dinamice care poate să fie mult mai mare decât partea utilă. Acest aspect poate limita semnificativ performanțele algoritmilor de acest tip.+==== Relocarea ====
  
-Pentru că obiectele alocate dinamic nu se mută, obiectele create la începutul execuției programului ajung să fie vecine cu obiecte create mult mai târziu. În acest mod, localitatea referințelor este distrusă și apar probleme ​de performanță.+Este o acțiune făcută și de **linker** și de **loader**.
  
-==== Variațiuni algoritmi ====+Compilatoarele și asambloarele creează, în general, fișiere obiect cu adrese relative (pornind de la 0). Relocarea este procesul prin care se atribuie adrese de încărcare diferitelor părți din program, ajustând codul și datele în program pentru a reflecta adresele atribuite. Un program poate fi relocat de mai multe ori. De regulă, un linker creează un program mare cu adresele pornind de la zero din mai multe subprograme (fiecare fiind creat cu adrese pornind de la zero) care vor fi introduse în zone diferite în programul mare. La încărcare,​ loaderul va reloca executabilul produs de linker la o adresă efectivă în memorie.
  
-Fragmentarea se poate rezolva: +==== Rezolvarea simbolurilor ====
-  * mai multe liste de spațiu liber, ordonate după dimensiuni; alocarea se va face după principiul **best fit** +
-  * compactarea zonelor disponibile vecine +
-  * se alocă pagini suficient de mari pentru a ține orice obiect (dezavantaj:​ se irosește multă memorie)+
  
-Compactarea se poate rezolva: +Este o acțiune făcută de **linker**.
-  * prin "​alunecarea"​ zonelor în viață peste zonele inaccesibile (rezolvă fragmentarea și localitatea,​ dar mărește timpul ​de execuție) +
-  ​variante: +
-    ​arbitrar - nu există nicio garanție a ordinii obiectelor +
-    ​alunecare - se face alunecarea pentru a păstra ordinea inițială de alocare +
-    ​liniarizare - obiectele sunt mutate conform modului în care se referă unul la altul+
  
-==== Algoritmul Cheney ​(two fingers====+Într-un program compus din mai multe subprograme se pot face referințe între subprograme prin simboluri ​(de ex. proceduri sau variabile externe: un apel către sqrt() din biblioteca matematică). Rezolvarea unui simbol se face înlocuind toate utilizările simbolului cu adresa la care acesta este definit.
  
-Este o variantă de algoritm din clasa mark&​sweep ce se poate utiliza dacă toate obiectele au aceeași dimensiune.+==== Încărcarea ====
  
-Algoritmul are doi pași: +Este o acțiune făcută de **loader**.
-  ​primul pas face compactarea +
-  ​al doilea pas face actualizarea pointerilor+
  
-Se folosesc doi pointeri: +Încărcarea este procesul ​de aducere a unui program ​de pe un mediu de stocare secundar (hard-disk, SSD, flash etc.) în memoria principală astfel încât programul să fie gata de execuție. Această acțiune poate varia de la simpla copiere ​datelor până la alocarea spațiuluisetarea unor biți de protecție ai memoriei (de ex. paginile de cod sunt read-only) sau maparea unor zone de memorie virtuală pe spații de pe disk/​flash/​SSD/​etc.
-  * //free// - parcurge heap-ul ​de la limita ​de pornire căutând poziții libere +
-  * //​live// ​parcurge heap-ul de la capăt spre început căutând obiecte ​în viață +
-Când //free// găsește o poziție liberă și //​live// ​găsit și el un obiect în viațăse face deplasarea obiectului. După ce se face mutarea, este memorată în vechea locație o referință la noua poziție.+
  
-În pasul al doilea se parcurc obiectele live, iar dacă ele indică spre zona liberă se face corecția corespunzătoare.+==== Linking loaders ====
  
-**Avantaj**:​ simplu, nu necesită spațiu suplimentar\\ +Un singur program poate face toate cele trei funcții amintite mai sus. Limita dintre relocare și identificarea simbolurilor poate fi destul de confuză. Din moment ce linkerele pot rezolva referințe la simboluriun mod de a trata relocarea este de a atribui un simbol adresei de start a fiecărei părți din program și apoi de a trata relocarea adreselor ca referințe la adresa simbolului ​de bază.
-**Dezavantaj**:​ ordine arbitrară, distruge localitatea datelor, o singură dimensiune ​de obiecte (se pot utiliza mai multe zone de heap pentru dimensiuni diferite) +
-{{ :​cpl:​labs:​screen_shot_2016-01-06_at_11.10.57.png?500 |}}+
  
-===== Reference Counting ===== 
  
-Algoritmii de tip **reference counting** păstrează contoare de utilizare pentru fiecare obiect.+===== Formatul Unix ELF =====
  
-De fiecare dată când un obiect este referit ​de un pointercontorul este incrementat. De fiecare dată când un pointer este distrus, contorul obiectului spre care acesta indică este decrementat. Dacă un contor a ajuns la zero înseamnă că obiectul respectiv nu mai este accesibil ​și poate fi trecut imediat în lista spațiului disponibil sau se poate face o fază de măturare în care se caută obiecte cu contor zero.+Fișierele ELF pot fi de trei tipuri (puțin diferite):​ 
 +  * **relocabile** - create de compilatoare sau asambloaredar trebuie procesate de linker înainte de execuție 
 +  * **executabile** - gata relocate ​și cu toate simbolurile rezolvate exceptând, ​poate, simbolurile din bibliotecile partajate care vor fi rezolvate la runtime 
 +  * **partajate** - biblioteci comune ce conțin informații despre simboluri (folosite ​de linker) și cod executabil
  
-Probleme: +Compilatoarele,​ asambloarele și linkerele tratează fișierele ELF ca un set de secțiuni logice descrise de un //section header table//. Loaderul tratează fișierele ELF ca un set de segmente descrise de un //program header table//, un segment fiind de obicei compus din mai multe secțiuni.
-  * structurile ciclice nu ajung la zero +
-  * menținerea contoarelor mărește timpul ​de execuție+
  
-Se poate combina cu execuția periodică a unui algoritm mark&​sweep prin limitarea valorii contoarelor. Dacă se ajunge la limita maximă, atunci contorul nu mai este nici incrementat,​ nici decrementat,​ limitând astfel numărul de operații suplimentare pentru obiectele des referite. Prin execuția ulterioara a algoritmului mark&​sweep se va parcurge toată memoria ​și se vor identifica atât structurile ciclice cât și obiectele cu contor blocat.+==== Fișiere ELF relocabile ​și partajate ====
  
-===== Copy Collection =====+Un fișier relocabil sau partajat este considerat o colecție de secțiuni definite în header. Fiecare secțiune conține un singur tip de informație (ex: codul programului,​ date read-only sau read-write, intrări relocabile, simboluri, etc.). Fiecare simbol definit este relativ la o secțiune (ex: punctul de intrare al unei proceduri va fi definit relativ la secțiunea care conține codul programului). ​
  
-În algoritmii de acest tip, memoria dinamică este împărțită în două zone. Se face alocarea de memorie într-o singură zonă (**from-space**) până când aceasta se umple. Execuția algoritmului începe în acest moment și copiază toate zonele de memorie accesibile din prima zonă, în a doua zona (**to-space**), care nu va mai conține și //​garbage//​-ul. În continuare cele două zone își schimbă rolurile.+Există două pseudo-secțiuni:​ 
 +  ​SHN_ABS ​conține simboluri ne-relocabile 
 +  ​SHN_COMMON ​- conține ​blocuri de date neinițializate,​ moștenire din formatul a.out
  
-==== Algoritmul Cheney ​pentru ​Copy Collection ====+Un executabil relocabil are în jur de 12 secțiuni. Numele secțiunilor au semnificație ​pentru ​linker, care caută anumite secțiuni pentru prelucrări specifice. Tipurile de secțiuni includ: 
 +  * PROGBITS - conținut al programului:​ cod, date, informații pentru debugger 
 +  * NOBITS - la fel ca și PROGBITS, dar nu alocă spațiu în fișierul propriu zis, ci folosește BSS pentru alocarea datelor la încărcarea programului 
 +  * SYMTAB - tabelă ce conține toate simbolurile 
 +  * DYNSYM - tabelă ce conține simbolurile folosite pentru linkarea dinamică 
 +  * STRTAB - tabelă de stringuri 
 +  * RELA - valorile de bază pentru relocare 
 +  * REL - valorile cu care se fac relocările (valorile sunt adăugate la valoarea de bază) 
 +  * DYNAMIC - informații pentru linkarea dinamică 
 +  * HASH - tabelă de simboluri folosite la runtime
  
-Folosește doi pointeri ​(**scan** și **next**) ​care indică ​la început ​spre zona **to-space**.+Se pot defini ​și secțiuni proprii, însă vom enumera câteva dintre secțiunile tipice ​(standard):​ 
 +  ​.text - de tip PROGBITS (cu modificatori ce permit execuția instrucțiunilor),​ este echivalentul segmentului text din a.out 
 +  ​.data - de tip PROGBITS (cu modificatori ce permit scrierea datelor), este echivalentul segmentului de date din a.out 
 +  ​.rodata - date read-only  
 +  ​.bss - de tip NOBITS (cu modificatori ce permit scrierea); nu ocupă spațiu în fișier ​și este alocată la runtime 
 +  ​.init și .fini - cod care este executat ​la inițializarea/​finalizarea programului; ​spre exemplu, compilatoarele de C++ folosesc aceste secțiuni pentru a inițializa datele globale alocate static 
 +  ​.symtab și .dynsym ​tabele de simboluri 
 +  ​* .strtab și .dynstr - tabele de stringuri
  
-Fiecare obiect accesibil poate să fie referit de către mai mulți pointeri din obiecte diferite - trebuie actualizați pointerii; se memoriează noua adresă (din **to-space**) la vechea adresă (în **from-space**). Această adresă se numește **forwarding pointer**. ​ 
  
-Algoritmul folosește o funcție **forward** care întoarce tot timpul valoarea din **to-space** pentru un pointer. Acesta are două faze: +==== Fișiere ELF executabile ====
-  * în prima fază, obiectele accesibile direct din root sunt mutate în zona **to-space** +
-    * în copiile vechi ale obiectelor se memorează adresele din zona **to-space** +
-    * obiectele mutate în zona **to-space** pot să conțină pointeri către alte obiecte din zona **from-space** +
-  * în a doua fază sunt parcurse obiectele care sunt conținute între adresele indicate de către pointerii **scan** și **next** și se tratează pointerii conținuți în aceste obiecte +
-    * se vor copia noi obiecte în **to-space** +
-    * se vor actualiza pointerii care indică spre obiecte conținute deja în **to-space**+
  
-== pseudocod ==+Fișierele executabile au același format, dar datele sunt aranjate astfel încât fișierul poate fi mapat direct în memorie și rulat. Conține un header de program care urmează headerului ELF din fișier și definește segmentele care trebuie mapate.
  
-^ ^ ^ +Un executabil are în general doar câteva segmenteunul read-only pentru cod și datele read-only și unul read-write pentru datele read-write. Toate secțiunile sunt împachetate în segmentele corespunzătoare astfel încât sistemul poate mapa simplu fișierul în memorie. 
-| <code c> +===== Linker Command Language =====
-### MAIN+
-scan next începutul zonei to-space +
-for each registru r din root +
-  r forward(r)+
  
-while scan < next { +Utilitarul ''​ld''​ poate fi configurat atât din linia de comandă, cât și printr-un limbaj specific: ​//Linker Command Language//. Fișierele ​de configurare au extensia ''​.lds'' ​(ld scriptși constituie o înșiruire de comenzi: 
-  for fiecare camp fi al obiectului *scan +  * cuvânt cheie [argumente] 
-    scan.fi = forward(scan.fi) +  * atribuiri unui simbol 
-  scan = scan + dim(*scan) +  ​* comentarii
-+
-</​code>​ | <code c> +
-forward(p) { +
-  if p indică spre from-space +
-    if p.f1 indica spre to-space +
-      return p.f1 +
-    else { +
-      *next = *p // copiere ​de obiect +
-      p.f1 = next +
-      next = next + dim(*p+
-      ​return p.f1 +
-   } +
-  ​else +
-    return p +
-+
-</​code>​ |+
  
-**Probleme**:​ +=== SECTIONS ===
-  * formularea originală face o trecere BFS - distruge localitatea datelor +
-    * se poate face o trecere DFS, dar avem nevoie din nou de stivă (spațiu limitat) +
-    * se poate copia doar obiectul + descendenții imediați+
  
-==== Non-Copying Implicit Collection ​(Baker) ====+Secțiuniile de cod sunt definite folosind comanda ''​SECTIONS'',​ urmată de diverse atribute. Exemplu: 
 +<​code>​ 
 +SECTIONS 
 +
 +       /*  
 +        * „.” (contorul de locație) este setat la 0x10000  
 +        * implicit, la începutul comenzii SECTIONS are valoarea 0 
 +        * este incrementat automat la includerea unei secțiuni cu dimensiunea acesteia 
 +        */ 
 +       ​. ​0x10000; 
 +  
 +       /* 
 +        * adună toate secțiunile .text din fișierele de intrare  
 +        * și pune-le în secțiunea .text în fișierul de ieșiere 
 +        * 
 +        * dacă nu există nici o secțiune .text în nici un fișier de intrare 
 +        * nu se generează în fișierul de ieșire secțiunea .text 
 +        */ 
 +       .text : { *(.text
 +  
 +       /* adresa de la care se scriu date este 0x8000000 */ 
 +       ​. ​0x8000000;​ 
 +  
 +        /* similar text */ 
 +       .data : { *(.data) } 
 +        /* similar text */ 
 +       .bss : { *(.bss) } 
 +
 +</​code>​
  
-În loc să se facă o mutare fizică a obiectelor dintr-o zonă în alta, se mută pointerii la obiecte între două liste.+=== ENTRY ===
  
-Fiecare obiect are trei câmpuri suplimentare invizibile pentru programul care se execută. Două dintre ele sunt utilizate pentru ca obiectul să fie legat într-o listă dublu înlănțuită. Al treilea câmp indică lista la care este conectat obiectulSunt folositeastfeltrei liste: o listă a spațiului disponibilo listă **from** și o listă **to**.+Comanda ''​ENTRY''​ setează entry-pointul programului (prima instrucțiune de executat). Dacă comanda lipsește, atunci se va utiliza valoarea simbolului ''​.start''​ (dacă este definit), sau valoarea simbolului ''​.text''​ (dacă este definit)saudacă nuvaloarea 0.
  
-Aloacarea de memorie se face mutând elemente din lista spațiului disponibil în lista **from**. Algoritmul se declanșează când se epuizează prima listă, cea a spațiului liber disponibil.+=== PROVIDE ===
  
-Colectarea memoriei ​se face mutând obiectele ​în viață din lista **from** în lista **to**. Când toate obiectele accesibile au fost mutate, lista **from** conține numai pointeri spre obiecte care nu mai sunt în viață și devine o listă a spațiului liber disponibil. Execuția copierii pointerilor ​se face într-o manieră similară cu cea a algoritmului Cheney.+Pentru a defini un simbol nou se poate folosi comanda ''​PROVIDE'' ​în cadrul comenzii ''​SECTIONS''​. Exemplu: 
 +<​code>​ 
 +SECTIONS 
 +
 +   .text : { *(.text) } 
 +  
 +   ​PROVIDE(__data_start = .); 
 +   .data : { *(.data) } 
 +   ​PROVIDE(__data_stop =  .); 
 +  
 +   ​PROVIDE(__bss_start = .); 
 +   .bss : { *(.bss) } 
 +   ​PROVIDE(__bss_stop =  ​.); 
 +
 +</​code>​ 
 +Pentru a folosi simbolurile definite ​în exemplul anterior ​se poate proceda ca în exemplul următor: 
 +<code c> 
 +#include <stdio.h>
  
-Principalul avantaj este vitezadeoarece nu se fac copieriiar valorile pointerilor vizibili nu se schimbăceea ce simplifică rolul compilatorului.+extern void * __data_start* __data_stop* __bss_start* __bss_stop;
  
-===== Algoritmi incrementali =====+int main(void) 
 +
 + printf("​.data starts at %p and ends at %p\n", ​ &​__data_start,​ &​__data_stop);​ 
 + printf("​.bss starts at %p and ends at %p\n", ​ &​__bss_start,​ &​__bss_stop);​
  
-Întreruperile necesare GC sunt inacceptabile într-un sistem de timp real - se face colectarea incremental.+ return 0; 
 +
 +</​code>​
  
-Se pune problema consistenței datelor deoarece rulează două procese simultan: +=== Atribute GCC ===
-  * mutator (programul) +
-  * colector (GC)+
  
-**M&S** - cititori-scriitor,​ doar mutatorul modifică pointerii +În GCC se pot specifica pentru anumite tipuri de simboluri comportamente speciale. Sintaxa prin care se decorează un element cu un atribut este următoarea:​ 
-**CC** - mai mulți scriitori+<​code>​ 
 +type_of_element element_name __attribute__ ( attribute_goes_here ); 
 +</​code>​
  
-==== Marcajul tricolor ====+Unul din atributele pe care GCC le suportă este specificarea secțiunii în care va fi inclus simbolul generat: 
 +<code c> 
 +// definim variabila x, o inițializăm cu 7  
 +// și specificăm compilatorului să o pună în secținea "​.cpl"​ 
 +int x __attribute__ ((section ("​.cpl"​))) ​7; 
 +</​code>​
  
-Marcajul tricolor este o notație folosită pentru sincronizare. Obiectele pot să fie colorate cu o culoare din trei posibile: +===== Variabile ​de mediu folosite ​de linker =====
-  * alb - marcajul la începutul ciclului ​de colectare +
-  * gri - obiectul a fost identificat ca fiind în viață, dar obiectele la care se poate ajunge utilizând câmpurile obiectului respectiv nu au fost încă parcurse. La CC, gri sunt obiectele dintre scan și next +
-  * negru - obiectul este în viață la sfârșitul ciclului ​de colectare+
  
-Indiferent ​de tipul de tipul de algoritm utilizat colectorul trebuie ​să respecte condiția - nici un câmp dintr-un obiect negru nu conține un pointer către un obiect alb. +**LD_LIBRARY_PATH** - o listă ​de directoare separate prin '':''​ ce specifică directoarele în care linkerul ​să caute biblioteci, înainte de a se uita în directoarele standard
  
-**Colectorul** realizează traversarea grafului de obiecte în viață şi le schimbă culoarea.+**LD_PRELOAD** - calea către o bibliotecă dinamică ce va fi încărcată în locul unei alte biblioteci; spre exemplu astfel se poate înlocui biblioteca standard C 
 +======= Exerciții de laborator (15p) =======
  
-** Mutatorul** poate să modifice obiectele care au fost deja tratate. +În rezolvarea laboratorului folosițarhiva ​de sarcini {{:​cpl:​labs:​lab12-tasks.zip|}}.
-  * nu poate să facă dintr-un obiect inaccesibil un obiect accesibil șnici să modifice câmpuri din interiorul unui astfel ​de obiect +
-  * un obiect care a fost deja marcat în viață poate să devină inaccesibil +
-  * un câmp (pointer) dintr-un obiect care a fost deja tratat poate să fie modificat+
  
-Prima situație poate să fie ignorată, considerarea unui obiect inaccesibil ca fiind în viață este conservativă și obiectul respectiv va fi identificat ca inaccesibil la următoarea trecere a algoritmului.+Pe măsura ce rezolvați exercițiilenu uitați că modificarea optiunilor compilării implică și recompilarea ​fișierelor sursă. 
 +===== Exercițiul 1 - name mangling (4p) =====
  
-**Probleme:​** +Linkerul este responsabil de rezolvarea numelor. Fiecare fișier obiect conține o tabelă de simboluri. În continuare vom analiza cum arată aceste tabele pentru diverse tipuri de funcții ​în C și în C++.
-  * modificarea câmpurilor ​în obiectele care au fost deja tratate+
  
-Coordonarea între mutator și colector presupune existenţunui mecanism prin care: +Pentru ​vizualiza tabela de simboluri vom utiliza utilitarul ''​nm''​.
-  * mutatorul să fie împiedicat să acceseze un obiect alb sau +
-  * să fie împiedicat să scrie valoarea unui pointer către un obiect alb într-un obiect negru+
  
-În primul caz se utilizează o barieră la citire, (detectează dacă mutatorul încearcă să utilizeze un pointer la un obiect alb). Acesta poate fi vopsit în gri pentru că acum "​se ​știe" că obiectul este accesibil, dar nu se știe cum sunt descendenții acestuia.+<note tip> 
 +Denumirea simbolurilor nu este standard și este strâns dependentă de compilator ​și de platformă. 
 +</​note>​
  
-În al doilea caz se utilizează o barieră la scriere (înregistrează scrierile de pointeri ​în obiecte).+=== Name mangling ​în C (2p===
  
-==== Bariere la scriere ====+Intrați în directorul ''​1-name/​c''​. Vom analiza pe rând fiecare din cele 3 fișiere.
  
-În cazul algoritmilor care nu realizează copierea se utilizează barierele la scriere (nu se pune problema ca mutatorul ​să citească un pointer incorect).+Pentru a genera fișierele obiect, rulați comanda ''​make obj''​. Vom avea nevoie de acestea pentru a vizualiza tabelele de simboluri. Tabela de simboluri poate fi vizualizată rulând comanda: 
 +<​code>​nm -$FIȘIER_OBIECT</​code>​
  
-Există 2 tipuri de bariere la scriere: +Deschideți fișierul ''​main-only.c''​Programul apelează funcția ​''​printf''​fără a include niciun header specificVizualizați tabela ​de simboluriCe observați? Ce înseamnă ​''​T''​Dar ''​U''​Consultați pagina ​de manual ​utilitarului [[http://linux.die.net/man/1/nm|nm]].
-  * snapshot-at-beginning +
-    * se salveaza o copie a tuturor pointerilor înlocuiți la atribuiri; valorile salvate se adaugă la root +
-    * toate obiectele accesibile la inceputul ciclului vor fi negre +
-    * obiectele nou create în timpul unui ciclu sunt negre +
-  * incremental update +
-    * se detectează când pointerii sunt scriși în obiecte negre; dacă pointerul indică spre un obiect alb, se colorează în gri obiectul negru +
-    * posibila implementare:​ se reparcurg obiectele negre din paginile de memorie marcate “dirty” +
- +
- +
-==== Algoritmii Baker cu barieră la citire ==== +
- +
-Copiere incrementală (bazat pe algoritmul Cheney): +
-  * operație atomică: se invalidează obiectele din **from-space**, se copiază root-set-ul în **to-space** (practic, //​from-space//​-ul devine alb, iar root-set-ul devine gri); apoi se colectează incremental +
-  * dacă mutatorul citește un pointer către **from-space**,​ obiectul respectiv este copiat în **to-space** (marcat gri) și pointerul actualizat +
-  * obiectele nou alocate sunt alocate în **to-space** (colorate cu negru) +
- +
-Bariera poate fi implementată software, compilatorul generând pentru fiecare referință la un pointer un cod corespunzător,​ sau hardware pentru mașini dedicate. +
- +
-Utilizarea unei bariere la citire este în general destul de ineficientă deoarece presupune ​că pentru fiecare referire de pointer se face un test referitor la zona în care este conținut obiectul respectivDacă obiectul este într-o zonă de top from se va declanșa operația de copiere a obiectului respectiv în zona to. +
- +
-Se poate utiliza și un sprijin din partea compilatorului care poate să identifice accese care se referă la câmpuri din același obiect și să optimizeze pe această bază codul general. +
- +
-Algoritmul este unul conservativ,​ obiectele noi fiind negre, deci chiar dacă mor imediat nu vor fi șterse decât la următorul ciclu de colectare. +
- +
-===== Generational GC ===== +
- +
-Clasa generational GC încearcă să beneficieze de o propietate observată empiric a obiectelor alocate, și anume faptul că majoritatea obiectelor trăiesc foarte puțin, iar doar o mică parte trăiesc perioade mai lungi. Obiectele cu viață lungă încetinesc în mod nenecesar GC. Tehnicile algoritmului curent împart heap-ul în mai multe sub-heap-uri și separă obiectele pe sub-heap-uri în funcție de generația fiecărui obiect. Obiectele noi sunt alocate într-un subheap dedicat. Când nu mai există memoriese scanează doar primul subheap, iar majoritatea obiectelor vor fi, probabil, dealocate. Subheapurile cu generații mai mari sunt scanate mai puțin frecvent. De vreme ce se scanează fragmente mici de heap și se recuperează proporțional mai mult spațiu, eficiența algoritmului este îmbunătățită. +
- +
- +
-**Câte cicluri de colectare trebuie să fie supravieţuite de către un obiect pentru a fi mutat într-o generație mai veche ?** +
-  * dacă se păstrează un singur ciclu, se poate folosi CC și se pot copia direct în generația ‘mai bătrână’ obiectele care supraviețuiesc +
-  * un obiect cu viața scurtă care a fost creat chiar înainte de colectare, va avansa degeaba la o generație mai mare +
- +
-==== Algoritmul Ungar ==== +
- +
-Prima generație poate avea heap-ul împărțit în trei zone: una pentru obiectele nou create, celelalte fiind zonele **from** și **to**. Se face astfel diferența dintre obiectele foarte noi și cele mai vechi din generația curentă. +
- +
-O variantă a algoritmului Ungar ține doar două zone: de memorare și de alocareObiectele care sunt colectate din zona de memorare se copiază într-o generație mai veche, iar cele din zona de alocare se copiază în zona de memorare. +
-{{ :​cpl:​labs:​screen_shot_2016-01-06_at_12.21.54.png?​500 |}} +
- +
-==== Pointeri între generații ==== +
- +
-Pentru a rezolva problema pointerilor dintr-o generație veche către obiect dintr-o generație nouă, se pot folosi bariere la scriere similare celor utilizate în cazul algoritmilor de alocare incrementali. +
- +
-Pentru orice operație de modificare a unui câmp de tip pointer trebuie să se faca o verificare pentru a stabili dacă nu cumva este vorba de un pointer de la un obiect dintr-o generație mai veche la un obiect dintr-o generație mai nouă. Pointerul respectiv va trebui să fie utilizat în mulțimea rădăcină pentru generația nouă. Abordarea este conservativă,​ obiectul dintr-o generație mai veche datorită căruia se păstrează un obiect dintr-o generație mai nouă poate să nu mai fie accesibil. +
- +
-O altă metodă este folosirea memoriei virtuale (LISP: Symbolics). În loc să se înregistreze obiectele care conțin pointeri între generații se înregistrează paginile din memoria virtuală care conțin astfel de pointeri, granularitatea utlizată fiind la nivel de pagină. Timpul pentru parcurgerea setului înregistrat va depinde de numărul de pagini și de lungimea paginilor și nu de numărul de obiecte în care s-au scris pointeri. +
- +
-===== JAVA ===== +
- +
-Java pornește în implementarea Garbage Collectorului de la observația empirică cunoscută ca //Weak generational hypothesis//​. Obiectele sunt folosite, în majoritatea lor, pentru foarte puțin timp, iar restul au o durată de viață îndelungată. +
- +
-{{:​cpl:​labs:​lab12object-age-based-on-gc-generation-generational-hypothesis.png?​nolink|}} +
- +
-Pornind de la această observație,​ Java împarte heapul în două regiuni (sau generații):​ Young(sau Nursery) șOld. Pentru obiectele noi, alocarea se face din regiunea Young. Dacă spațiul nu este suficient, un GC este executat pe această regiune. În urma lui memoria care nu mai este referită este revendicată,​ iar obiectele care sunt încă în viață sunt mutate în generația Old. În momentul în care spațiul din regiunea Old este epuizat, un GC este executat si spațiul ocupat de obiectele care între timp au devenit nefolosite este eliberat. Colectarea memoriei libere din regiunea Young este referită ca “minor collection”,​ cea din regiunea Old ca “major collection”. O colectare ce are loc în ambele regiuni e referită ca “Full collection”. Modelul prezentat este unul simplificat. Din motive de eficiență,​ Java împarte mai departe regiunile Young și Old în mai multe subregiuni. +
- +
-{{:​cpl:​labs:​12java-heap-eden-survivor-old.png?nolink|}} +
- +
-Zona //Permanent Generation//,​ ce aparține regiunii Old, conține informații cum ar fi: +
-  * metadata despre reprezentarea internă a claselor +
-  * metodele claselor, constante și variabile statice +
-  * informații suplimentare pentru optimizări (JIT) +
-Dimensiunea acestei regiuni poate fi setată prin parameterul XX:​MaxPermSize. +
- +
-In Java 8, informațiile din această regiune au fost mutate în Metaspace, care se află în regiunea nativă a memoriei(XX:​MaxMetaspaceSize). +
- +
-În continuare, pentru generația Old, vom vorbi doar despre regiunea Tenured. +
- +
-//Q: Un program poate primi excepția "​Permanent Generation’s area in memory is exhausted"?​ Care credeți că este cauza?// +
-<​hidden>​ +
-A: Prea multe clase încarcate sau prea mari. +
-</​hidden>​ +
- +
-//Q: Ce credeți că se întâmplă dacă nu e suficient spațiu în Young pentru alocarea unui obiect nou?// +
-<​hidden>​ +
-A: Se încearcă alocarea din Old și dacă nu se reușește se aruncă excepție. +
-</​hidden>​ +
- +
-=== Generația Young === +
-Cea mai mare parte a obiectelor noi se “nasc” aici, iar majoritatea vor “muri” tot aici. În urma unui GC, majoritatea obiectelor sunt colectate, iar obiectele care vor supraviețui suficient de mult timp vor fi mutate în regiunea Tenured. În acest fel, numărul de obiecte “în viață” din generația Young rămâne constant mic. Asta face ca algoritmii de colectare liniari în numărul de obiecte “în viață” să fie foarte eficienți pentru această regiune. +
- +
-Pe de altă parte, obiectele din generația Tenured sunt obiecte care au în general durată de viață mare, motiv pentru care multe dintre ele vor supraviețui procesului de GC. Odată cu numărul mare de obiecte, crește și spațiul ocupat din această regiune și procesul de GC durează considerabil mai mult. +
- +
-Din acest motiv, este important momentul în care decidem să mutăm un obiect din generația Young în generația Tenured. Pe de o parte vrem ca numărul de obiecte din generația Young să rămână mic, lucru pe care îl putem obține prin mutarea imediată a obiectelor în generația Tenured. Pe de altă parte, vrem ca memoria ocupată de obiectele ieșite din uz să fie cât mai repede colectată. Pentru că majoritatea colectărilor se petrec în zona Young, asta înseamnă ​că dorim ca un obiect să rămână suficient de mult în această zonă, altfel, odată ajuns în Tenured va trebui să așteptăm ​ colectare a acestui spațiu, care se face mai rar. Un alt lucru de care dorim să ținem cont este faptul că vom aplica algoritmi de colectare diferiți pentru cele două zone, așa că am vrea ca obiectele din fiecare zonă să respecte presupunerile zonei de care aparțin: +
-  * obiecte cu durată de viață mică în Young.  +
-  * obiecte cu durată de viață mare în Tenured +
- +
-Mașina virtuală de Java încearcă să rezolve această problemă prin împărțirea zonei Young în: +
-  * regiunea Eden: alocările obiectelor noi se fac din această regiune. +
-  * 2 regiuni Survivor: from și to. Aici sunt ținute obiectele vii până ating maturitatea necesară pentru a considera că vor avea o durată de viață îndelungată. În acel moment vor fi mutate în zona Tenured. +
- +
-Inițial, spațiile Eden, Survivor 1 și Survivor 2 sunt libere. Unul dintre ele va fi considerat spațiul //from// iar celălalt spațiul //to//. Rolurile celor două spații se vor schimba la fiecare GC. De menționat faptul că unul dintre cele două spații (to) trebuie să fie întotdeauna liber. +
-Alocările obiectelor se fac din zona Eden. Cu timpul, această zona va fi epuizată, moment în care un GC de tipul Mark and Copy va fi declanșat pentru obiectele din zona Eden și zona Survivor from. Majoritatea obiectelor au “murit” între timp. Obiectele rămase în viață vor fi copiate în zona Survivor //to//, în cazul în care nu au atins încă maturitatea,​ sau în zona Tenured, în caz contrar. La sfârșitul colectării,​ pointerii from și to sunt interschimbați. +
- +
-== Evaluarea maturității == +
-Pentru obiectele aflate în viață se menține un contor al numărului de GC (minor collection) cărora le-au supraviețuit. În momentul în care acest contor depăsește o anumită valoare (denumită //tenuring threshold// și calculată dinamic), un obiect este considerat matur și poate fi mutat în generația Tenured. +
-  +
-//Q: Valoarea maximă pe care poate să o ia //tenuring threshold// e dată de MaxTenuringThreshold care poate fi setat la pornirea mașinii virtuale. Ce valori credeți că sunt recomandate pentru acest parametruDe ceCe credeți că se întâmplă în cazul în care setăm această valoare la 0?// +
-<​hidden>​ +
-A: Obiectele sunt promovate imediat, fără a trece prin survivor. Discuția cu avantajele și dezavantajele ​de a ajunge repede în Tenured. +
-</​hidden>​ +
-//QSe poate întâmpla o promovare în generația Tenured, fără ca un obiect să fi atins maturitatea?//​ +
-<​hidden>​ +
-A: Cand spațiul Survivor //to// este insuficient pentru a ține obiectele rămase în viață din //Eden// și //from//. +
-</​hidden>​ +
- +
-== Marcarea obiectelor aflate în viață == +
- +
-Colectarea pentru zona Young ține cont doar de obiectele din zonele Eden și Survivor from, cele din Tenured fiind ignorateTrebuie totuși detectate referințele către obiectele din zona Young care provin din zona Tenured. +
-Pentru detectarea obiectelor se folosește un algoritm de tip Mark. Rădăcinile sunt considerate a fi: +
-  * variabilele locale (stivă, regiștrii) +
-  * parametrii funcției curente +
-  * threadurile java active (inclusiv partea de thread local storage) +
-  * variabilele statice +
-  * referințele JNI +
-  * referințele din zona tenured +
- +
-Pentru detectarea referințelor din zona tenured în zona Young, se folosește tehnica numită ​//card-marking/​/. Generația veche este împărțită în bucăți de 512 bytes, denumite //​cărți//​. Mașina virtuală va menține un bitmap în care fiecare bit corespunde unei //​cărți//​. În momentul în care o actualizare a unei referințe are loc pentru un obiect din zona Old, bitul corespunzător //​cărții//​ din care face parte obiectul este setat ca fiind dirty. În momentul unei colectări minore, obiectele din zonele marcate ca dirty din zona Old sunt scanate pentru găsirea de referințe către zona Young. Pentru actualizarea referințelor se folosesc bariere la scriere introduse de compilator. +
-  +
-//Q: Credeți că numărul de //cărți// dirty este mare? De ce? Dați exemplu de situație în care poate să apară.// +
-<​hidden>​ +
-A: Nu ar trebui să fie. Obiectele old au tendința de a referi obiecte old. Obiect global cu un câmp currentProcessedItem. +
-</​hidden>​+
  
-Notă: În momentul în care se face Marking, threadurile sunt întrerupte,​ pentru ca graful de referințe să nu mai fie modificat în continuare (pauză //Stop the world//)Oprirea threadurilor se poate face doar în anumite puncte, unde structura obiectelor ​și conținutul referințelor acestora sunt valide. Astfel de puncte poartă denumirea de Safe-Points șsunt introduse automat ​de compilatorAstfel, un GC minor nu pornește imediat ​ce este nevoie de el, ci în momentul în care toate threadurile au atins un SafePoint. ​+Deschideți fișierul ''​simple-functions.c''​Programul definește mai multe funcții având diverse tipuri de argumente. Vizualizați tabela ​de simboluriCe observați? În ce ordine sunt listate funcțiile?
  
-== Copierea Obiectelor rămase în viață == +Deschideți fișierul ''​static-function.c''​. Programul definește o funcție statică și o apelează din funcția main. Vizualizați tabela de simboluri. Ce observați? Ce înseamnă ''​t''?​ Consultați pagina de manual a utilitarului [[http://linux.die.net/man/1/nm|nm]].
-Pentru copierea obiectelor din Eden și Survivor ​//from// în Survivor ​//to// sau Tenured, se folosește un algoritm de tipul Cheney.+
  
-//Q: Care este avantajul acestor copieri? Cum arată zona Eden după colectare? Care e costul alocării unui obiect?// 
-<​hidden>​ 
-A: Nu e nevoie de tracking pentru obiectele moarte. Tot ceea ce a fost în Eden e fie copiat în Survivor to sau în tenured, fie mort. Zona Eden devine "​instant"​ liberă. Nu există fragmentare. Alocarea unui obiect nou necesită doar mutarea unui pointer (pointer bumping). 
-</​hidden>​ 
  
-== Multithreading ​=+=== Name mangling ​în C++ (2p) ===
-Alocarea de obiecte noi e foarte rapidă. Dar majoritatea aplicațiilor java folosesc threaduri. Pentru a evita costurile de performanță implicate de sincronizarea accesului la un pool comun, spațiul Eden este împărțit ​în mai multe “memory pools”, care poartă denumirea de Thread Local Allocation Buffer. Fiecare thread va aloca memorie din TLAB-ul asociat, iar când acesta este epuizat, alocarea se va face dintr-o zonă comună tuturor threadurilor,​ moment în care e nevoie de sincronizare.+
  
-=== Generația Tenured ===+Intrați în directorul ''​1-name/​cpp''​. Vom analiza pe rând fiecare din cele 5 fișiere sursă.
  
-Generația Tenured conține obiectele care au supraviețuit colectării din generația YoungAceste obiecte au tendința de a avea o viață îndelungatăNumărul ​de obiecte aflate în viață la momentul unui GC este așadar mare și prin urmare și spațiul de heap necesar. +Pentru a genera ​fișierele obiect, rulați comanda ''​make obj''​Vom avea nevoie de acestea pentru ​vizualiza tabelele ​de simboluriTabela ​de simboluri poate fi vizualizată rulând comanda: 
-Din aceasta cauza nu putem să folosim Mark and Copy și trebuie să apelăm la alte strategii de colectare.+<​code>​nm -$FIȘIER_OBIECT</​code>​
  
-//Q: De ce nu putem folosi Mark and Copy?// +Deschideți fișierul ''​main-only.cpp''​. Programul include header-ul ''​iostream''​ și importă în cadrul namespace-ului curent streamul ''​cout''​. Vizualizați tabela ​de simboluriCe observați? Folosiți parametrul ''​C''​ al utilitarului ''​nm'' ​pentru a înțelege mai bine simbolistica outputuluiConsultați pagina de manual a utilitarului [[http://​linux.die.net/​man/​1/​nm|nm]].
-<​hidden>​ +
-A: Nu avem o zonă în care să facem spill, așa cum generația Young avea spațiul de TenuredÎnseamnă că spațiul disponibil ar trebui să fie împărțit în 2, pentru a acoperi cazul în care toate obiectele supraviețuiesc colectării. +
-</hidden>+
  
-Un algoritm de Mark Sweep Compact este preferat: +Deschideți fișierul ''​simple-functions.cpp''​. Programul definește mai multe funcții având diverse tipuri de argumenteVizualizați tabela de simboluri.
-  - Mark: asemănător cu ce se întâmplă în genererația Young +
-  ​Sweep: implementare folosind “free lists” pentru obiectele reclamate; acest pas are complexitate liniară în dimensiunea spațiului Heap disponibil. +
-  - Compact: încearcă să rezolve problema fragmentării;​ Obiectele rămase în viață sunt mutate la începutul regiunii+
  
-=== Finalizers === +Deschideți fișierul ''​static-function.cpp''​. Programul definește o funcție statică și o apelează din funcția main. Vizualizați tabela de simboluri.
-Java pune la dispoziția programatorilor posibilitatea definirii, per clasă, a unei metode care să fie apelată în momentul în care un obiect ​din acea clasă este colectat.+
  
-// Q: De ce am folosi-o? Care ar fi dezavantajele?​ Ce se întâmplă dacă aruncăm o excepție? // +Deschideți fișierele ''​myClass.hpp''​ și ''​myClass.cpp''​Cele două definesc clasa ''​myClass''​Vizualizați tabela de simboluriDe ce sunt două simboluri diferite pentru constructorul fără argumente? Citiți explicația ​de [[http://​stackoverflow.com/questions/​6921295/​dual-emission-of-constructor-symbols/​6921467#​6921467|aici]].
-<​hidden>​ +
-A: E.geliberare de resurseNu e predictibil când se face eliberarea obiectuluiÎntârzie colectarea obiectului. Probleme de performanțăFinalizers pentru obiecte ​sunt apelate pe un thread separat care ignoră excepțiile aruncate ​de acestea. +
-</hidden>+
  
-=== Algoritmi de GC în Java === +Deschideți fișierul ''​useClass.cpp''​. Programul folosește clasa definită anterior. Vizualizați tabela de simboluri.
-Pentru că aplicații diferite au nevoi diferite, Java permite alegerea algoritmilor de GC pentru generația Young și generația OldÎn alegerea acestora trebuie să se țină cont de:  +
-  * Performanță (throughput):​ se dorește minimizarea timpului total petrecut în GC +
-  * Latență: se dorește minimizarea pauzelor în care aplicația nu răspunde +
-  * Footprint: se dorește minimizarea memoriei folosite+
  
-//Q: Dați exemple de aplicații cu necesități diferite.//​ +===== Exercițiul 2 - rezolvarea simbolurilor ​(2p=====
-<​hidden>​ +
-A: Server web, unde latența e ascunsă de rețea. Aplicație cu interfață. Server cu puțin ram (e.g. mașină virtuală) +
-</​hidden>​+
  
-Combinațiile ​de GC cele mai întâlnite sunt+Rezolvarea simbolurilor ​de către linker se face în următoarea ordine
-  * Serial GC atât pentru Young, cât și pentru Old +  * simbolul este căutat în fișierul obiect în care este folosit prima oară 
-  * Parallel GC atât pentru Young, cât și pentru Old +  * simbolul este căutat în oricare din fișierele obiect sau bibliotecile statice cu care este compilat ​(în ordinea în care acestea apar ca argumente ale linkerului
-  * Parallel New pentru Young și Concurrent Mark and Sweep (CMSpentru Old +  * simbolul este căutat în bibliotecile partajate (în ordinea în care acestea apar în linia de comandă)
-  * G1 care înglobează colectarea atât pentru Young cât și pentru Old+
  
-== Serial GC == +Intrați în directorul ''​2-symbols''​Vizualizați conținutul fișierelor sursă c. O să găsițtrei implementări diferite ale funcției ''​function''​.
-În cazul acesta se folosește MarkCopy pentru generația Young șMarkSweepCompact pentru generația OldAcești algoritmi sunt single-threaded ​și de tipul stop-the-world.+
  
-//QCând ar fi utilă această combinație?// +Rulați comanda ''​make''​ pentru a genera: 
-<​hidden>​ +  * fișierele obiect corespunzătoare fiecărui fișier sursă 
-A: Sisteme cu câteva sute de MB (single threaded=durează mult) sau când avem un singur CPU+  * biblioteca dinamică ''​libA.so''​ ce implementează funcția ''​function''​ în fișierul ''​a.c''​ 
-</​hidden>​+  * biblioteca dinamică ''​libB.so''​ ce implementează funcția ''​function''​ în fișierul ''​b.c''​ 
 +  * executabilul ''​exe12''​ compilat astfel ''​gcc main.o -lA -lB -L. -o exe12''​ 
 +  * executabilul ''​exe21''​ compilat astfel ''​gcc main.o -lB -lA -L. -o exe21''​ 
 +  * executabilul ''​exe-static''​ compilat astfel ''​gcc main.o c.o -lA -lB -L. -o exe-static''​
  
-== Parallel GC == +Setați variabila ​de mediu [[http://​ocw.cs.pub.ro/​courses/​cpl/​labs/​12?&#​variabile_de_mediu_folosite_de_linker|LD_LIBRARY_PATH]] corespunzătorpentru ca linkerul să găsească bibliotecile generate în directorul curent.
-În acest caz, se folosește MarkCopy pentru generația Young șMarkSweepCompact pentru generația Old. De asemenea tipul de colectare este stop-the worldOperațiile de MarkCopy, Compact sunt însă executate pe mai multe threaduri.+
  
-//Q: Când ar fi utilă această combinație?// +Executabilul ''​exe12''​ a fost generat linkând 2 biblioteci dinamice ce implementează funcția ''​function''​. Care este ordinea bibliotecilor dinamice în linia de comandăRulați binarul pentru ​observa outputul produs.
-<​hidden>​ +
-A: Sisteme multicore, atunci când latența nu e importantă. +
-</​hidden>​+
  
 +Executabilul ''​exe21''​ a fost generat linkând 2 biblioteci dinamice ce implementează funcția ''​function''​. Care este ordinea bibliotecilor dinamice în linia de comandă? Rulați binarul pentru a observa outputul produs.
  
-== Concurrent Mark and Sweep == +Executabilul ''​exe-static''​ a fost generat linkând 2 biblioteci dinamice ​și un fișier obiect ce implementează funcția ''​function''​În ce ordine au fost specificate în linia de comandă? Rulați binarul pentru a observa outputul produs.
-Numele oficial este de fapt "​Mostly Concurrent Mark and Sweep Garbage Collector"​. +
-Folosește un algoritm MarkCopy paralel și stop-the world pentru generația Young și un algoritm "​aproape concurent"​ de MarkSweep pentru generația OldPentru a evita pauzele lungi generate ​de colectarea generației Old, acest algoritm nu execută operația de compactare în această regiune șfolosește "​free-lists"​. De asemenea, majoritatea operațiilor din fazele de Mark și Sweep sunt făcute concurent cu aplicația.+
  
-//Q: Când ar fi utilă această combinație?//​ 
-<​hidden>​ 
-A: Când latența e importantă. 
-</​hidden>​ 
  
-== Garbage First GC ==+===== Exercițiul 3 - instrumentarea binarelor (3p) =====
  
-G1 este un GC destinat mașinilor multiprocesor și memorie ​de dimensiune mareÎși propune ​să ofere, cu probabilitate ridicată, pauze //Stop the world// care se încadrează în parametrii specificați, fără însă a sacrifica high performanța(//​throughput//​). G1 atinge acest scop prin câteva tehnici.+Presupunem că avem un set de binare ale unei aplicații/​biblioteci ale cărei surse nu le mai avem (de exam cumpărat licența pentru o bibliotecă proprietară ​și compania care producea acea bibliotecă s-a închis). Dorim să modificăm aplicația/biblioteca, fără a modifica acele binare. :)
  
-Heap-ul este partiționat în regiuni cu aceași dimensiune. iar G1 încearcă ​să colecteze mai întâi regiunile care au cel mai mult spațiu liber. Etapa de Marking este concurentă și în timpul ei G1 colectează statistici despre spațiul liber al fiecărei regiuni. În etapa următoare, G1 încearcă să colecteze mai întâi regiunile aproape goale (de unde și denumirea lui). +Neavând acces la surse putem spune linkerului ​să routeze toate apelurile către un anumit simbol către o funcție specială aflată sub controlul nostruDacă se trimite argumentul ''​--wrap=symbol''​ linkerului acesta: 
-În etapa următoare, G1 încearcă să copieze dintr-una sau mai multe regiuni obiecte într-o singură regiune, pentru a compacta și elibera memorie în același timp. Aceast proces este realizat în paralel, pe mai multe procesoare. Astfel, ​cu fiecare GC, G1 încearcă să reducă fragmentarea într-o manieră incrementală.+  * va înlocui toate referințele către simbolul ''​symbol'' ​cu referințe către ''​%%__%%wrap_symbol''​ 
 +  * va genera un simbol ''​%%__%%real_symbol''​ care va trimite către varianta originală a lui ''​symbol''​
  
-G1 nu oferă garanții //hard// pentru durata unui //Stop the world//, dar încearcă să atingă durata configurată cu probabilitate mareFace acest lucruprin actualizarea de statistici în timpul unui GC. Astfel, din procesel de GC precedente, putem estima câte regiuni pot fi colectate în fereastra de timp disponibilă.+Intrați în directorul ''​3-wrap''​. Vizualizați fișierul ''​main.c''​. Programul ​încearcă să scrie un mesaj în fișierul ''/​bad/​path''​ și eșuează din lipsa permisiunilor și a existenței directoarelor superioarePentru a compila fișierulrulați comanda ''​make default''​ și rulați executabilul ''​exe_default''​.
  
-G1 este un GC paralel, incremental ​și "​aproape"​ concurentcare compactează spațiul liberScopul lui este să înlocuiască CMS și să ofere pauze //Stop the world// predictibilefără însă a sacrifica performanța globală.+**Fără a modifica fișierul ''​main.c''​**,​ vețsuprascrie funcția ''​open''​ așa cum am descris mai susastfel încât să obțineți un file descriptor validImplementarea o veți realiza în cadrul fișierului ''​wrap.c''​. Pentru a compila, rulațcomanda ''​make wrapped''​apoi rulați executabilul ''​exe_wrapped''​.
  
-== Linkuri Utile ==+<note tip> **Hint**: man ld, apoi căutați --wrap </​note>​ 
 +===== Exercițiul 4 - Linker Command Language (2p) =====
  
-[[https://​www.youtube.com/​watch?​v=lt1cVudGKA8|Understanding Java Garbage Collection and what you can do about it]]+<​note>​  
 +Dacă lucrați pe calculatoarele proprii și acestea sunt pe 64 de biți, instalați gcc-multilib pentru a putea compila cross-platform: 
 +**sudo apt-get install gcc-multilib** 
 +</note>
  
-[[http://www.azulsystems.com/sites/default/files/images/​Understanding_Java_Garbage_Collection_v3.pdf|Understanding Java Garbage Collection and what you can do about it(presentation)]]+Consultați secțiunea ​[[http://ocw.cs.pub.ro/courses/cpl/labs/11?&#​linker_command_language|Linker Command Language]] din laborator.
  
-[[https://www.youtube.com/watch?v=we_enrM7TSY|Are Your Garbage Collection Logs Speaking to You?]]+Intrați în directorul ''​4-lcl''​. Aveți definit în cadrul fișierului ''​def.h''​ un macro care primește numele unei funcții și generează un pointer la funcție și-l pune în secținuea ''​.cpl''​: 
 +<code c> 
 +// def.
 +#define MODULE_INIT(X) \ 
 +  static void * __ptr_init_cpl_##​X __attribute__ ((section (".cpl"​))) ​&X; 
 +</​code>​ 
 +De asemenea, în director veți găsi un set de fișiere cu funcții de inițializare pentru care s-a apelat macro-ul de mai sus.
  
-[[http://​wiki.apache.org/​tomcat/​MemoryLeakProtection|Memory Leaks in Java]]+Practic, în secțiunea ''​.cpl''​ se vor adăuga pointeri către toate funcțiile.
  
-[[https://plumbr.eu/handbook/gc-tuning-in-practice|GC tuning in practice]]+Modificați scriptul de linker din cadrul directorului pentru a introduce o secțiune de date specială ''​.cpl''​ care să adune secțiunile ''​.cpl''​ din fiecare fișier obiect de intrare, conform **TODO-ului** din script (revedeți secțiunea ​[[http://ocw.cs.pub.ro/courses/cpl/​labs/​11#​provide|PROVIDE]]). Apoi, în cadrul fișierului ''​main.c'',​ exportați două simboluri pentru începutul și sfârșitul acestei secțiuni și iterați prin toți pointerii, apelând pe rând funcția către care aceștia trimit (puteți pasa orice întreg ca parametru). 
 +===== BONUS =====
  
-[[http://​psy-lob-saw.blogspot.ro/​2014/​10/​the-jvm-write-barrier-card-marking.html|The JVM Write Barrier - Card Marking]]+Scopul acestui exercițiu este de a suprascrie o funcție de bibliotecă (similar ca la exercițiul 3).
  
-[[http://​mechanical-sympathy.blogspot.ro/​2013/​07/​java-garbage-collection-distilled.html|Java Garbage Collection Distilled]]+Implementați acest lucru folosindu-vă de variabila de mediu ''​LD_PRELOAD''​.
  
-[[http://​dept.cs.williams.edu/​~freund/​cs434/​hotspot-gc.pdf|Garbage Collection in the Java HotSpot Virtual Machine]] 
  
-[[http://​www.infoq.com/​articles/​G1-One-Garbage-Collector-To-Rule-Them-All|G1:​ One Garbage Collector To Rule Them All]]+Refaceți exercițiul 2 fără a folosi variabila de mediu ''​LD_LIBRARY_PATH''​
  
-[[http://citeseerx.ist.psu.edu/viewdoc/download?​doi=10.1.1.63.6386&​rep=rep1&​type=pdf|Garbage-First Garbage Collection]]+Folosiți-vă de [[https://en.wikipedia.org/wiki/Rpath|run-time search path]]
 +Citiți secțiunea ''​GNU ld.so''​ pentru a înțelege algoritmul aplicat de linker pentru a căuta biblioteci dinamice.
  
cpl/labs/12.1452542675.txt.gz · Last modified: 2016/01/11 22:04 by sorin.baltateanu
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