Home - qdidactic.com
Didactica si proiecte didacticeBani si dezvoltarea cariereiStiinta  si proiecte tehniceIstorie si biografiiSanatate si medicinaDezvoltare personala
referate stiintaSa fii al doilea inseamna sa fii primul care pierde - Ayrton Senna





Aeronautica Comunicatii Drept Informatica Nutritie Sociologie
Tehnica mecanica

Linux


Qdidactic » stiinta & tehnica » informatica » linux
Procese si Fire de executie Linux



Procese si Fire de executie Linux


Procese si Fire de executie Linux

 LAB1

1. Obiective

Intelegerea structurii si functionalitatii proceselor si firelor de executie Linux;

constuirea si manipularea proceselor Linux folosind limbajul C;

construire si manipularea firelor de executie folosind limbajul C;

2. Notiuni teoretice

 Ce este un proces ?

Un concept cheie in orice sistem de operare este procesul. Un proces este un program aflat in executie. Procesele sunt unitatea primitiva prin care sistemul de operare aloca resurse utilizatorilor.



In momentul lansarii in executie a unui program executabil, in sistemul de operare se va crea un proces pentru alocarea resurselor necesare rularii programului respectiv. Fiecare sistem de operare pune la dispozitie apeluri de sistem pentru:

crearea unui proces;

terminarea unui proces;

asteptarea terminarii unui proces;

apeluri pentru duplicarea descriptorilor de resurse intre procese ori inchiderea acestor descriptori;

 Cum este identificat un proces in cadrul sistemul de operare ?

Fiecare proces este identificat in mod unic in cadrul Linux printr-un identificator unic numit Process ID (PID). Acesti identificatori sunt asignati automat proceselor de catre sistemul de operare in momentul in care un proces este construit.

 Care sunt relatiile intre procese ?


Orice proces Linux are un parinte (cu exceptia procesului init ce are PID=1 si este lansat automat in executie in momentul initializarii sistemului de operare). Un proces care creeaza un  nou proces devine parintele acelui nou proces. Un proces poate avea mai multe procese fiu.

Comenzi utile Linux pentru manipularea proceselor

Comanda ps afiseaza lista proceselor aflate in executie la un moment dat in cadrul sistemului.

 Comanda kill permite transmiterea semnalului de terminare a executiei pentru un proces.

% kill -9 pid

Comanda kill transmite de fapt catre procesul cu pid-ul pid semnalul SIGTERM, care determina terminarea executie procesului. Comanda kill poate fi folosita pentru a trimite orice semnal catre un proces. Se va reveni intr-o lucrare urmatoare asupra semnalelor Linux.


Functii sistem pentru gestionarea proceselor

Creare proceselor folosind functia fork

Functia fork creeaza un nou proces. Semnatura functiei este urmatoarea:


  #include <sys/types.h>

  #include <unistd.h>


  pid_t

  fork(void);

La apelarea functiei fork, un proces nou este creat prin duplicarea procesului care a initiat apelul functiei fork. Dupa crearea procesului nou, numit si proces fiu, procesul initiator numit si proces parinte va continua executia cu urmatoarea instructiune aflata dupa fork. Procesul fiu va executa acelasi program incepand cu aceiasi instructiune de dupa functia fork.

Functia fork returneza pid-ul procesul fiu in cadrul procesului parinte, o in cadrul procesului fiu sau -1 in caz de eroare.

Avand in vedere ca procesul fiu va executa acelasi program ca si procesul parinte (incepand cu instructiunea ce urmeaza instructiunii fork cu care a fost creat) se utilizeaza valoarea returnata de functia fork pentru a putea executa instructiuni diferite in procesul fiu si in procesul parinte.


pid_t p = fork();

if(p==0)

else if(p>0)

else



Procesul parinte si procesul fiu sunt procese complet independente. Nu exista nici o posibilitate de a modifica din cadrul unui proces fiu o variabila dintr-un proces parinte, sau de a avea vizibilitatea asupra modificarii valorilor variabilelor dintr-un proces in altul.

Pentru a comunica intre procese sistemul de operare Linux pune la dispozitia programatorului un set de mechanisme numit IPC (Inter Process Communication) ce va fi prezentat intr-un alt capitol.

Functilei getpid si getppid pentru determinarea pid-ului unui proces.

Functia getpid returneaza pid-ul procesului curent. Functia getppid returneaza pid-ul procesului parinte. Semnaturile functiilor sunt:

#include <sys/types.h>

  #include <unistd.h>


  pid_t

  getpid(void);


  pid_t

  getppid(void);           

Lansarea in executie a programelor folosind functiile de tip  exec.

In cadrul librariilor sistem linux exista definit un set de functii ce incep cu prefixul exec (execl, execv, execlp, execvp, execle si execve) care sunt utilizate pentru a lansa in executie dintr-un proces un program. Semnatura functiei execve este data in continuare (restul functiilor au semnaturi asemanatoare):

#include <unistd.h>


  int

  execve(const char *path, char *const argv[], char *const envp[]);

