CP Anexa2 Procese Si Thread-uri

16
ANEXA 2 PROCESE ŞI THREAD-URI Procesele şi thread-urile sunt elemente de bază în programare: Procesul - este o instanţă de execuţie a unui program; execută operaţiile corespunzătoare unuia sau mai multor task-uri ale unui algoritm. Thread-ul - este un fir de execuţie în cadrul unui proces; un proces poate gestiona mai multe thread-uri. În programarea paralelă şi distribuită se pot executa concurent mai multe procese sau thread-uri pe mai multe procesoare ale unui sistem paralel sau distribuit, obţinându-se creşterea performanţelor de execuţie (accelerare). Un thread implementează unul sau mai multe task-uri; dacă sunt mai multe, acestea se execută secvenţial (unul după altul). Când este posibil, se preferă thread-uri multiple deoarece timpul de comutare între thread-urile aceluiaşi proces este mai mic decât timpul de comutare între procese. A.2.1 Procese În esenţă, un proces este o instanţă de execuţie a unui segment de cod (o parte a unui program). Pentru acelaşi concept, unii autori, ca de exemplu, proiectanţii limbajului de programare Ada, folosesc denumirea de task. Termenul de task are, în cele mai multe lucrări, un înţeles mai general, de sarcină de lucru, care se poate atribui unui proces, unui thread sau chiar unei singure instrucţiuni, şi va fi utilizat cu această semnificaţie. Procesele sunt create de către sistemul de operare, ca urmare a execuţiei unei comenzi de tip RUN pentru un fişier executabil, care conţine codul (sau o parte a codului) programului, sau pot fi creeate în mod dinamic, de către alte procese în cursul execuţiei acestora. Sistemul de operare grupează toate informaţiile despre un proces într-o structură de date numită blocul de control al procesului (Process Control Block - PCB). Atunci când este creat un proces nou, sistemul crează un bloc de control nou, pentru a-l utiliza ca descriptor de-a lungul existenţei procesului. Când procesul se termină, blocul lui de control este distrus şi eliminat din memorie. În starea dormantă, un proces nu are un bloc de control şi nu poate intra în competiţie pentru obţinerea de resurse. În mod tipic, blocul de control menţine următoarele informaţii despre proces:

description

CP CP CP CP CP CP CP CP CP CP

