Table of Contents

Planificator de threaduri

  • Deadline: 15.12.2022, ora 23:55

Obiectivele temei

Enunț

Să se implementeze un planificator de threaduri care va controla execuția acestora în user-space. Acesta va simula un scheduler de procese preemptiv, într-un sistem uniprocesor, care utilizează un algoritm de planificare Round Robin cu priorități.

Implementarea planificatorului de threaduri se va face într-o bibliotecă partajată dinamică, pe care o vor încărca thread-urile ce urmează să fie planificate. Aceasta va trebui să exporte următoarele funcții:

Pe lângă implementarea propriu-zisă a funcțiilor de mai sus, va trebui să asigurați și planificarea/execuția corectă a threadurilor, conform algoritmului Round Robin cu priorități. Fiecare thread trebuie să ruleze în contextul unui thread real din sistem. Fiind un planificator pentru sisteme uniprocesor, un singur thread va putea rula la un moment dat.

Descriere detaliată a funcționării planificatorului

Timp execuție:

Într-un sistem real, pentru controlul execuției, contorizarea timpului de rulare a unui proces se realizează la fiecare întrerupere de ceas.

Pentru facilitarea implementării, modelul temei va simula un sistem real astfel:

  1. sistemul simulat va folosi un timp virtual (logic), independent de cel real pentru a contoriza timpul de rulare pe procesor.
  2. veți considera că o instructiune durează o singură perioadă de ceas (unitate de timp logic).
  3. fiecare din funcțiile prezentate mai sus reprezintă o singură instructiune ce poate fi executată de un thread la un moment dat.

Evenimente și I/O

Threadurile din sistem se pot bloca în așteptarea unui eveniment sau a unei operații de I/O. Un astfel de eveniment va fi identificat printr-un id (0 - nr_events). Numărul total de evenimente (care pot apărea la un moment dat în sistem) va fi dat ca parametru la inițializarea planificatorului.

Un thread se blochează în urma apelului funcției wait (ce primește ca parametru id-ul/indexul evenimentului/dispozitivului) și este eliberat atunci când un alt thread apelează funcția signal pe același eveniment/dispozitiv. Signal trezește toate threadurile care așteapta un anumit eveniment.

Atenție, un thread care a apelat funcția wait, indiferent de prioritate, nu poate fi planificat decât după ce este trezit de un alt thread.

Round Robin cu priorități

instruction()
{
    do work
    check scheduler
    if (preempted)
        block();
    return;
}

În momentul în care threadul a fost planificat pe procesor, se va executa secvența de cod de mai sus. Funcția “do work” va fi executată imediat ce threadul este planificat. Este nevoie să verificăm dacă threadul a fost preemptat, iar acest lucru se verifică in funcția “check scheduler”. Dacă “check scheduler” va semnala că threadul a fost preemptat, atunci acesta se va bloca până când va fi planificat din nou.

Prin urmare, cazurile în care threadul curent este preemptat și un alt thread începe rularea sunt:

Pentru mai multe detalii despre algoritmi de planificare și fire de execuție puteți consulta aici sau aici.

Stări threaduri:

Stările prin care poate trece un thread sunt:

Pentru o mai bună întelegere a algoritmilor de planificare, se recomandă urmărirea tranzițiilor dintre stări ca în desenul de mai jos.

Detalii implementare instrucțiuni

Funcțiile care trebuie exportate de planificator, alături de parametrii fiecăruia, sunt detaliate mai jos:

În mod normal, so_fork va fi apelată din contextul unui alt thread din sistem. Se garantează faptul că va exista întotdeauna cel puțin un thread ce poate fi planificat, pe întreg parcursul rulării planificatorului. Excepție face cazul primului so_fork ce va crea primul thread din sistem și va fi apelat din contextul testelor, neavând ca părinte un thread din sistemul simulat.