Spre deosebire de functia fork, care are ca efect construirea unui nou proces, functiile exec realizeaza inlocuirea procesului apelant cu un nou proces. Procesul nou are aceiasi intrare in tabela de procese (acelasi PID). Daca apelul exec se desfasoara normal, programul vechi este pierdut. Instructiunile ce urmeaza instructiunii exec nu se vor mai executa.

In mod uzual functiile exec se folosesc impreuna cu functia fork pentru a crea procese copil si a lansa in executie in cadrul acestora noi programe. Folosind aceasta tehnica codul proceselor copil poate fi implementat in fisiere sursa diferite fata de procesul parinte.

pid_t child_pid = fork ();

if (child_pid == 0)

Terminarea proceselor folosind functia exit.

Terminarea unui proces de catre el insusi se realizeaza prin apelul functie exit. Semnatura functiei este:

#include <unistd.h>

Void

exit(int status);

 Daca apelul functie exit lipseste din program, aceasta este totusi executata la revenirea din functia main.

Asteptarea terminarii proceselor folosind functiile wait si waitpid

Functiile wait si waitpid sunt folosite pentru a pune un proces parinte in asteptare pana cand un proces fiu isi termina activitatea.  Semnatura functiei este:

#include <sys/types.h>

  #include <sys/wait.h>


  pid_t

  wait(int *status);


  pid_t

  waitpid(pid_t wpid, int *status, int options);

In unele situatii este utila punerea in asteptare a unui proces parinte pana cand unul sau mai multe procese fiu isi termina activitatea. In astfel de situatii se folosesc functiile wait si waitpid.

Functia wait pune in asteptare procesul apelant pana cand unul dintre procesele fiu isi termina executia. Functia waitpid pune in asteptare procesul apelant pana cand un proces fiu cu un pid dat isi termina activitatea.


Valoarea returnata de functia wait este pid-ul procesului fiu ce si-a terminat executia. In variabila status este returnata o valoare ce semnifica cauza treminarii procesului fiu. Daca in momentul apelarii functiei wait procesul nu avea nici un proces fiu atunci aceasta returneaza valoarea -1.



Fire de executie


1. Obiective

Crearea si distrugerea firelor de executie

Sincronizarea firelor de executie

2. Notiuni teoretice

Ce sunt firele de executie?

Firele de executie sunt un mecanism ce permite unui program executarea simultan a doua sau mai multe secvente de cod in mod concurent.

In momentul lansarii in executie a unui program, nucleul creeaza un proces si apoi in cadrul acelui proces va construi un fir de executie in cadrul caruia se va rula programul. Acest fir de executie poate la randul lui crea alte fire de executie.

Un fir de executie trebuie vazut ca un flux de instructiuni care se executa in interiorul unui proces. Un proces poate sa fie format din mai multe asemenea fire, care se executa in paralel, avand, insa, in comun toate resursele principale caracteristice procesului. Prin urmare, in interiorul unui proces, firele de executie sunt entitati care ruleaza in paralel, impartind intre ele zona de date si executand portiuni distincte din acelasi cod. Deoarece zona de date este comuna, toate variabilele procesului vor fi vazute la fel de catre toate firele de executie, orice modificare facuta de catre un fir devenind vizibila pentru toate celelalte.

Spre deosebire de firele de executie, procesele sunt complet independente si nu exista posibilitate ca dintr-un proces sa se acceseze zona de date sau de cod a altui proces.

  Un proces, imediat ce a fost creat, este format dintr-un singur fir de executie, numit fir de executie principal (initial).

  Toate firele de executie din cadrul unui proces se vor executa in paralel.

  Datorita faptului ca impart aceeasi zona de date, firele de executie ale unui proces vor folosi in comun toate variabilele globale. De aceea, se recomanda ca in programe firele de executie sa utilizeze numai variabilele locale, definite in functiile care implementeaza firul, in afara de cazurile in care se doreste partajarea explicita a unor resurse.

  Daca un proces format din mai multe fire de executie se termina ca urmare a primirii unui semnal, toate firele de executie ale sale se vor termina.

  Daca un fir de executie apeleaza functia exit( ), efectul va fi terminarea intregului proces, cu toate firele de executie din interior.

  Orice functie sau apel sistem care lucreaza cu sau afecteaza procese, va avea efect asupra intregului proces, indiferent de firul de executie in care a fost apelata functia respectiva. De exemplu, functia sleep( ) va 'adormi' toate firele de executie din proces, inclusiv firul de executie principal (initial), indiferent de firul care a apelat-o.

 Compilarea programelor C ce contin fire de executie se face specificand optiunea –lpthread in cadrul linie de comanda. Exemplu:

gcc –lpthread test.c

Crearea firelor de executie

 Pentru a crea un fir de executie se foloseste functia pthread_create. Semnatura functie este:


  #include <pthread.h>


  int

  pthread_create(pthread_t *thread, const pthread_attr_t *attr,

void *(*start_routine)(void *), void *arg);

