Differences

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

Link to this comparison view

so:laboratoare:laborator-08 [2019/03/31 11:42]
bogdan.purcareata [Suport POSIX]
so:laboratoare:laborator-08 [2022/05/02 17:25] (current)
daniel.dosaru [Tipuri de fire de execuție]
Line 1: Line 1:
-====== Laborator 08 - Thread-uri ​Linux ====== +====== Laborator 08 - Threaduri ​Linux ======
-===== Materiale ajutătoare ​=====+
  
-  *[[http://​elf.cs.pub.ro/​so/​res/​laboratoare/​lab08-slides.pdf | lab08-slides.pdf]] 
-  *[[http://​elf.cs.pub.ro/​so/​res/​laboratoare/​lab08-refcard.pdf | lab08-refcard.pdf]] 
  
 ==== Nice to read ==== ==== Nice to read ====
Line 10: Line 7:
   * TLPI - Chapter 30, Threads: Thread Synchronization   * TLPI - Chapter 30, Threads: Thread Synchronization
   * TLPI - Chapter 31, Threads: Thread Safety and Per-Thread Storage   * TLPI - Chapter 31, Threads: Thread Safety and Per-Thread Storage
 +
 +===== Link-uri către secțiuni utile =====
 +
 +==== Linux ====
 +  * [[#​suport_posix|Fire de execuție - creare, așteptare, terminare]]
 +  * [[#​sincronizarea_firelor_de_executie|Sincronizarea firelor de execuție]]
 +  * [[#​thread_specific_data_tsd|Thread Specific Data (TSD)]]
 +  * [[#​mutex|Mutex-uri]]
 +  * [[#​futex-uri|Futex-uri]]
 +  * [[#​semafor|Semafoare]]
 +  * [[#​variabile_conditie|Variabile condiție]]
 +  * [[#​bariera|Bariere]]
 +
 ===== Prezentare teoretică ===== ===== Prezentare teoretică =====
  
-În laboratoarele anterioare a fost prezentat conceptul de **proces**, acesta fiind unitatea elementară de alocare a resurselor utilizatorilor. În cadrul acestui laborator este prezentat conceptul de **fir de execuție** (sau **thread**),​ acesta fiind unitatea elementară de planificare într-un sistem. Ca și procesele, firele de execuție reprezintă un mecanism prin care un calculator poate sǎ ruleze mai multe task-uri simultan.+În laboratoarele anterioare a fost prezentat conceptul de **proces**, acesta fiind unitatea elementară de alocare a resurselor utilizatorilor. În cadrul acestui laborator este prezentat conceptul de **fir de execuție** (sau **thread**),​ acesta fiind unitatea elementară de planificare într-un sistem. Ca și procesele, firele de execuție reprezintă un mecanism prin care un calculator poate să ruleze mai multe task-uri simultan.
  
-Un fir de execuție există în cadrul unui proces, și reprezintă o unitate de execuție mai fină decât acesta. În momentul în care un proces este creat, în cadrul lui există un singur fir de execuție, care execută programul secvențial. Acest fir poate la rândul lui sǎ creeze alte fire de execuție; aceste fire vor rula porțiuni ale binarului asociat cu procesul curent, posibil aceleași cu firul inițial (care le-a creat).+Un fir de execuție există în cadrul unui proces, și reprezintă o unitate de execuție mai fină decât acesta. În momentul în care un proces este creat, în cadrul lui există un singur fir de execuție, care execută programul secvențial. Acest fir poate la rândul lui să creeze alte fire de execuție; aceste fire vor rula porțiuni ale binarului asociat cu procesul curent, posibil aceleași cu firul inițial (care le-a creat).
 ==== Diferențe dintre fire de execuție și procese ==== ==== Diferențe dintre fire de execuție și procese ====
  
Line 27: Line 37:
     *stivă     *stivă
     *set de registre (deci și un contor de program - registrul ''​(E)IP''​)     *set de registre (deci și un contor de program - registrul ''​(E)IP''​)
 +    *spaţiu de stocare local (EN: thread-local storage - [[https://​gcc.gnu.org/​onlinedocs/​gcc/​Thread-Local.html|TLS]])
  
 Procesele sunt folosite de SO pentru a grupa și aloca resurse, iar firele de execuție pentru a planifica execuția de cod care accesează (în mod partajat) aceste resurse. Procesele sunt folosite de SO pentru a grupa și aloca resurse, iar firele de execuție pentru a planifica execuția de cod care accesează (în mod partajat) aceste resurse.
Line 58: Line 69:
  
 __Dezavantaje__ : __Dezavantaje__ :
-  *comutarea contextului este efectuată de kernel (cu o viteză de comutare mai mică):+  *comutarea contextului este efectuată de kernel (cu o viteză de comutare mai mare):
     *se trece dintr-un fir de execuție în kernel     *se trece dintr-un fir de execuție în kernel
     *kernelul întoarce controlul unui alt fir de execuție.     *kernelul întoarce controlul unui alt fir de execuție.
Line 1011: Line 1022:
  
  
-====== Exerciţii ​de laborator ​======+====== Exerciţii ======
  
-===== Exercițiul 0 Joc interactiv (2p) =====+<note important>​ 
 +În cadrul laboratoarelor vom folosi repository-ul de git al materiei SO - https://​github.com/​systems-cs-pub-ro/​so. Va trebui sa clonați repository-ul pe masinile virtuale folosind comanda: ''​git clone https://​github.com/​systems-cs-pub-ro/​so''​. Dacă doriți să descărcați repositoryul în altă locație, folosiți comanda ''​git clone https://​github.com/​systems-cs-pub-ro/so ${target}''​.
  
-  * Detalii desfășurare [[http://​ocw.cs.pub.ro/courses/​so/​meta/​notare#​joc_interactiv|joc]].+Pentru a actualiza repository-ul,​ folosiți comanda ''​git pull origin master''​ din interiorul directorului în care se află repository-ulRecomandarea este să îl actualizați cât mai frecvent, înainte să începeți lucrul, pentru a vă asigura că aveți versiunea cea mai recentăÎn cazul în care gitul detectează conflicte la nivelul vreunui fişier, folosiți următoarele comenzi pentru a vă păstra modificările:​ 
 +<​code>​ 
 +git stash 
 +git pull origin master 
 +git stash pop 
 +</code>
  
-===== Linux (9p) =====+Pentru mai multe informații despre folosirea utilitarului git, urmați ghidul de la https://​gitimmersion.com. 
 +</​note>​
  
-În rezolvarea laboratorului folosiți arhiva de sarcini [[http://​elf.cs.pub.ro/so/​res/​laboratoare/​lab08-tasks.zip | lab08-tasks.zip]]+<note tip> 
 +Pentru a vă ajuta la implementarea exercițiilor din laborator, în directorul ''​utils''​ din arhivă există un fișier ''​utils.h''​ cu funcții utile. 
 +</note>
  
-<note tip> Pentru a vă ajuta la implementarea exercițiilor din laborator, în directorul ''​utils''​ din arhivă există un fișier ''​utils.h''​ cu funcții utile. </​note>​+===== Linux =====
  
 <​note>​Pentru a instala paginile de manual pentru '​pthreads'​ <​note>​Pentru a instala paginile de manual pentru '​pthreads'​
Line 1027: Line 1047:
 </​note>​ </​note>​
  
-==== Exercițiul 1 - Thread Stack (1p) ====+==== Exercițiul 1 - Thread Stack ====
  
 Intrați în directorul ''​1-th_stack''​ și inspectați sursa, apoi compilați și rulați programul. Urmăriți cu ''​pmap''​ sau folosind ''​procfs''​ cum se modifică spațiul de adresă al programului:​ Intrați în directorul ''​1-th_stack''​ și inspectați sursa, apoi compilați și rulați programul. Urmăriți cu ''​pmap''​ sau folosind ''​procfs''​ cum se modifică spațiul de adresă al programului:​
Line 1035: Line 1055:
 </​code> ​ </​code> ​
  
-Zonele de memorie cu dimensiunea de 8MB (8192KB) care se creează după fiecare apel ''​pthread_create''​ reprezintă noile //stive// alocate de către biblioteca ''​libpthread''​ pentru fiecare thread în parte. Observați că, în plus, se mai mapează ​de fiecare dată o pagină (4KB) cu protecția ''​%%---p%%''​ (PROT_NONE, private ​- vizibil în ''​procfs''​) care are rolul de %%"​%%pagină de gardă%%"​%%. ​+Zonele de memorie cu dimensiunea de 8MB (8192KB) care se creează după fiecare apel ''​pthread_create''​ reprezintă noile //stive// alocate de către biblioteca ''​libpthread''​ pentru fiecare thread în parte. Observați că, în plus, se mapează ​între paginile alocate stivelor thread-urilor (8192K) câte o pagină (4KB) cu protecția ''​%%----- [anon] ​%%''​ (PROT_NONE - vizibil în ''​procfs''​) care are rolul de %%"​%%pagină de gardă%%"​%%. ​
  
 Motivul pentru care nu se termină programul este prezența unui ''​​while(1)''​​ în funcția thread-urilor. Folosiți ''​Ctrl+C''​ pentru a termina programul. ​ Motivul pentru care nu se termină programul este prezența unui ''​​while(1)''​​ în funcția thread-urilor. Folosiți ''​Ctrl+C''​ pentru a termina programul. ​
-==== Exercițiul 2 - Fire de execuție vs Procese ​(1p) ====+==== Exercițiul 2 - Fire de execuție vs Procese ====
  
 Intrați în directorul ''​2-th_vs_proc''​ și inspectați sursele. Ambele programe simulează un server care creează fire de execuție/​procese. Compilați și rulați pe rând ambele programe. ​ Intrați în directorul ''​2-th_vs_proc''​ și inspectați sursele. Ambele programe simulează un server care creează fire de execuție/​procese. Compilați și rulați pe rând ambele programe. ​
Line 1049: Line 1069:
 </​code>​ </​code>​
  
-Verificați ce se întâmplă dacă la un moment dat un fir de execuție ​ moare (sau un proces, în funcție de ce executabil testați). Testați utilizând funcția ''​do_bad_task''​ la fiecare al 4-lea fir de execuție/​process. +Verificați ce se întâmplă dacă la un moment dat un fir de execuție moare (sau un proces, în funcție de ce executabil testați). Testați utilizând funcția ''​do_bad_task''​ la fiecare al 4-lea fir de execuție/​process. ​Observaţi cum încheierea anormală a unui thread duce la încheierea procesului din care face parte.  
-==== Exercițiul 3 - Thread safety (1p) ====+ 
 +==== Exercițiul 3 - Generator de numere pseudoaleatoare ​====
  
 <note important>​ <note important>​
-Datorită ​faptului că mașina virtuală ​''​spook'' ​are un singur core virtual, exercițiul următor trebuie realizat pe mașina fizică pentru a permite mai multor thread-uri să ruleze în același moment de timp.+Din cauza faptului că mașina virtuală are un singur core virtual, exercițiul următor trebuie realizat pe mașina fizică pentru a permite mai multor thread-uri să ruleze în același moment de timp.
 </​note>​ </​note>​
  
-Intrați în directorul ''​3-safety''​ și inspectați sursa ''​vars.c''​. Funcțiile ''​thread_function''​ și ''​main''​ **NU** sunt thread-safe relativ la variabilele ''​a''​ și ''​b''​ (revedeți semnificația lui ''​[[http://​en.wikipedia.org/​wiki/​Thread_safety | thread safety]]''​). Există o [[https://​en.wikipedia.org/​wiki/​Race_condition | condiție de cursă]] între cele două thread-uri create la incrementarea variabilei ''​b'',​ declarată în funcția ''​thread_function'',​ și o altă condiție de cursă între toate thread-urile procesului la incrementarea variabilei globale ''​a''​. ​Datorită introducerii artificiale a apelurilor ''​sleep'',​ manifestarea condițiilor de cursă poate fi diminuată (dar nu **eliminată**). ​  +Intrați în directorul ''​3-safety''​ și inspectați sursa ''​malloc.c''​. Funcțiile ''​thread_function''​ și ''​main''​ **NU** sunt thread-safe relativ la variabilele ''​global_storage''​ și ''​function_global_storage''​ (revedeți semnificația lui ''​[[http://​en.wikipedia.org/​wiki/​Thread_safety | thread safety]]''​). Există o [[https://​en.wikipedia.org/​wiki/​Race_condition | condiție de cursă]] între cele două thread-uri create la incrementarea variabilei ''​function_global_storage'',​ declarată în funcția ''​thread_function'',​ și o altă condiție de cursă între toate thread-urile procesului la incrementarea variabilei globale ''​global_storage''​.
 <note tip> <note tip>
 Un utilitar foarte folositor este ''​helgrind'',​ care poate detecta automat aceste condiții de cursă. Îl putem folosi în cazul nostru așa: Un utilitar foarte folositor este ''​helgrind'',​ care poate detecta automat aceste condiții de cursă. Îl putem folosi în cazul nostru așa:
 <code bash> <code bash>
-valgrind --tool=helgrind ./vars+valgrind --tool=helgrind ./mutex
 </​code>​ </​code>​
 </​note>​ </​note>​
  
-Observați ce se întâmplă cu memoria alocată pentru variabila ''​rez''​ după ce se face join. Folosiți ''​valgrind''​ pentru a investiga:​ +== Sincronizare acces variabile ==
-<code bash> +
-valgrind --leak-check=full ./vars +
-</​code>​+
  
-În fișierul ''​malloc.c''​ se creează ''​NUM_THREADS''​ thread-uri care alocă ​memorie în ''​NUM_ROUNDS'' ​runde. Sunt șanse mari ca thread-urile să execute apeluri ''​malloc''​ concurente. După compilare ​și rulare ​de mai multe ori, observăm că programul rulează cu succes. Pentru a face verificări suplimentare,​ rulăm din nou ''​helgrind'':​+Rezolvați comentariile ''​TODO1''​ din ''​malloc.c''​. Pentru a rezolva aceste două condiții de cursă apelați functia ''​increase_numbers''​ intr-un mod thread safe cu ajutor API-ului pus la dispozitie de ''​critical.h''​. De asemenea, sincronizați accesul în funcția ''​print_stats''​ (**de ce?**). 
 + 
 +Pentru testare, folosiți executabilul ''​mutex''​. 
 + 
 +În fișierul ''​malloc.c''​ se creează ''​NUM_THREADS''​ thread-uri care alocă ​o matrice de ''​NUM_ROWS X NUM_COLUMNS'' ​întregi. Sunt șanse mari ca thread-urile să execute apeluri ''​malloc''​ concurente. 
 +  
 +După ce ați rezolvat ''​TODO1'',​ compilați ​și rulați ​de mai multe ori. Observăm că programul rulează cu succes. Pentru a face verificări suplimentare,​ rulăm din nou ''​helgrind'':​
  
 <code bash> <code bash>
-valgrind --tool=helgrind ./malloc+valgrind --tool=helgrind ./mutex
 </​code>​ </​code>​
  
-Observăm că nici ''​helgrind''​ nu raportează vreo eroare, lucru care conduce la faptul că funcția ''​malloc''​ ar fi thread-safe. Pentru a putea fi siguri trebuie să consultăm paginile de manual și codul sursă.+Observăm că nici ''​helgrind''​ nu raportează vreo eroare, lucru care conduce la faptul că funcția ''​malloc''​ ar fi thread-safe. ​(chiar daca acesta nu este protejat de API-ul pus la dispoziție). 
 +  
 +Pentru a putea fi siguri trebuie să consultăm paginile de manual și codul sursă.
  
 <note important>​ <note important>​
Line 1084: Line 1109:
 </​note>​ </​note>​
  
-==== Exercițiul 4 - Parallel fgrep (1.5p) ​====+== Spinlocks == 
 + 
 +''​TODO 2'':​ Implementati un spinlock folosindu-va de operatii atomice. 
 +Operatiile atomice existente in standardul GCC le gasiti la [[https://​gcc.gnu.org/​onlinedocs/​gcc/​_005f_005fatomic-Builtins.html | __atomic functions]] 
 + 
 +In fisierul ''​critical.c''​ trebuie sa completati in dreptul comentarilor asociate ''​TODO 2'',​ avand la dispozitie hint-uri. 
 + 
 +Pentru testare, folosiți executabilul ''​spin''​. 
 + 
 +<​note>​ 
 +Aici, la verificarea corectitudinii nu vă mai poate ajuta ''​helgrind''​ pentru că nu are implementat suportul nativ. Există adnotări cu care poate fi ajutat să nu dea false positives, dar pentru asta e nevoie sa compilați și să linkați o versiune modificată de helgrind. Detalii [[http://​valgrind.org/​docs/​manual/​hg-manual.html|aici,​ secțiunea 7.5]]. 
 +</​note>​ 
 +==== Exercițiul 4 - Parallel fgrep ====
  
 <note important>​ <note important>​
-Datorită ​faptului că mașina virtuală ​''​spook'' ​are un singur core virtual, exercițiul următor trebuie realizat pe mașina fizică pentru a permite mai multor thread-uri să ruleze în același moment de timp.+Din cauza faptului că mașina virtuală are un singur core virtual, exercițiul următor trebuie realizat pe mașina fizică pentru a permite mai multor thread-uri să ruleze în același moment de timp.
 </​note>​ </​note>​
  
Line 1110: Line 1147:
 </​code>​ </​code>​
  
-==== Exercițiul 5 - Blocked ​(1.5p) ​====+==== Exercițiul 5 - Blocked ====
  
  
Line 1124: Line 1161:
 Așa cum ne arată și ''​helgrind'',​ problema constă în faptul că cele două thread-uri iau cele două mutex-uri în ordinea inversă, situație foarte probabilă în a cauza un [[https://​en.wikipedia.org/​wiki/​Deadlock|deadlock]]. Așa cum ne arată și ''​helgrind'',​ problema constă în faptul că cele două thread-uri iau cele două mutex-uri în ordinea inversă, situație foarte probabilă în a cauza un [[https://​en.wikipedia.org/​wiki/​Deadlock|deadlock]].
 </​note>​ </​note>​
-==== Exercițiul 6 - Implementare comportament pthread_once (1p)====+==== Exercițiul 6 - Single time start / stop functions ​==== 
 + 
 +Aveți un număr de thread-uri, și două funcții: 
 +  * O funcție de ''​init''​ apelată de toate thread-urile,​ dar rulată doar de primul care ajunge să execute. 
 +  * O funcție de ''​deinit''​ apelată de toate thread-urile,​ dar rulată doar de ultimul care ajunge să execute. 
 + 
 +Pornind de la sursa ''​once.c''​ din directorul ''​6-once'',​ asigurați-vă că funcțile ''​init_func''​ / ''​deinit_func''​ sunt apelate o singură dată, după scenariul descris mai sus. **Nu** aveți voie să modificați codul în afara funcțiilor marcate cu ''​TODO''​. 
 + 
 +Thread-urile apelează o funcție ''​thread_func''​ care printează câte un caracter la consolă. 
 +  * Ce se întâmplă dacă **nu** se mai face ''​fflush(stdout)''​ după fiecare printare?
  
-Aveți o funcție de inițializare pe care vreți să o apelați o singură dată. Pornind de la sursa ''​once.c'' ​din directorul ''​6-once'',​ asigurați-vă că funcția ​''​init_func''​ este apelată o singură dată. **Nu** aveți voie să modificați funcția ''​init_func''​ sau să folosiţi ''​pthread_once''​.+Thread-urile apelează ''​usleep(5000)'' ​pentru a încuraja invocarea scheduler-ului. 
 +  * Ce se întâmplă dacă perioada de ''​usleep''​ este micșorată?
  
-Citiți despre funcționalitatea [[http://​linux.die.net/​man/​3/​pthread_once | pthread_once]] și revedeți secțiunea despre [[#mutex | mutex]].+Revedeți secțiunea despre [[#mutex | mutex]].
  
-==== Exercițiul 7 - Producător - Consumator ​(2p) ====+==== Exercițiul 7 - Producător - Consumator ====
  
 Intrați în directorul ''​7-prodcons''​. Completați //​TODO//​-urile din cod pentru a implementa sincronizarea unui producător cu un consumator, ce folosesc în comun un buffer. Intrați în directorul ''​7-prodcons''​. Completați //​TODO//​-urile din cod pentru a implementa sincronizarea unui producător cu un consumator, ce folosesc în comun un buffer.
Line 1142: Line 1189:
 ==== BONUS ==== ==== BONUS ====
  
-=== 1 so karma - fork vs pthread_create ===+=== fork vs pthread_create ===
  
 Vrem să aflăm ce apeluri de sistem sunt realizate în urma apelurilor funcțiilor ''​fork''​ și ''​pthread_create''​. Vrem să aflăm ce apeluri de sistem sunt realizate în urma apelurilor funcțiilor ''​fork''​ și ''​pthread_create''​.
Line 1160: Line 1207:
 Observați alocarea unei stive separate pentru noul thread (argumentul ''​child_stack''​) cât și partajarea resurselor procesului cu acesta (''​flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND''​). Observați alocarea unei stive separate pentru noul thread (argumentul ''​child_stack''​) cât și partajarea resurselor procesului cu acesta (''​flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND''​).
  
-=== 1 so karma - Thread Specific Data ===+=== Thread Specific Data ===
  
 Fișierul ''​9-tsd/​tsd.c''​ conține o aplicație ce împarte un task între mai multe fire de execuție. Fiecare fir de execuție are un fișier de log în care va înregistra mesaje despre progresul său. Observați următoarele aspecte: Fișierul ''​9-tsd/​tsd.c''​ conține o aplicație ce împarte un task între mai multe fire de execuție. Fiecare fir de execuție are un fișier de log în care va înregistra mesaje despre progresul său. Observați următoarele aspecte:
Line 1169: Line 1216:
  
  
-=== 1 so karma - Mutex vs Spinlock ===+=== Mutex vs Spinlock ===
  
 Dorim să testăm care varianta este mai eficientă pentru a proteja incrementarea unei variabile. Dorim să testăm care varianta este mai eficientă pentru a proteja incrementarea unei variabile.
Line 1193: Line 1240:
  
 */ */
-===== Soluții ===== 
  
-[[http://​elf.cs.pub.ro/​so/​res/​laboratoare/​lab08-sol.zip | lab08-sol.zip]] 
 ===== Resurse utile ===== ===== Resurse utile =====
  
so/laboratoare/laborator-08.1554021734.txt.gz · Last modified: 2019/03/31 11:42 by bogdan.purcareata
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