Un exemplu de model de implementare a funcției fork, ar putea fi folosirea unei funcții suplimentare (ex start_thread) care să determine contextul în care se va executa noul thread (handlerul primit ca parametru) ca în descrierea de mai jos:

so_fork(handler, prio)
-> initializare thread struct
-> creare thread nou ce va executa functia start_thread
-> asteaptă ca threadul să intre în starea READY/RUN
-> returnează id-ul noului thread

start_thread(params)
-> asteaptă să fie planificat
-> call handler(prio)
-> iesire thread

Atenție: Funcția se va întoarce abia după ce noul thread creat fie a fost planificat fie a intrat în starea READY.

Exemplu execuție

= Thread 0 = = Thread 1 = = Thread 2 = = Thread 3 =
exec exec exec exec
fork(thr2, 2) signal(3) wait(3) exec
fork(thr1, 1) fork(thr3, 1) exec
exec exec
exec
exec

În exemplul de mai sus, threadul 0 are prioritatea 0. Acesta pornește threadurile 1 și 2 cu prioritățile asociate. Threadul 2 se blochează așteptând evenimentul cu id-ul 3, eveniment ce va fi semnalat de threadul 1. Cuanta de rulare pe procesor este 3. În final, instrucțiunile se vor executa în următoarea ordine:

T0 exec
T0 forks T2, prio 2
--> T2 preempts T0 (prio(T2) > prio(T0))
T2 exec
T2 waits for event 3
--> T2 blocks and is preempted
T0 forks T1, prio 1
--> T1 preempts T0
T1 exec
T1 signals event 3
--> T2 is woken up and preempts T1
T2 exec
--> T2 finished
T1 forks T3, prio 1
T1 exec
T1 exec
--> T1 is preempted, its CPU quantum expired
T3 exec
T3 exec
--> T3 finished
T1 exec
--> T1 finished
T0 exec
--> T0 finished

Precizări

Identificatorul întors de funcția so_fork trebuie să fie structura pthread_t populată de funcția POSIX pthread_create().

Tema se va rezolva folosind doar funcții POSIX. Se pot folosi de asemenea și funcțiile de formatare printf, scanf, funcțiile de alocare de memorie malloc, free, și funcțiile de manipulare a șirurilor de caractere (strcat, strdup, etc.)

Tema se va rezolva folosind fire de execuție POSIX și exclusiv mecanisme de sincronizare a firelor de execuție POSIX (mutex, variabile de condiție, semafoare și oricare altele suportate de API-ul POSIX).

Utile

Testare

Înainte de a uploada tema, asigurați-vă că implementarea voastră trece testele pe mașinile virtuale. Dacă apar probleme în rezultatele testelor, acestea se vor reproduce și pe vmchecker. Nu folosiți vmchecker pentru testare continuă!

Notare

Nota maximă este de 100p.

Depunctări suplimentare:

Depunctările nu au caracter obligatoriu pentru orice greșeală (oricât de mică) și nu se aplică de mai multe ori. O eventuală depunctare va apărea o singură dată chiar dacă în codul vostru „problema” apare de mai multe ori

Materiale ajutătoare suplimentare

Cursuri:

Laboratoare:

FAQ

Suport, întrebări și clarificări

Pentru întrebări sau nelămuriri legate de temă folosiți forumul temei.

Orice intrebare pe forum trebuie să conțină o descriere cât mai clară a eventualei probleme. Întrebări de forma: “Nu merge X. De ce?” fără o descriere mai amănunțită vor primi un răspuns mai greu. Înainte să postați o întrebare pe forum citiți și celelalte întrebări(dacă există) pentru a vedea dacă întrebarea voastră a fost deja adresată sub o altă formă(în cazul în care răspunsul din partea echipei vine mai greu este mai rapid să căutați voi deja printre întrebările existente).

ATENȚIE să nu postați imagini cu părți din soluția voastră pe forumul pus la dispoziție sau orice alt canal public de comunicație. Dacă veți face acest lucru, vă asumați răspunderea dacă veți primi copiat pe temă.