Functia creeaza un fir de executie. Noul fir va executa functia start_routine care trebuie definita in program avand un singur argument de tip  (void *). Parametrul arg este argumentul care va fi transmis acestei functii. Parametrul attr este un cuvant care specifica diferite optiuni de creare a firului de executie. In mod obisnuit, acesta este dat ca NULL, acceptand optiunile implicite. Firul de executie creat va primi un identificator care va fi returnat in variabila indicata de parametrul thread. Functia returneaza 0 daca crearea a avut succes si un numar diferit de zero in caz contrar.


Terminarea firelor de executie

 Pentru a termina executia unui fir se foloseste functia pthread_exit. Semnatura functie este:


  #include <pthread.h>


  void

  pthread_exit(void *value_ptr);

Valoarea retval este valoarea pe care firul o returneaza la terminare. Starea returnata de firele de executie poate fi preluata de catre oricare dintre firele aceluiasi proces, folosind functia pthread_join.

 Observatii:

  • Un proces, imediat ce a fost creat, este format dintr-un singur fir de executie, numit fir de executie principal (initial).
  • Toate firele de executie din cadrul unui proces se vor executa in paralel.
  • Datorita faptului ca impart aceeasi zona de date, firele de executie ale unui proces vor folosi in comun toate variabilele globale. De aceea, se recomanda ca in programe firele de executie sa utilizeze numai variabilele locale, definite in functiile care implementeaza firul, in afara de cazurile in care se doreste partajarea explicita a unor resurse.
  • Daca un proces format din mai multe fire de executie se termina ca urmare a primirii unui semnal, toate firele de executie ale sale se vor termina.
  • Daca un fir de executie apeleaza functia exit( ), efectul va fi terminarea intregului proces, cu toate firele de executie din interior.
  • Orice functie sau apel sistem care lucreaza cu sau afecteaza procese, va avea efect asupra intregului proces, indiferent de firul de executie in care a fost apelata functia respectiva. De exemplu, functia sleep( ) va 'adormi' toate firele de executie din proces, inclusiv firul de executie principal (initial), indiferent de firul care a apelat-o.
  • Standardul POSIX defineste si cateva primitive de sincronizare intre firele de executie pentru accesul la resursele comune, care nu fac obiectul lucrarii de fata.



Sincronizarea proceselor prin semafoare- Linux

1. Obiective

2. Notiuni teoretice

S-a vazut in laboratorul anterior modul in care se pot crea procese in Linux folosind functia fork. Un proces astfel creat poate sa execute un anumit set de activitati fara a interactiona cu alte procese. De multe ori insa, apare necesitatea ca un proces sa comunice cu alte procese aflate in executie in cadrul aceluiasi sistem.

Deoarece procesele sunt entitati complet independente ce nu isi pot accesa decat propria zona de date si de cod, sistemul de operare Linux pune la dispozitia programatorului o serie de mecanisme pe care le poate utiliza pentru a realiza schimbul de dare intre procese.

Cel mai simplu mecanism de comunicare este cel prin fisiere standard. Doua procese deschid un fisier in din care datele sunt scrise sau citite. Problema sincronizarii intre cele doua procese, pentru a asigura consistenta datelor revine programatorului.

Un alt mecanism de comunicare bazat pe fisiere este comunicarea prin pipe-uri (cu nume sau fara nume). Pipe-urile sunt fisiere speciale gestionate de catre nucleul sistemului de operare. Pipe-urile pot fi asemanate cu niste conducte care conecteaza doua procese comunicante. Printr-un capat al pipe-ului datele sunt scrise de catre un proces iar prin celalalt capat datele sunt citite de un alt proces.

Pe langa mecanismele clasice de comunicare prin fisiere amintite anterior, Linux ofera prin intermediul Inter Process Communication Package (IPC) o serie de mecanisme avansate de comunicare intre procese. Aceste mecanisme sunt: semafoarele, cozile de mesaje si zonele de memorie comuna.

In cadrul acestui laborator  se va detalia mecanismul de sincronizare intre procese folosind semafoarele IPC.


Semafoare Linux

Ce este un semafor?

Semafoarele reprezinta un mecanism prin care se poate controla accesul a doua sau mai multe procese la o resursa comuna. De exemplu in situatia in care doua procese trebuie sa acceseze acelasi fisier pentru a scrie si citi date, pentru a asigura consistenta datelor, trebuie sa se foloseasca un mecanism de excludere mutuala, astfel incat, atata timp cat unul dintre procese se afla in zona de cod in care se realizeaza operatii asupra continutului fisierului, celalalt proces sa nu poata accesa respectivul fisier. 

In esenta un semafor este un numar intreg care poate fi incrementat respectiv decrementat de catre doua sau mai multe procese prin intermediul unei functii speciale. Aceasta functia asigura de asemenea si blocarea sau deblocarea proceselor in momentul in care valoarea semaforului atinge o anumita limita.

 Cum se creeaza un semafor?

Functia sistem prin intermediul careia se construieste un set de semafoare este functia semget. Semnatura functiei este urmatoarea:

