Sunteți pe pagina 1din 16

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:

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 threadurile 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

Spatiul
virtual de
memorie al
procesului

Segment de date
Segment de stiva

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

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:
r, daca x se afla in memorie la locatia r
f ( x) =
semnalare exceptie daca x nu se afla in memoria reala

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)

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.
Execuie
(d)

(f)

Dormant

(b)

(c)

Suspendat

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

(e)

(a)
Gata

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 threaduruilor n mai multe sisteme de operare, i anume n sistemele de operare multitasking

Anexa 2 Procese i thread-uri

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 <stdio.h>
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:

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,
const char *arg0,
const char *arg1,

// calea cu numele fiierului


// arg. coresp. lui argv[0]
// arg. coresp. lui argv[1]

..................,

const char * argn,


char * /* NULL */

// ultimul argument
// 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 <stdio.h>
#include <sys/types.h>
#include <unistd.h>
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

wait(0);
return 0;

}
// Program Fiu.c
#include <stdio.h>
// 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.

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,
//
LPTSTR lpCmdLine,
//
LPSECURITY_ATTRIBUTES lpPSA, //
LPSECURITY_ATTRIBUTES lpTSA, //
BOOL bInheritHandles,
//
DWORD dwCreationFlags,
//
LPVOID lpEnvironment,
//
LPCTSTR lpCurrentDir,
//
LPSTARTINFO lpStartInfo,
LP_PROCESS_INFORMATION lpPInfo

nume modul executabil


linia de comand
atrib. sec. proces
atrib. sec. thread
flag de motenire
flag de creare
un nou bloc de mediu
nume dir. curent

);

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 hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION;

//
//
//
//

handle proces creat


handle thread principal
identific. proces creat
identific. thread princ.

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

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
Proces A
Segment
de text

Segment
de date

...

Segment de memorie
partajat atasat

Spaiul virtual de adres

0x0060 0000

Segment
de stiv
0x0068 0000

Memorie
Spaiul de memorare
a datelor

Proces B
Segment
de text

0x0050 0000
Segment
de date

Segment de memorie
partajat atasat

0x0058 0000
Segment de
stiv

Spaiul virtual de adres

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,
int size,
int shm);

// creare sau ataarea unui segment


// dim.n numr de octeti a segment
// 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,
void *shmaddr,
int shmflg);

// identif. segm. mem. partajat


// adresa de amplasare a segm. memorie
// 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

11

Anexa 2 Procese i thread-uri

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,
BOOL bInheritFlag,
LPCTSTR lpName);

// atribute de acces
// flag de motenire
// 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 handleului 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
DWORD
DWORD
DWORD

dwAccess,
dwOffHigh,
dwOffLow,
dwNumber);

//
//
//
//

mod de acces
cel mai semnif. cuv. offset
cel mai puin semnif. offset
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 <stdio.h>
#include <stdlib.h>
#include <pthread.h>
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