Transcript of CP Anexa2 Procese Si Thread-uri

  • ANEXA 2

    PROCESE I THREAD-URI

    Procesele i thread-urile sunt elemente de baz n programare:

    Procesul - este o instan de execuie a unui program; execut operaiile corespunztoare unuia sau mai multor task-uri ale unui algoritm.

    Thread-ul - este un fir de execuie n cadrul unui proces; un proces poate gestiona mai multe thread-uri.

    n programarea paralel i distribuit se pot executa concurent mai multe procese sau thread-uri pe mai multe procesoare ale unui sistem paralel sau distribuit, obinndu-se creterea performanelor de execuie (accelerare).

    Un thread implementeaz unul sau mai multe task-uri; dac sunt mai multe, acestea se execut secvenial (unul dup altul).

    Cnd este posibil, se prefer thread-uri multiple deoarece timpul de comutare ntre thread-urile aceluiai proces este mai mic dect timpul de comutare ntre procese.

    A.2.1 Procese

    n esen, un proces este o instan de execuie a unui segment de cod (o parte a unui program). Pentru acelai concept, unii autori, ca de exemplu, proiectanii limbajului de programare Ada, folosesc denumirea de task. Termenul de task are, n cele mai multe lucrri, un neles mai general, de sarcin de lucru, care se poate atribui unui proces, unui thread sau chiar unei singure instruciuni, i va fi utilizat cu aceast semnificaie.

    Procesele sunt create de ctre sistemul de operare, ca urmare a execuiei unei comenzi de tip RUN pentru un fiier executabil, care conine codul (sau o parte a codului) programului, sau pot fi creeate n mod dinamic, de ctre alte procese n cursul execuiei acestora.

    Sistemul de operare grupeaz toate informaiile despre un proces ntr-o structur de date numit blocul de control al procesului (Process Control Block - PCB). Atunci cnd este creat un proces nou, sistemul creaz un bloc de control nou, pentru a-l utiliza ca descriptor de-a lungul existenei procesului. Cnd procesul se termin, blocul lui de control este distrus i eliminat din memorie. n starea dormant, un proces nu are un bloc de control i nu poate intra n competiie pentru obinerea de resurse. n mod tipic, blocul de control menine urmtoarele informaii despre proces:

  • 2 CALCUL PARALEL

    Numele procesului (identificator) Prioritatea Starea de execuie (gata, n execuie, suspendat) Starea hardware (registrele procesorului i flag-uri) Informaii de planificare. Informaii despre resursele utilizate (fiiere, intrri/ieiri). Fiecare proces are un spaiu de adrese al memoriei i poate trece prin mai

    multe stri n cursul execuiei.

    A.2.1.1 Spaiul virtual de adrese al procesului

    Spaiul virtual de adrese al unui proces este reprezentat de mulimea tuturor adreselor pe care procesul le poate utiliza (accesa). De exemplu, n sistemele Unix/Linux cu adresare pe 32 de bii, adresele pot fi cuprinse ntre 0 i 0x7fffffff, ceea ce reprezint 2 GB, restul de 2 GB, adresele de la 0x80000000 la 0xffffffff, fiind n spaiul de adrese al sistemului de operare.

    Spatiul de adrese al unui proces formeaz ceea ce se numete imaginea procesului (Fig. A.2.1) i cuprinde mai multe segmente cu rol diferit, n funcie de natura informaiilor pe care le contin, i anume:

    Segmentul de cod (numit i segment de text), conine imaginea executabil a programului i este ntotdeauna cu tip de acces citete-numai (read-only), i poate fi partajat de mai multe procese (printe-fii).

    Segmentul de date al unui proces conine variabilele alocate static (iniializate sau nu) ale programului i variabilele alocate dinamic (heap sau memoria liber). Acest segment nu poate fi partajat cu alte procese, dar poate fi partajat de toate thread-urile componente ale acelui proces.

    Segmentul de stiv memoreaz variabilele locale ale funciilor i adresele de revenire din funcii. Nu poate fi partajat cu alte proceses, iar dac sunt definite mai multe thread-uri, fiecare thread are propria lui zon de stiv n cadrul segmentului de stiv, i aceste zone nu pot fi partajate de mai multe thread-uri.

    Proces Bloc de control proces

    Segment de cod

    Segment de date

    Segment de stiva

    Spatiul virtual de

    memorie al procesului

    Fig. A.2.1 Spaiul virtual de adrese al procesului.

    n general, sistemele de operare multitasking sau cu multiprocesare implementeaz un mecanism de memorie virtual (virtual memory), prin care este

  • Anexa 2 Procese i thread-uri 3

    posibil ca numai o parte din spaiul virtual de adrese al unui proces s fie ncrcat n memoria fizic. Memoria virtual poate fi implementat ca o extensie a modului de administrare al memoriei (memory management), prin paginare, prin segmentare, sau o combinaie a acestora dou. n acest din urm caz, care este cel mai frecvent utilizat, segmentarea este folosit din punct de vedere al utilizatorului, dar fiecare segment este divizat n pagini de dimensiune fix, pentru a fi alocate n memorie.

    Operaia de asignare a adreselor (address mapping) n sistemele de memorie virtual poate fi definit astfel. Fie spaiul virtual de adrese al unui proces V = { 0, 1,,v-1 }, i spaiul de adrese al memoriei fizice (reale) M = { 0, 1, , m-1 }.

    n general, n sistemele mari, spaiul virtual de adrese este mai mare dect memoria fizic (v > m), dar, n sistemele mai mici, se poate ntlni i situaia invers.

    Sistemul de operare aloc n mod dinamic zone de memorie fizic unor pri din spaiul de adrese virtuale ale proceselor. Mecanismul de translaie a adreselor trebuie s asocieze fiecare adres virtual cu o locaie fizic. Cu alte cuvinte, n orice moment hardware-ul de asignare trebuie s realizeze funcia f : V M , astfel nct:

    =

    realamemoria inafla se nu daca exceptie semnalarelocatia la memorie inafla se daca ,

    )(x

    rxrxf

    Astfel, n sistemele cu memorie virtual trebuie s existe resurse hardware care s detecteze dac o anumit entitate (care poate fi segment sau pagin) se afl n memoria fizic sau nu. n cazul n care entitatea respectiv este absent, se semnaleaz o excepie, de lips a memoriei reale (memory miss). La o astfel de semnalare, sistemul de operare acioneaz pentru alocarea entitii respective (pagin sau segment) n memoria real, n general prin dezalocarea altei entiti, dup o anumit politic de nlocuire (a paginilor sau segmentelor) (memory replacement policy).

    Atunci cnd un proces aflat n execuie ncearc s acceseze o adres pentru care se primete o semnalare de lips a memoriei, el este suspendat, pn cnd pagina (sau segmentul) respectiv este adus de pe disc n memorie. Dat fiind c timpul de acces la disc este n mod obinuit cu cteva ordine de mrime mai mare dect un ciclu de memorie, sistemul de operare planific alt proces pentru a fi executat n acest interval de timp. Suspendarea proceselor pentru ncrcarea zonelor de memorie absente este nglobat n operaia de comutare a proceselor, descris n continuare.

    A.2.1.2 Comutarea proceselor

    n timpul execuiei, un proces trece printr-o succesiune de stri: starea dormant (dormant), starea gata de execuie (ready), starea de execuie (running) i starea suspendat (suspended) (Fig. A.2.2).

    Starea dormant este starea n care procesul nu este cunoscut de sistemul de operare, pentru c nu a fost nc creat. Toate programele care nu au fost nc lansate n execuie pot fi privite ca procese dormante.

    n starea gata de execuie, procesul are alocate toate resursele necesare pentru execuie, cu excepia procesorului. Planificatorul sistemului de operare (scheduler)

  • 4 CALCUL PARALEL

    selecteaz un proces gata de execuie, pe care l trece n starea de execuie, operaie numit planificarea procesului (process scheduling).

    n starea de execuie, un proces posed toate resursele necesare, inclusiv un procesor pe care se execut. n sistemele uniprocesor, n orice moment de timp, cel mult un proces poate s se afle n starea de execuie; n sistemele multiprocesor mai multe procese se afl n execuie n acelai moment de timp.

    (f)

    (e)

    (d)

    (c)(b)

    (a)

    Execuie

    Gata

    SuspendatDormant

    Legend: (a) Creare (b) Planificare (c) Prelevare (d) Ateptare eveniment (e) Apariie eveniment (f) Terminare

    Fig. A.2.2 Diagrama de tranziie a strilor unui proces.

    Procesele aflate n strile de execuie sau gata de execuie sunt procese active, de care sistemul de operare se ocup pentru a asigura o anumit politic de planificare. Un proces rmne n starea de execuie pn cnd sistemul de operare l ntrerupe (preleveaz) i l trece n starea gata de execuie, pentru a planifica un alt proces gata de execuie.

    Tranziia ntre dou procese active ntr-un sistem de operare cu multitasking sau cu multiprocesare se numete comutarea proceselor (process switch), i are loc ca rspuns la un eveniment din sistem.

    Comutarea proceselor este o operaie complex, care implic un cost (overhead) important i, datorit frecvenei cu care are loc ntr-un sistem, poate influena semnificativ performanele acestuia. Costul de comutare apare, n principal, datorit salvrii strii procesului atunci cnd este prelevat i a refacerii strii atunci cnd este planificat. Eficiena operaiei de comutare a proceselor poate fi crescut prin prevederea unor faciliti hardware (ca, de exemplu, seturi de registre multiple), sau printr-o modalitate de structurare a proceselor, utiliznd thread-uri.

    Un proces n execuie poate deveni suspendat prin apelul unei operaii de intrare/ieire, sau prin ateptarea unui eveniment. Sistemul de operare memoreaz cauza suspendrii procesului, astfel nct s-l poat relua atunci cnd condiia de suspendare a disprut. La reluare, procesul suspendat este trecut n starea gata de execuie. Un proces suspendat este un proces inactiv, care nu consum timp de execuie al procesorului i aceast stare suspendat mbuntete performanele sistemului de operare.

    Implementarea proceselor i a thread-urilor difer de la un sistem de operare la altul. n continuare se va prezenta modul de implementare a proceselor i thread-uruilor n mai multe sisteme de operare, i anume n sistemele de operare multitasking

  • Anexa 2 Procese i thread-uri 5

    sau cu multiprocesare bazate pe Unix/Linux i n sistemele de operare Windows (NT/2000/XP/Vista).

    A.2.1.3 Procese Unix/Linux

    n sistemele de operare Unix/Linux un proces este identificat printr-un identificator de proces (process identifier, pid), care este o variabil de tipul definit pid_t (care poate fi un numr ntreg), care este unic determinat n sistem.

    Un proces nou poate fi creat n mod dinamic prin apelul funciei de sistem: pid_t fork ();

    Ca urmare a execuiei acestei funcii, procesul apelant (printe) este duplicat, noul proces creat (fiu) primind o copie independent a imaginii procesului printe i acces partajat la toate fiierele acestuia. Mai precis, n Unix, procesul fiu va avea propriile lui segmente de date i de stiv, dar va executa instruciuni din segmentul de cod al printelui, care va fi astfel partajat de cele dou procese.

    Procesele printe i fiu mai difer ntre ele i prin aceea c identificatorul de proces returnat printelui de ctre funcia fork este identificatorul procesului fiu (care nu poate avea niciodat valoarea 0), iar procesul fiu primete ntotdeauna valoarea 0. Aceast informaie poate fi utilizat de fiecare proces pentru a-i determina propria identitate, folosind o secven de cod ca cea dat n programul urmtor.

    Orice proces poate s afle propiul pid prin apelul funciei getpid() i pid-ul printelui prin apelul funciei getppid().

    Ateptarea terminrii unui proces de ctre un altul (n general, procesul printe ateapt terminarea execuiei proceselor fii), se face prin apelul funciei: pid_t wait (int * status)

    Aceast funcie returneaz pid-ul unuia din fiii care s-au terminat, iar n locaia indicat de pointerul dat ca argument se obine un cod de terminare al procesului fiu. Dac se ateapt mai muli fii, trebuie apelat funcia wait pentru fiecare fiu. Dac se apeleaz funcia wait i nu mai exist procese fii, atunci wait returneaz valoarea -1. Dac procesul printe se termin nainte ca s se termine un fiu, acel fiu devine proces zombie, care primete ppid = 1 (pid-ul procesului init).

    n exemplul urmtor un proces printe creeaz un fiu i valoarea returnat de funcia fork este folosit pentru diferenierea execuiei n procesele printe i fiu.

    // Program Hello_PROCESS.c #include int main () {

    if (fork() == 0 ) printf(Hello FIU pid=%d ppid=%d\n, getpid(), getppid()); else { printf(Hello PARINTE pid=%d ppid=%d\n, getpid(), getppid()); wait(0); } return 0;

    } Pentru utilizarea programului se compileaz cu comanda:

  • 6 CALCUL PARALEL

    gcc -o Hello_PROCESS Hello_PROCESS.c Dup lansarea n execuie cu comanda: ./Hello_PROCESS, se afieaz la

    consol mesaje ca: Hello PARINTE pid=7024 ppid=100 Hello FIU pid=7025 ppid=7024 Valorile identificatorilor i ordinea acestor mesaje poate varia de la o execuie

    la alta, dat fiind c se atribuie valori diferite identificatorilor proceselor nou create. Funcia wait asigur sincronizarea la terminarea unui proces fiu; dac este

    apelat pentru toate procesele fiu create, reprezint o barier de sincronizare. nlocuirea imaginii unui proces. Un proces fiu poate s execute un alt cod

    de program prin apelul unei funciei de sistem de tipul exec(). Exist mai multe de variante ale funciei exec (execl, execv, execlp),

    depinznd de tipul argumentelor care se paseaz acesteia. Argumentele specific, n general, numele fiierului care conine codul executabil care va nlocui segmentul de cod existent, precum i argumente de linie de comand (argc, argv). Caracterul l din denumirea funciei exec indic utilizarea unei liste de parametri, iar caracterul v indic utilizarea unui vector de parametri; caracterul p indic modul de cutare a fiierului executabil, i anume n calea data ca argument (path). De exemplu, prototipul funciei execlp este: int execlp (

    const char *path, // calea cu numele fiierului const char *arg0, // arg. coresp. lui argv[0] const char *arg1, // arg. coresp. lui argv[1] .................., const char * argn, // ultimul argument char * /* NULL */ // inchiderea listei de arg.

    ) n cele mai multe programe, funciile fork() i exec() sunt utilizate mpreun:

    procesul printe creaz un proces fiu i execuia acestuia este supranscris, prin apelul funciei exec.

    n exemplul urmtor, procesul printe (dat n programul Parinte.c) creeaz un proces fiu prin apelul funciei fork; procesul fiu astfel creat nlocuiete imaginea motenit cu o nou imagine creeat prin programul fiu.c, transmitndu-i ca argument pid-ul procesului iniial (procesul printe). Procesul fiu afieaz aceste valori i revine.

    // Program Parinte.c #include #include #include int main(){ char arg1[4]; pid_t pid = getpid();

    sprintf (arg1,"%4d",pid); printf("Proces parinte pid: %d\n", pid); if (fork() == 0) execlp("/calea_abs/Fiu", "Fiu", arg1, (char*)0);

  • Anexa 2 Procese i thread-uri 7

    wait(0); return 0;

    } // Program Fiu.c #include // Argumentul primit in argv[1] este pid-ul parintelui int main(int argc, char** argv){

    int arg1 = atoi(argv[1]); printf("Proces fiu pid: %d ppid: %d arg. primit: %d\n", getpid(), getppid(), arg1); return 0;

    } Se compileaz cele dou programe i se execut: gcc Parinte.c o Parinte gcc Fiu.c o Fiu ./Parinte La consol se afieaz mesaje astfel: Proces parinte pid: 2018 Proces fiu pid: 2019 ppid: 2018 arg. primit: 2018 Se poate da ca prim argument al functiei execlp() calea relativ a fiierului

    executabil aflat n directorul curent (Fiu), dar, n acest caz variabila de mediu PATH trebuie s conin directorul curent (.)

    A.2.1.4 Procese Windows

    Sistemele de operare Windows sunt sisteme de operare cu multiprocesare, care pot controla funcionarea concurent a mai multor procesoare cu memorie partajat. n sistemele Windows, entitatea de alocare a timpului procesorului (procesoarelor) este thread-ul. Fiecare proces conine cel puin un thread de execuie (thread-ul principal) i poate crea thread-uri noi, care partajeaz spaiul de adres al procesului care le-a crea, precum i resursele acestuia (fiiere, obiecte de sincronizare).

    Un proces Windows este identificat att printr-un instrument de acces, un handle, care este o intrare n tabela de resurse a sistemului, ct i printr-un identificator (id), care este un numr unic, atribuit unui proces, i care l caracterizeaz pe toat durata de existen a acestuia. Aceast dualitate de identificare ngreuneaz, oarecum, lucrul cu procesele Windows, unele funcii cer ca parametru handle-ul procesului, altele identificatorul acestuia (id-ul).

    La lansarea n execuie (cu comanda RUN) a unui program executabil, se creaz un proces nou, cu propriul spaiu de adrese, i cu minimum un thread de execuie (thread-ul principal). Trecerea unui proces prin diferitele stri de execuie are loc, aa cum a fost artat n paragrafele precedente, pe baza evenimentelor care apar n sistem i conform unei anumite politici de planificare.

    Accesul programatorului la funciile sistemului de operare este posibil prin intermediul unei interfee, numit Interfaa de Programare a Aplicaiilor (Application Programming Interface API, pentru sistemele pe 32 de bii, Win32 API), care conine definiia tipurilor de date i a funciilor de apel ale sistemului.

  • 8 CALCUL PARALEL

    Un proces aflat n starea de execuie poate crea un proces nou (proces fiu) prin

    apelul funciei CreateProcess(). Se poate considera c, n mare, funcia CreateProcess are funcionalitatea combinaiei de apeluri fork exec din sistemele Unix: creaz un proces nou, mpreun cu thread-ul lui primar, care execut un segment de cod executabil coninut n fiierul dat ca prim argument. Prototipul funciei CreateProcess este urmtorul: BOOL CreateProcess ( LPCTSTR lpAppName, // nume modul executabil LPTSTR lpCmdLine, // linia de comand LPSECURITY_ATTRIBUTES lpPSA, // atrib. sec. proces LPSECURITY_ATTRIBUTES lpTSA, // atrib. sec. thread BOOL bInheritHandles, // flag de motenire DWORD dwCreationFlags, // flag de creare LPVOID lpEnvironment, // un nou bloc de mediu LPCTSTR lpCurrentDir, // nume dir. curent LPSTARTINFO lpStartInfo, LP_PROCESS_INFORMATION lpPInfo );

    Valoarea returnat de funcia CreateProcess, de tip boolean (BOOL), are semnificaia obinuit pentru toate apelurile de funcii ale interfeei Win32 API, i anume: TRUE semnific crearea cu succes a procesului; FALSE semnific eroare, procesul nu a fost creat, iar informaii detaliate despre cauza erorii se pot obine prin apelul funciei GetLastError.

    Apelul unei funcii CreateProcess are ca efect crearea unui proces nou, care va executa programul continut n fisierul al carui nume este dat ca ca argument (lpApplicationName). Pointerul de tip LPSTARTUPINFO este un pointer la o structur de date de tipul STARTUPINFO, ale crei date membre descriu modul de pornire al procesului, i care trebuie s fie definite nainte de apelul funciei CreateProcess.

    Pointerul de tip LPPROCESS_INFORMATION este un pointer la o structur de date de tipul PROCESS_INFORMATION, n care se obin datele de identificare ale procesului nou creat i ale thread-ului lui principal. Aceast structur, definit n interfaa Win32 API este urmtoarea: typedef _PROCESS_INFORMATION {

    HANDLE hProcess; // handle proces creat HANDLE hThread; // handle thread principal DWORD dwProcessId; // identific. proces creat DWORD dwThreadId; // identific. thread princ.

    } PROCESS_INFORMATION; Ceilali parametri de apel ai funciei CreateProcess, descrii pe scurt n

    prototipul acesteia, permit stabilirea diferitelor opiuni pentru atributele de securitate, motenirea resurselor, director curent, linie de comand, etc.

    A.2.2 Memoria partajat ntre procese

    Fiecare proces are propriul su spaiu virtual de adrese, constituit din segmentele de text, de date i de stiv, la care are acces; procesul nu poate adresa

  • Anexa 2 Procese i thread-uri 9

    cuvinte de memorie n afara acestui spaiu, dup cum nici alte procese nu pot avea acces la spaiul acestuia. Modalitatea prin care dou sau mai multe procese pot accesa o zon de memorie comun o reprezint crearea unui segment de memorie partajat (shared memory segment).

    n general, un proces creeaz (aloc) un segment de memorie partajat, pentru care definete dimensiunea i drepturile de acces, dup care l amplaseaz n propriul spaiu de adrese, prin stabilirea unei corespondene (mapping) ntre adresele din segmentul partajat i adresele din spaiul su de adrese. De aceea, de multe ori, segmentele de memorie partajate mai sunt denumite segmente (sau obiecte) amplasate (mapped segments, mapped objects).

    O dat creat un segment de memorie partajat, alte procese pot s-l ataeze i s-l amplaseze n spaiul lor virtual de adrese. Fiecare proces acceseaz memoria partajat relativ la adresele de ataare proprii, aa cum se poate vedea n Fig. A.2.3.

    Cnd un proces nu mai necesit accesul la segmentul de memorie partajat, el se poate detaa de acesta. Cnd toate procesele s-au detaat de la un segment de menorie partajat, el poate fi ters din memorie, operaie pe care, n general, o execut procesul care l-a creat. Localizarea n memoria fizic a unui segment de memorie partajat depinde de mecanismul de memorie virtual al sistemului de operare. ntr-un sistem cu memorie virtual prin paginare, segmentul de memorie partajat este alctuit dintr-un numr ntreg de pagini. Aceste pagini se pot afla n memoria fizic (RAM), la adrese stabilite prin mecanismul de memorie virtual, adrese care variaz n cursul derulrii execuiei, i nu neaprat contigue (alturate), sau pot fi stocate pe disc

    Spaiul virtual de adres 0x0060 0000 0x0068 0000

    Segment de text

    Segment de date

    ... Segment de memorie partajat atasat

    Segment de stiv

    Proces A

    Spaiul virtual de adres

    0x0050 0000 0x0058 0000

    Segment de text

    Segment de date

    Segment de memorie partajat atasat

    Segment de stiv

    Proces B

    Spaiul de memorare a datelor

    Memorie

    Fig. A.2.3 Amplasarea unui segment de memorie partajat.

    Fiierul de salvare pe disc (backing store) a paginilor unui segment de memorie partajat n operaia de comutare a paginilor de memorie (swap), poate fi specificat de ctre procesul care l creeaz, sau poate fi definit de sistemul de operare.

    ntr-un sistem paralel condiia necesar ca mai multe procese care se execut pe procesoare diferite s poat accesa segmente de memorie partajat o constitue existena unui spaiu unic de adrese al memoriei, aa cum este, de exemplu ntr-un multiprocesor. Memoria (fizic) partajat a calculatorului poate fi memorie global

  • 10 CALCUL PARALEL

    (deci centralizat, cum este n cazul arhitecturilor UMA), sau distribuit (deci ataat unor module combinate procesor-memorie, cum este cazul arhitecturilor NUMA). Ceea ce este important, este posibilitatea ca toate procesoarele sistemului s poat accesa (prin intermediul reelei de interconectare) un spaiu unic de adrese partajate.

    n astfel de sisteme, segmentul de memorie partajat, creat de un proces i ataat de acesta i de alte procese, trebuie s fie amplasat n memoria (fizic) partajat a sistemului, i nu ntr-o memorie local (dac aceasta exist).

    Crearea segmentelor de memorie partajat n Unix. Pentru crearea sau ataarea segmentelor de memorie partajat n sistemele Unix tradiionale, se folosete apelul funciei sistem shmget(), care creaz un segment de memorie partajat i genereaz structurile de date de sistem asociate. Funcia shmget() returneaz identificatorul unui segment de memorie partajat, n cazul execuiei corecte, sau 1 n caz de eroare. Prototipul acestei funcii este: int shmget ( key_t key, // creare sau ataarea unui segment int size, // dim.n numr de octeti a segment int shm); // flag de conditii de acces

    Dup apelul acestei funcii, segmentul este creat sau ataat de ctre un proces, dar nu va putea fi utilizat, pn ce nu este amplasat n spaiul de adres propriu, prin apelul funciei de sistem shmat(): void *shmat ( int shmid, // identif. segm. mem. partajat void *shmaddr, // adresa de amplasare a segm. memorie int shmflg); // flag de acces la segm. memorie

    Valoarea returnat de funcia shmat() reprezint adresa, n spaiul de adrese al procesului apelant, unde are loc amplasarea efectiv a segmentului de memorie partajat. Aceast adres poate fi diferit de adresa dorit de amplasare (shmaddr), datorit faptului c, n funcie de valoarea flagului shmflg, se poate ca segmentul s fie aliniat la dimensiunile unei pagini, sau s fie amplasat la prima adres aliniat disponibil n spaiul de adrese.

    Fiecare proces care comunic prin memoria partajat trebuie s se ataeze la segmentul de memorie partajat prin apelul functiei shmat, pentru a obine adresa de nceput a segmentului. Pentru aceasta trebuie s cunoasc identificatorul shmid, pe care l obine (ntr-un mod oarecare) de la procesul care a creat segmentul respectiv.

    Dup ce toate procesele au ataat segmentul de memoria partajat respectiv, ele vor comunica prin variabilele partajate alocate n acel segment.

    Crearea segmentelor de memorie partajat n Windows. n biblioteca Win32 API memoria partajat ntre procese este implementat prin amplasarea (mapping) unui segment de memorie n spaiul de adres al fiecrui proces. Segmentul de memorie partajat se creaz sau se ataeaz de ctre un proces folosind funcia CreateFileMapping() i se amplaseaz n spaiul de adrese folosind funcia MapViewOfFile().

    Pentru conceptul de segment de memorie partajat, n documentaia Windows, se folosete mai frecvent denumirea de obiect de memorie partajat (shared memory

  • Anexa 2 Procese i thread-uri 11

    object), sau de obiect fiier amplasat (file-mapping object). n continuare, n exemplificrile Windows, se va prefera denumirea de obiect de memorie partajat.

    Obiectele de memorie partajat pot fi create cu nume (cel dat prin argumentul lpName), sau fr nume, dac acest argument este NULL.

    Pentru crearea unui obiect de memorie partajat este necesar s fie specificat un fiier (dat printr-un handle), care reprezint fiierul de salvare necesar n operaiile de comutare a paginilor (swap) n mecanismul de memorie virtual al sistemului. Acest fiier trebuie s fie mai nti creat, cu funcia CreateFile(), care returneaz handle-ul fiierului creat, i acest handle este folosit ca argument pentru funcia CreafFileMapping(), al crei prototip este dat mai jos: HANDLE CreateFileMapping ( HANDLE hFile, // handle fiier de salvare LPSECURITY_ATTRIBUTES lpSAttr, DWORD fProtect, // mod de acces la mem. partajata DWORD dwSizeHigh, // cel mai semnif. cuvnt.al dim.

    DWORD dwSizeLow, LPCTSTR lpName ); // nume segm. memorie partajat Valoarea returnat, de tip HANDLE, are valoarea NULL n caz de eroare, iar

    tipul erorii se poate afle prin apelul funciei GetLastError(). Dac valoarea returnat este diferit de NULL, atunci obiectul de memorie partajat a fost creat sau ataat (deschis) corect. Obiectul este creat ca un obiect nou atunci cnd, la apelul funciei CreateFileMapping, nu exist nici un alt obiect cu aceleai nume. Dac un astfel de obiect exit, handle-ul returnat este un handle valid i identific obiectul deja existent. Aceast situaie este detectat prin apelul, imediat dup funcia CreateFileMapping, a funciei GetLastError, care returneaz 0, dac obiectul nu fusese creat mai nainte, sau constanta simbolic ERROR_ALREADY_EXISTS, dac obiectul exista deja.

    Apelul unei funcii CreateFileMapping pentru un obiect existent este o operaie de ataare (deschidere) a obiectului de memorie partajat de ctre procesul apelant i este echivalent cu apelul funciei OpenFileMapping(), al crui prototip este urmtorul: HANDLE OpenFileMapping( DWORD dwAcces, // atribute de acces BOOL bInheritFlag, // flag de motenire LPCTSTR lpName); // numele ob. memorie partajat

    Cel mai simplu mod de implementare a memoriei partajate ntre procese n Windows, este folosirea obiectelor de memorie partajat cu nume. n aceast situaie, un proces creaz i ataeaz obiectul cu numele dorit, folosind funcia CreateFileMapping, iar celelalte procese ataeaz (deschid) obiectul cu acelai nume, folosind una din funciile CreateFileMapping sau OpenFileMapping.

    Pot fi folosite i obiecte de memorie partajat fr nume, dar, n aceast situaie, mecanismul de creare-deschidere se face prin transmisia i duplicarea handle-ului obiectului de memorie partajat i este puin mai complicat.

    Amplasarea unui obiect de memorie partajat n spaiul de adres al unui proces, se face prin apelul unei funcii MapViewOfFile: LPVOID MapViewOfFile( HANDLE hObject, // obiectul de memorie partajat

  • 12 CALCUL PARALEL

    DWORD dwAccess, // mod de acces DWORD dwOffHigh, // cel mai semnif. cuv. offset DWORD dwOffLow, // cel mai puin semnif. offset DWORD dwNumber); // numar de octeti de amplasat

    La apelul unei funcii MapViewOfFile, un numr dwNumber octei ai obiectului de memorie partajat cu handle-ul hObject, situai cu un deplasament (offset) fa de nceputul acestui obiect dat prin cuvintele dwOffHigh i dwOffLow, sunt amplasai n spaiul de adres al procesului apelant, ncepnd cu adresa returnat de aceast funcie.

    A.2.3 Thread-uri

    Thread-urile reprezint o modalitate software de a mbunti performanele sistemelor de calcul prin reducerea timpului de comutare a proceselor. Un thread este un fir de executie n cadrul unui proces, iar n fiecare proces se pot defini mai multe thread-uri.

    Fiecare thread are o stare mai redus (constituit din starea registrelor i a flag-urilor procesorului i stiva proprie) i toate thread-urile unui proces partajeaz resursele de calcul ale acestuia (ca de exemplu, memoria, fiierele, canale I/O etc.).

    n sistemele bazate pe thread-uri, thread-ul este cea mai mic entitate de planificare, iar procesul servete ca un mediu de execuie a thread-urilor.

    De vreme ce toate celelalte resurse, cu excepia procesorului, sunt gestionate de ctre procesul care le nglobeaz, comutarea ntre thread-urile care aparin aceluiai proces, care implic doar salvarea, respectiv restaurarea, strii hardware (nu i a stivei, care este separata, proprie fiecarui thread), este rapid i eficient. Totui, comutarea ntre thread-urile care aparin unor procese diferite implic tot costul de comutare a proceselor.

    Thread-urile sunt un mecanism eficient de exploatare a concurenei programelor. Un program poate fi mprit n mai multe thread-uri, fiecare cu o execuie mai mult sau mai puin independent, i pot comunica ntre ele prin accesul la spaiul de adres a memoriei procesului, pe care l partajeaz ntre ele.

    Toate thread-urile unui proces partajeaz segmentul de cod i segmentul de date al procesului care le-a creeat. Fiecare thread are propriul segment de stiv care nu este partajat cu alte thread-uri.

    Dac thread-urile aparin unor procese diferite, trebuie s fie definit n mod explicit un segment de memorie partajat ntre procese, la fel cum se procedeaz pentru partajarea memoriei ntre procese.

    A.2.3.1 Thread-uri POSIX

    n sistemul de operare Unix tradiional nu sunt definite thread-uri; fiecare proces conine un singur thread, iar alocarea timpului de execuie al procesorului se face ntre procesele active, dup diferii algoritmi de planificare.

    n sistemele de operare moderne Unix/Linux, dezvoltate pentru multiprocesoare i multicalculatoare, s-au implementat diferite faciliti pentru controlul i partajarea resurselor multiple (procesoare, memorie), inclusiv thread-uri.

  • Anexa 2 Procese i thread-uri 13

    Cea mai utilizat modalitate de implementare a thread-urilor n astfel de sisteme este aceea definit n standardul POSIX (IEEE POSIX Standard 1003.4), care asigur portabilitatea ntre platforme hardware diferite. Un thread-ul POSIX este denumit pthread [But97].

    O aplicaie cu pthread-uri POSIX este un program C care utilizeaz diferite funcii pentru crearea thread-urilor, definirea atributelor, controlul strilor (terminare, detaare) thread-urilor. Aceste funcii sunt definite ntr-o bibliotec (libpthread.so), care trebuie s fie adugat (la link-are) programului apelant (prin opiunea de compilare lpthread).

    Un thread POSIX este caracterizat printr-un identificator de tipul pthread_t i o colectie de atribute grupate ntr-o structura de tipul pthread_attr_t.

    La lansarea n executie a unui program, sistemul de operare creeaz un proces care conine un singur thread, numit thread-ul principal. n cursul executiei (n mod dinamic) se pot crea alte thread-uri prin apelul funciei: int pthread_create (pthread_t *thread, pthread_attr_t *attr,

    void *(functie_thread)(void*), void* param); Identificatorul thread-ului creat (de tipul pthread_t) este returnat thread-ului

    apelant prin parametrul thread. n structura de date de tip pthread_attr_t sunt grupate atributele de creare ale pthread-ului, ca, de exemplu, prioritatea, politica de planificare, dimensiunea i adresa stivei, etc. Valoarea NULL pasat ca pointer la atribute are ca efect crearea unui thread cu atribute implicite. Thread-ul nou creat execut funcia: void * functie_thread (void* param) Parametrul care se transmite functiei thread-ului la creare (param) trebuie s fie de tip pointer la void (void *) i acesta se obtine prin conversia unui pointer la o variabila care contine propiu-zis valoarea transmis sau la o structur care poate grupa mai multe valori. Dat fiind ca thread-urile aceluiai proces partajeaz doar segmentele de cod i de date (nu i stiva, care este proprie fiecarui thread i nu poate fi partajat), este necesar ca parametrul transmis ca argument al functiei thread-ului s se afle n segmentul de date i nu n stiv. Deci el poate fi definit ca:

    variabil global (de tip static sau nu), variabil local de tip static, variabil n memoria liber (heap) alocat cu una din funciile alloc,

    malloc, realloc.

    Un thread se termin ntr-una din urmtoarele condiii:

    La terminarea funciei thread-ului (revenire prin execuia return). La apelul funciei exit se termin procesul, cu toate thread-urile sale;

    aceast funcie se apeleaz de thread-ul principal, sau de oricare alt thread n cazul aperiiei unei erori care nu permite continuarea execuiei.

    La apelul funciei pthread_exit se termin numai thread-ul apelant; dac aceast funcie a fost apelat de thread-ul principal (din funcia main) nu

  • 14 CALCUL PARALEL

    se termin ntreg procesul ci numai thread-ul principal, iar procesul se termin atunci cnd se termina toate thread-urile create.

    Un thread poate atepta terminarea altui thread prin apelul funciei: int pthread_join (pthread_t thread, void **retval);

    Funcia pthread_join asigur sincronizarea thread-ului apelant cu terminarea unui singur thread; apelul acestei funcii pentru toate thread-urile unei regiuni paralele reprezint o barier de sincronizare.

    Exemplu de program cu thread-uri POSIX: // Program Hello_PTHREAD.c #include #include #include int param0 = 0; // in memoria partajata int param1 = 1; // in memoria partajata void *hello (void *param); int main (int argc, char *argv[]) { pthread_t id0, id1; printf ("Hello Thread master\n"); pthread_create(&id0, 0, hello, &param0); pthread_create(&id1, 0, hello, &param1); pthread_join (id0, 0); pthread_join (id1, 0); exit (0); } void *hello (void *param){ int id = *(int*)param; printf ("Hello Thread %d\n",id); return 0; } Thread-ul principal creaz dou thread-uri noi, crora le transmite un parametru (cu valoare 0, respectiv 1). Thread-urile create execut funcia hello() i afieaz la consol un mesaj cu valoarea parametrului primit. Programul se compileaz i se link-eaz cu biblioteca libpthread.so cu comanda: gcc -o Hello_PTHREAD Hello_PTHREAD.c -l pthread La lansare n execuie cu comanda ./Hello_PTHREAD, afieaz la consol: Hello Thread Master Hello Thread 0 Hello Thread 1

    A.2.3.2 Thread-uri Windows

    Sistemele de operare Windows (NT/2000/XP/Vista) sunt sisteme de operare cu multiprocesare, care pot controla funcionarea concurent a mai multor procesoare care acceseaza memorie partajat.

    n sistemele Windows, entitatea de alocare a timpului procesorului (procesoarelor) este thread-ul. Fiecare proces conine cel puin un thread de execuie

  • Anexa 2 Procese i thread-uri 15

    (thread-ul principal) i poate crea thread-uri noi, care partajeaz spaiul de adres al procesului care le-a creat, precum i resursele acestuia (fiiere, obiecte de sincronizare).

    Un thread Windows este identificat att printr-un instrument de acces numit handle (n continuare se va folosi aceast denumire, netradus, dar mai apropiat de informaiile pe care un programator le utilizeaz n mod obinuit), ct i printr-un identificator (id), care este un numr unic, atribuit unui thread, care l caracterizeaz pe toat durata de existen a acestuia. Aceast dualitate de identificare ngreuneaz, oarecum, lucrul cu thread-urile Windows, unele funcii cer ca parametru handle-ul thread-ului, altele identificatorul acestuia (id-ul). Crearea unui thread nou ntr-un proces necesit, mai nti definirea funciei pe care thread-ul urmeaz s o execute, urmat de apelul unei funcii CreateThread(), care are urmtorul prototip: HANDLE CreateThread ( LPSECURITY_ATTRIBUTES lpTSA, DWORD dwStackSize, // dimensiunea stivei LPTHREAD_START_ROUTINE lpThreadFunc, LPVOID lpParam, // parametrii noului thread DWORD dwCreationFlags, // flag de creare LPDWORD lpThreadID ); // pointer la id. returnat

    Funcia CreateThread returneaz handle-ul thread-ului nou creat. Dac valoarea returnat este NULL, atunci a avut loc o eroare i thread-ul nu a fost creat.

  • 16 CALCUL PARALEL

    ANEXA 2 PROCESE I THREAD-URI A.2.1 Procese A.2.1.1 Spaiul virtual de adrese al procesului A.2.1.2 Comutarea proceselor A.2.1.3 Procese Unix/Linux A.2.1.4 Procese Windows

    A.2.2 Memoria partajat ntre procese A.2.3 Thread-uri A.2.3.1 Thread-uri POSIX A.2.3.2 Thread-uri Windows