#include <sys/types.h>

#include <sys/ipc.h>

  #include <sys/sem.h>


  int

  semget(key_t key, int nsems, int flag);

Functia semget construieste un set format din unul sau mai multe semafoare. Desi pentru a sincroniza doua procese intre ele este suficient un singur semafor, pentru operatii mai complexe de sincronizare (stabilirea ordinii de executie a unor zone critice) sau pentru cazurile in care exista mai multe zone critice ce trebuie sincronizate prin semafoare diferite, se pot construi seturi cu mai multe semafoare.

Argumentul key, reprezinta o cheie pe baza careia se genereaza un identificator unic pentru setul de semafoare. Argumentul nsemn specifica numarul de semafoare care vor fi create in cadrul setului. Argumentul flag specifica drepturile de acces asupra setului de semafoare si daca sa se creeze un nou set de semafoare sau doar sa se caute in sistem existenta unui set creat anterior cu cheia key si sa returneze id-ul acelui set.


Iata o secventa ce construieste un set format din 10 semafoare:

#include <sys/ipc.h>

#include <sys/sem.h>


int semid;

semid = semget(100, 10, 0666 | IPC_CREAT);

Daca se doreste obtinerea id-ului unui set existent deja in sistem atunci al doilea si al treilea argument pot avea valoarea 0.

Toate semafoarele din set sunt initializate la valoarea 0. Primul semafor din set are indexul 0.

Cum se foloseste un semafor?

Odata obtinut id-ul unui set de semafoare (fie prin crearea, fie prin conectare la un set deja existent in sistem), se pot implementa mecanisme de sincronizare intre procese.

 Cele mai cunoscute primitive de sincronizare sunt  P si V si sunt prezentate in continuare:

P(x)


V(x)



Unde prin v este identificat un semafor, iar e(x) reprezinta valoarea semaforului v.

 Pentru realizarea unor regiuni critice se seteaza valoarea initiala a semaforului la 1, si regiunile critice sunt delimitate de apelul primitivelor P si V.

 Primitivele de sincronizare P si V pot fi implementate folosind functia semop. Prin intermediul functie  semop un proces poate incrementa (marcheaza o resursa ca fiind folosita) sau poate decrementa (marcheaza o resursa ca fiind eliberata) valoarea unui semafor din set.

 Semnatura functiei semop este:

#include <sys/types.h>

  #include <sys/ipc.h>

  #include <sys/sem.h>


  int

  semop(int semid, struct sembuf *array, size_t nops);

Argumentul semid reprezinta id-ul setului de semafoare (returnat de functia semget) asupra caruia se aplica operatia. Argumentul array este un pointer catre un vector de elemente de tip sembuf (structura sembuf va fi descrisa in pargraful urmator), argumnetul noops specifica cate elmenete de tip sembuf se gasesc in vectorul array (daca de exemplu se aplica o singura operatie asupra structurii de semafoare atunci noops va avea valoarea 1).

 Revenind la cel de-al doilea argument al functiei semop, acesta specifca ce operatii se aplica asupra  semafoarelor din set. Structura sembuf are forma:

  struct sembuf ;

Unde sem_num specifica numarul semaforului din set asupra caruia se va aplica operatia, sem_op specifica operatia care se va aplica semaforului (la valoarea curenta a semaforului va fi adaugata valoarea specificata de sem_op) iar argumentul sem_flg specifica modul de comportament al functiei semop in momentul in care semaforul este incrementat sau decrementat (vezi detalii in cadrul paginii man asociate functiei semop).

O proprietate foarte importanta a functiei semop  este ca aceasta se executa in mod atomic, adica sau sunt efectuate simultan toate operatiile coprinse in vectorul de operatii array, daca acest lucru este posibil sau nu se efectueaza nici una dintre ele si procesul este blocat.


Modul de implementare si de folosire a celor doua primitive de sincronizare P si V este ilustrat in programul de mai jos.

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>


void semcall int sid,int op)

void p(int sid


void v(int sid



void main()

if(fork()==0)

while(wait()!=-1);

semctl(semid, 0, IPC_RMID, 0);

Functiile p si v sunt folosite in cadrul celor doua procese fiu pentru delimitarea unor secvente de cod critice. Ca urmare a acestei delimitari, la un moment dat doar unul dintre cele doua procese se poate afla in executia zonei delimitate de apelul functiilor p si v, celalalt proces fiind blocat si pus in coada de asteptare. 

Pentru ca mecanismul de sincronizare sa functioneze corect este foarte important ca valoarea semaforului sa fie initializata la 1 inainte de a lansa in executie cele 2 procese, in caz contrar nici unul dintre procese nu va primi permisiunea de a intra in zona critica (in cazul programul de mai sus initializarea semaforului la valoarea 1 se face prin apelul functiei v inainte de a construi cele doua procese fiu).

 Nu exista nici o garantie cu privire la ordinea in care cele doua procese vor executa zonele lor critice. Primul dintre procese care reuseste sa execute functia p de decrementare a semaforului va intra in executia zonei critice.

 Cum se efectueaza alte operatii asupra seturilor de semafoare?

Functia semctl are rolul de a efectua o serie de operatii asupra structurii asociate unui set de semafoare. Semnatura functiei este:

#include <sys/types.h>

  #include <sys/ipc.h>

  #include <sys/sem.h>


  int

  semctl(int semid, int semnum, int cmd, );

 Functia semctl este utilizata de cele mai multe ori pentru setarea valorii initiale a unui semafor si pentru stergerea unei structuri de semafoare.

Pentru a seta valoarea initiala a unui semafor se foloseste functia semctl sub forma:

semctl(semID,0, SETVAL,1);

Pentru a sterge o structura de semafoare se foloseste functia semctl sub forma:

semctl(semID,0,IPC_RMID,0);

 Deoarece semafoarele sunt resurse de nucleu externe proceselor este important ca dupa terminarea utilizarii unei structuri de semafoare aceasta sa fie stearsa, in caz contrar ea ramanand rezidenta in memorie chiar si dupa terminarea procesului care a creato.



Sincronizarea firelor de executie


1. Obiective

Sincronizarea firelor de executie.

2. Notiuni teoretice

Notiunile de baza referitoare la fire de executie (crearea si distrugerea) au fost prezentate in laboratorul anterior. Reaminitim faptul ca un fir de executie trebuie vazut ca un flux de instructiuni care se executa in interiorul unui proces. Un proces poate sa fie format din mai multe asemenea fire, care se executa in paralel, avand, insa, in comun toate resursele principale caracteristice procesului. Prin urmare, in interiorul unui proces, firele de executie sunt entitati care ruleaza in paralel, impartind intre ele zona de date si executand portiuni distincte din acelasi cod.

Compilarea programelor C ce contin fire de executie se face specificand optiunea –lpthread in cadrul linie de comanda. Exemplu:

gcc –lpthread test.c

Sincronizarea firelor de executie

 Libraria de fire de executie ofera urmatoarele mecanisme de sincronizare:

Join – pune in asteptare un fir pana cand un alt fir isi termina activitatea;

Mutex – componente pentru excludere mutuala. Blocheaza accesul unui fir de executie la o anumita secventa de cod atata timp cat alt fir executa acea secventa;

Variable de contitie – asigura executia sau blocarea unui fir in functie de o anumita conditie.

 Sincronizarea folosind join

Functia pthread_join intrerupe executia firului apelant pana cand firul identificat prin  variabila thread isi termina executia, moment in care starea lui va fi salvata la adresa value_ptr.


Semnatura functie pthread_join este:

 #include <pthread.h>


  int

  pthread_join(pthread_t thread, void **value_ptr);

 Sincronizarea folosind blocuri de excludere mutuala

Blocurile de excludere mutuala sunt utilizate pentru a preveni situatii de inconsistenta a datelor ca urmare a accesul simultan a doua sau mai multe fire la o anumita secventa critica (de exemplu doua fire executa simultan operatii asupra aceleiasi zone de memorie). Componentele de excludere mutuala pot fi folosite doar pentru sincronizarea intre ele a firelor din cadrul aceluiasi proces.

Pentru a realiza excluderea mutuala libraria pentru fire de executie pune la dispozitia programatorului functiile: pthread_mutex_lock si pthread_mutex_unlock. Semnatura celor doua functii este prezentata in continuare:

#include <pthread.h>


  int

  pthread_mutex_lock(pthread_mutex_t *mutex);

si

  #include <pthread.h>


  int

  pthread_mutex_unlock(pthread_mutex_t *mutex);

Dupa cum se observa din semnatura celor doua functii, acestea lucreaza cu o variablia pthread_mutex_t, care reprezinta de fapt monitorul, sau semaforul care permite realizarea excluderii mutuale. Initializarea pthread_mutex_t se face astfel:

pthread_mutex_t mutex;

pthread_mutex_init (&mutex, NULL);

Sau variabila mutex poate fi initializata cu o valoare speciala PTHREAD_MUTEX_INITIALIZER, caz in care nu mai este nevoie sa se apeleze functia pthread_mutex_init. Codul de mai sus poate fi rescris astfel:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;


O secventa de cod ce foloseste excludere mutuala este prezentat in continuare:

 pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;

int counter=0;


/* Function C */

void functionC()


Se defineste ca fiind zona critica, zona de cod cuprinsa intre apelul functiei pthread_mutex_lock si pthread_mutex_unlock.

Daca functia functionC() este apelata simultan de doua sau mai multe fire de executie, doar unul dintre procese se poate afla la un moment dat in executie in zona critica.

 Sincronizarea folosind variabile conditionale

O variabila conditionala este o variabila de tipul pthread_cond_t folosita pentru blocarea respectiv deblocarea unui anumit fir. Mecanismul cu variabile conditionale permite firelor de executie sa isi suspende activitatea pana cand o anumita conditie este indeplinita.

Exista trei operatii de baza ce pot fi aplicate pe o variabila conditionala: wait, signal si broadcast. Operatia wait face ca firul apelant sa se blocheze in asteptarea aparitiei evenimentului asociat variabilei de conditie. Celelalte doua operatii permit unui fir de executie sa semnaleze generarea (sau sesizarea) evenimentului sau indeplinirea conditiei asociate. Diferenta dintre ele consta in faptul ca signal va anunta (va debloca) un singur fir din coada de asteptare iar broadcast va debloca toate firele.

O variabila conditionala trebuie asociata intotdeauna cu un element de excludere mutuala pentru a evita aparitia situatiilor de interblocaje.

 Functiile de lucru cu variabilele conditionale sunt:

Creare si distrugere

o pthread_cond_init – initializarea variabilei conditionale (ca si alternativa variabila conditionala poate fi initializata cu valoarea PTHREAD_COND_INITIALIZER);

o pthread_cond_destroy – distruge variabila conditionala;

Blocare pentru asteptarea unei conditii

o pthread_cond_wait – blocheaza firul de executie apelant pe o variabila conditionala pana in momentul in care acesta receptioneaza un semnal de la un alt fir de executie ( prin apelarea functiilor de semnalizare a indeplinirii conditiei)

o pthread_cond_timedwait – este similara cu functia anterioara. In plus aceasta functie genereaza eroare daca timpul absolut dat ca si argument a expirat inainte de receptionarea unui semnal, sau daca timpul absolut curent este mai mare decat timpul absolut dat ca si argument.

Semnalizare indeplinire conditie

o phread_cond_signal – deblocheaza un thread blocat pe o anumita variabila conditionala.

o phread_cond_broadcast – transmite semnal de deblocare pentru toate firele blocate pe o anumita variabila conditionala.

 Semnaturile functiilor prezentate mai sus sunt prezentate in continuare:

Creare si distrugere:

  #include <pthread.h>


int pthread_cond_destroy(pthread_cond_t *cond);


  int pthread_cond_init(pthread_cond_t *restrict cond,

  const pthread_condattr_t *restrict attr);


  pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

 Blocare pentru asteptarea unei conditii:

  #include <pthread.h>


int pthread_cond_timedwait(pthread_cond_t *restrict cond,

  pthread_mutex_t *restrict mutex,

  const struct timespec *restrict abstime);

int pthread_cond_wait(pthread_cond_t *restrict cond,

  pthread_mutex_t *restrict mutex);


Semnalizare indeplinire conditie:

  #include <pthread.h>


int pthread_cond_broadcast(pthread_cond_t *cond);

int pthread_cond_signal(pthread_cond_t *cond);



IPC – Zone de memorie comuna


1. Obiective

2. Notiuni teoretice

In cadrul acestui laborator va fi detaliat mecanismul de comunicare intre procese prin zone de memorie comuna pus la dispozitie de pachetul IPC.

Zone de memorie comuna

Ce este o zona de memorie comuna?

Zonele de memorie comuna reprezinta cel mai rapid mecanism de comunicare intre procese pus la dispozitie de pachetul IPC. Principiul de functionare este simplu: un proces construieste o zona de memorie la care se pot conecta 2 sau mai multe procese – acestea pot sa scrie si sa citeasca date in din zona de memorie.

Este responsabilitatea programatorului sa asigure consistenta datelor din zona de memorie prin implementarea unui mecanism de excludere mutuala intre procesele care doresc sa accese simultan zona de memorie. Desigur nu apar probleme atunci cand doua procese incearca sa citeasca in mod concurent zona de memorie, ci problema apare atunci cand un proces scrie un set de date si un alt proces citeste continutul zonei de memorie.

In cadrul aceste prezentari, pentru sincronizarea accesului la zona de memorie comuna, se vor folosi semafoarele din pachetul IPC (mecanism prezentat intr-o alta lucrare).

 Cum se construieste o zona de memorie comuna?

Pentru a construi o zona de memorie sau pentru a obtine id-ul unei zone de memorie existente se foloseste functia sistem shmget. Semnatura functiei este:

#include <sys/types.h>

  #include <sys/ipc.h>

  #include <sys/shm.h>


  int

  shmget(key_t key, int size, int shmflg);


 Argumentul size specifica dimensiunea in octeti a zonei de memorie comuna ce va creata. Argumentul shmflg are aceiasi semnificatie ca si in cazul cozilor de mesaje.

 Alocarea unei zone de memorie de 1024 octeti se face astfel:

int shm_id

shm_id = shmget(100, 1024)

Pentru a putea utiliza o zona de memorie (pentru a scrie si citi date) un proces trebuie sa isi ataseze zona de memorie la propria sa zona de memorie.

 Atasarea zonei de memorie la propria zona de memorie de catre un proces se face folosind functia shmat. Semnatura functiei este:


  #include <sys/types.h>

  #include <sys/ipc.h>

  #include <sys/shm.h>


  void *

  shmat(int shmid, void *shmaddr, int shmflg);


  int

  shmdt(void *shmaddr);

Functia shmat returneaza adresa de inceput a zonei de memorie comunca. Argumentul shmaddr reprezinta adresa din zona de memorie a procesului unde functia shmat sa incerce sa mapeze  zona de memorie comuna – daca acest argument este 0 se va selecta automat o adresa disponibila. Argumentul shmflg permite setarea flag-ului SHM_RDONLY ce ataseaza zona de memorie in mod citire exclusiva (nu se permite scrierea in zona de memorie comuna).

Un exemplu de utilizare al functiei shmat este:

char* shm_addr;

/* attach the given shared memory segment, at some free position */

shm_addr = shmat(shm_id, NULL, 0);

 Dupa ce un proces a terminat de utilizat o zona de memorie aceasta trebuie detasata de la procesul curent folosind functia shmdt (semnatura acesteia a fost prezentata mai sus impreuna cu functia shmat).

 Cum se scrie intr-o zona de memorie comuna?


Dupa ce un proces si-a atasat o zona de memorie si a obtinut adresa de inceput a acesteia, datele pot fi scrise si citite in din zona de memorie comuna. In cadrul zonei de memorie pot fi scrise orice tip de date (cu exceptia pointerilor – care sunt la randul lor niste adrese).

 In continuare este dat un scurt exemplu despre modul cum datele pot fi scrise intr-o zona de memotie comuna. Practic se poate folosi orice metoda cunoscuta in C pentru a scrie date la o adresa cunoscuta.

int shmid;

char *adr_zona;


shmid=shmget(123, 0, 0);

adr_zona=shmat( shmid, NULL, 0);

strcpy( adr_zona, 'SCRIE');


 Cum se citeste dintr-o zona de memorie comuna?

Pentru a citi date dintr-o zona de memorie comuna, procesul trebuie sa aiba atasata zona de memorie la propria sa zona de memorie si apoi se poate folosi orice metoda cunoscuta  pentru citi date de la o adresa.

 Cum se realizeaza alte operatii asupra unei zone de memorie comuna?

Functia de sistem shmctl are rolul de a efectua asupra zonei de memorie diverse operatii: obtinerea de informatii despre structura, modificarea informatiilor structurii si stergerea structurii. Semnatura functiei shmctl este:

#include <sys/types.h>

  #include <sys/ipc.h>

  #include <sys/msg.h>


  int

  shmctl(int shmid, int cmd, struct shmid_ds *buf);

Un exemplu de folosire este pentru stergerea zonei de memorie:

shmctl(shmid,IPC_RMID,0);


IPC - Cozi de mesaje


1. Obiective

2. Notiuni teoretice

In cadrul acestui laborator va fi detaliat mecanismul de comunicare intre procese prin cozi de mesaje puse la dispozitie de pachetul IPC.

Cozile de mesaje

Ce este o coada de mesaje?

 O coada de mesaje este o zona in care pot fi adaugate mesaje de catre un proces, de unde mai apoi pot fi extrase de catre acelasi proces sau de catre un alt proces. Mesajele sunt identificate printr-un tip si un continut. Scrierea si citirea mesajelor nu se face dupa regula FIFO, procesul cititor putand extrage din coada un mesaj de un anumit tip.

Cum se creeaza o coada de mesaje?

Functia sistem pentru crearea de cozi de mesaje este msgget. Semnatura functiei este:

#include <sys/types.h>

  #include <sys/ipc.h>

  #include <sys/msg.h>


  int

  msgget(key_t key, int msgflg);

 Argument key reprezinta un intreg pozitiv pe baza  caruia va fi generat un ID unic pentru coada de mesaje construita. Daca valoarea cheii este IPC_PRIVATEA atunci coada de mesaje construita va putea fi accesata doar de catre procesul creator al cozii si de catre fii acestuia (se spune despre coada ca este privata).

 Argumentul msgflag poate sa contina flag-uri cum ar fi IPC_CREAT sau IPC_EXCL. Daca falg-ul IPC_EXCL este setat si o coada cu cheia key  exista deja in sistem, atunci functia va returna mesaj de eroare. Daca falgul IPC_CREAT este setat atunci functia construieste o noua structura de tip coada de mesaje sau returneaza ID-ul unei cozi existente construita cu aceiasi cheie in cazul in care flagul IPC_EXCL nu a fost setat.


Tot in cadrul argumentului msgflag sunt setate si drepturile de acces asupra cozii de forma 0xyz. Unde 0 specifica faptul ca numarul este in baza opt, x specifica drepturile pentru utilizator, y specifica drepturile pentru grupul din care face parte utilizatorul si z drepturile pentru restul utilizatorilor (similar cu modul in care sunt stabilite drepturile pentru fisierele standard Linux).

O secventa de cod care demonstreaza modul in care se construieste o coada de mesaje este:

#include <stdio.h>  /* standard I/O routines.*/

#include <sys/types.h> /* standard system data types. */

#include <sys/ipc.h>/* common system V IPC structures.*/

#include <sys/msg.h>/* message-queue specific functions. */


int queue_id msgget(101, 0660|IPC_CREAT); */

if (queue_id == -1)


 Cum se adauga un mesaj intr-o coada de mesaje?

Inainte de a adauga mesaje in coada trebuie sa stim care este formatul mesajelor. In cadrul IPC este definita structura msgbuf care precizeaza structura mesajelor:

struct msgbuf

 Atributul mtype specifica tipul mesajului si acesta trebuie sa fie un intreg pozitiv. Al doilea atribut, mtext, este un vector de caractere ce va memora continutul efectiv al mesajului.

 Nu suntem limitati la aceasta structura de mesaje. Daca dorim sa transmitem mesaje cu o alta structura putem defini propria structura. De exemplu:

struct mymsgbuf


Singurul atribut pe care trebuie sa il contina in mod obligatoriu structura este mtype de tip long care specifica tipul mesajului.

Odata stabilita structura mesajelor care vor fi scrise in coada putem scrie mesaje folosind functia msgsnd. Semnatura functie este:


  #include <sys/types.h>

  #include <sys/ipc.h>

  #include <sys/msg.h>


  int

  msgsnd(int msqid, void *msgp, size_t msgsz, int msgflg);


Unde argumentul msgid reprezinta id-ul cozii de mesaje returnat de functia msgget, argumentul msgp reprezinta un pointer la mesajul ce va fiscris, argumentul msgsz reprezinta dimensiunea efectiva a mesajului ce va fi scris (fara tipul mesajului) iar argumentul msgflg reprezinta un flag care specifica comportamentul functiei msgsnd atunci cand se incearca scrirea unnui mesaj intr-o coda plina. Daca flag-ul IPC_NOWAIT este setat si coada este plina atunci functia returneaza mesaj de eroare. Daca flag-ul nu este setat functia blocheaza procesul apelant pana cand mesajul poate fi scris in coada.

 Secventa urmatoare exemplifica transmiterea intr-o coada a unui mesaj de tip definit mai sus:

struct mymsgbuf msg;

char* text=”un mesaj”;


msg.mtype = 1;

strcpy(msg.mtext,text);

msgsnd(msgid,&msg,strlen(text),IPC_NOWAIT)l


Cum se extrage un mesaj dintr-o coada de mesaje?

Pentru a extrage un mesaj din coada de mesaje se foloseste functia msgrcv. Semnatura functiei este:

#include <sys/types.h>

  #include <sys/ipc.h>

  #include <sys/msg.h>


  int

  msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);



Argumentul msgp este un pointer catre o structura prealocata in care se va salva mesajul citit, argumentul msgsz este dimensiunea maxima a mesajului ce va fi citit, asrgumentul msgtyp este tipul mesajului ce va fi citit. Tipul mesajului poate fi:

-0 – caz in care primul mesaj din coada va fi returnat;

-Un intreg pozitiv – caz in care un mesaj de tipul respectiv va fi returnat (in cazul in care flagul msgflg este setat corespunzator);

-Un intreg negativ – caz in care un mesaj cu tipul mai mare sau egal cu valoarea absoluta a argumentului este returnat (in cazul in care flagul msgflg este setat corespunzator);

 Argumentul msgflg specifica comportamentul functiei in conditiile in care mesajul de tipul cerut nu exista in coada:

-daca IPC_NOWAIT este setat functia returneaza eroare daca nu exista mesaj de tipul cerut in coada. Daca nu este setat functia blocheaza procesul apelant pana cand un mesaj de tipul cerut este adaugat in coada sau pana cand coada este distrusa;

-daca MSG_EXCEPT este setat si tipul mesajului cerut este un intreg pozitiv atunci returneaza primul mesaj al carui tip NU este egal cu tipul specificat;

-daca MSG_NOERROR este setat si mesajul citit are continutul mai mare decat cel specificat prin msgsz atunci truncheaza mesajul. Daca falg-ul nu est especificat returneaza mesaj de eroare.

Secventa de cod ce exemplifica citirea unui mesaj din coada este data in continuare:

msgrcv(msgid,&mes,25,1,IPC_NOWAIT|MSG_ERROR);

 Cum se realizeaza alte operatii asupra cozii de mesaje?

Functia de sistem msgctl are rolul de a efectua asupra structurii asociate cozii diverse operatii: obtinerea de informatii despre structura, modificarea informatiilor structurii si stergerea structurii. Semnatura functiei semctl este:

#include <sys/types.h>

  #include <sys/ipc.h>

  #include <sys/msg.h>

int

  msgctl(int msqid, int cmd, struct msqid_ds *buf);

 Un exemplu de folosire este pentru stergerea structurii de mesaje: shmctl(shmid,IPC_RMID,0);



Contact |- ia legatura cu noi -| contact
Adauga document |- pune-ti documente online -| adauga-document
Termeni & conditii de utilizare |- politica de cookies si de confidentialitate -| termeni
Copyright © |- 2024 - Toate drepturile rezervate -| copyright