Sunteți pe pagina 1din 7

Comunicatia inter-procese in C sub Unix

Comunicatia prin canale interne (pipe-uri) :


1.
2.
3.
4.

Introducere
Canale interne. Primitiva pipe
Exercitii
Tipuri de comunicatie

Observatie: Programele referite in acest fisier se gasesc in directorul ~so/labs/Cprogs/pipe/ .

Introducere

Una dintre modalitatile de comunicare intre procese in Unix este cea prin intermediul
canalelor de comunicatie (numite pipes, in limba engleza). Practic este vorba despre o
"conducta" (un buffer) prin care pe la un capat se scriu mesajele, iar pe la celalalt
capat se citesc - deci este vorba despre o structura de tip coada, adica lista FIFO
(First-In,First-Out).
Aceste canale sunt de doua categorii:
pipe-uri interne:
aceste "conducte" sunt create in memoria interna a sistemului Unix respectiv;
pipe-uri externe:
aceste "conducte" sunt fisiere de un tip special, numit fifo, deci sunt pastrate in
sistemul de fisiere (aceste fisiere fifo se mai numesc si pipe-uri cu nume).
In aceasta lectie ne vom referi la canalele interne, iar in lectia fifo le vom trata pe cele
externe.

Canale interne. Primitiva pipe

Deci un canal intern este un canal aflat in memorie, prin care pot comunica doua sau
mai multe procese.
Crearea unui canal intern se face cu ajutorul functiei pipe.
Interfata functiei pipe este urmatoarea:

int pipe(int *p)

unde:
p = parametrul efectiv de apel trebuie sa fie un tablou int[2] ce va fi actualizat de
functie astfel:

va fi descriptorul de fisier deschis pentru capatul Read al canalului;


p[1] va fi descriptorul de fisier deschis pentru capatul Write al canalului;
p[0]

iar valoarea int returnata este 0, in caz de succes (daca s-a putut crea pipe-ul), sau -1,
in caz de eroare.
Efect: in urma executiei functiei pipe se creeaza un canal intern si este deschis la
ambele capete - in scriere la caparul referit prin p[0], respectiv in citire la caparul
referit prin p[1].
Dupa crearea unui canal intern, scrierea in acest canal si citirea din el se efectueaza la
fel ca pentru fisierele obisnuite. Si anume, citirea din canal se va face prin intermediul
descriptorului p[0] folosind functiile de citire uzuale (read,
respectiv fread sau fscanf daca se foloseste un descriptor de tip FILE*), iar scrierea in
canal se va face prin intermediul descriptorului p[1] folosind functiile de scriere
uzuale (write, respectiv fwrite sau fprintf daca se foloseste un descriptor de
tipFILE*).
Observatie importanta:
Pentru ca doua sau mai multe procese sa poata folosi un pipe pentru a comunica, ele
trebuie sa aiba la dispozitie cei doi descriptori p[0] si p[1] obtinuti prin creareapipeului, deci procesul care a creat pipe-ul va trebui sa le "transmita" cumva celuilalt
proces.
De exemplu, in cazul cind se doreste sa se utilizeze un canal intern pentru
comunicarea intre doua procese de tipul parinte-fiu, atunci este suficient sa se apeleze
primitivapipe de creare a canalului inaintea apelului primitivei fork de creare a
procesului fiu. In acest fel in procesul fiu avem la dispozitie cei doi descriptori
necesari pentru comunicare prin intermediul acelui canal intern.
La fel se procedeaza si in cazul apelului primitivelor exec (deoarece descriptorii de
fisiere deschise se mostenesc prin exec).
De asemenea, trebuie retinut faptul ca daca un proces isi inchide vreunul din capetele
unui canal intern, atunci nu mai are nici o posibilitate de a redeschide ulterior acel
capat al canalului.
Caracteristici si restrictii ale canalelor interne:

1. Canalul intern este un canal unidirectional, adica pe la capatul p[1] se scrie, iar
pe la capatul p[0] se citeste.
Insa toate procesele pot scrie la capatul p[1], si/sau sa citeasca la capatul p[0].
2. Unitatea de informatie pentru canalul intern este octetul. Adica, cantitatea
minima de informatie ce poate fi scrisa in canal, respectiv citita din canal, este
de 1 octet.
3. Canalul intern functioneaza ca o coada, adica o lista FIFO (= First-In,FirstOut), deci citirea din pipe se face cu distrugerea (consumul) din canal a
informatiei citite.
Asadar, citirea dintr-un pipe difera de citirea din fisiere obisnuite, pentru care
citirea se face fara consumul informatiei din fisier.
4. Dimensiunea (i.e. capacitatea) canalului intern este limitata la o anumita
dimensiune maxima (4ko, 16ko, etc.), ce difera de la un sistem Unix la altul.
5. Citirea dintr-un canal intern (cu primitiva read):
o Apelul read va citi din canal si va returna imediat, fara sa se blocheze,
numai daca mai este suficienta informatie in canal, iar in acest caz
valoarea returnata reprezinta numarul de octeti cititi din canal.
o Altfel, daca canalul este gol, sau nu contine suficienta informatie, apelul
de citire read va ramine blocat pina cind va avea suficienta informatie in
canal pentru a putea citi cantitatea de informatie specificata, ceea ce se
va intimpla in momentul cind alt proces va scrie in canal.
o Alt caz de exceptie la citire, pe linga cazul golirii canalului:
daca un proces incearca sa citeasca din canal si nici un proces nu mai
este capabil sa scrie in canal vreodata (deoarece toate procesele si-au
inchis deja capatul de scriere), atunci apelul read returneaza imediat
valoarea 0 corespunzatoare faptului ca a citit EOF din canal.
In concluzie, pentru a se putea citi EOF din pipe, trebuie ca mai intii toate
procesele sa inchida canalul in scriere (adica sa inchida
descriptorul p[1]).
Observatie: La fel se comporta si celelalte functii de citire (fread, fscanf, etc.)
la citirea din canale interne.
6. Scrierea intr-un canal intern (cu primitiva write):
o Apelul write va scrie in canal si va returna imediat, fara sa se blocheze,
numai daca mai este suficient spatiu liber in canal, iar in acest caz
valoarea returnata reprezinta numarul de octeti efectiv scrisi in canal
(care poate sa nu coincida intotdeauna cu numarul de octeti ce se doreau
a se scrie, caci pot apare erori I/O).
o Altfel, daca canalul este plin, sau nu contine suficient spatiu liber, apelul
de scriere write va ramine blocat pina cind va avea suficient spatiu liber

in canal pentru a putea scrie informatia specificata ca argument, ceea ce


se va intimpla in momentul cind alt proces va citi din canal.
Alt caz de exceptie la scriere, pe linga cazul umplerii canalului:
daca un proces incearca sa scrie in canal si nici un proces nu mai este
capabil sa citeasca din canal vreodata (deoarece toate procesele si-au
inchis deja capatul de citire), atunci sistemul va trimite acelui proces
semnalul SIGPIPE, ce cauzeaza intreruperea sa si afisarea pe ecran a
mesajului "Broken pipe".

Observatie: La fel se comporta si celelalte functii de scriere (fwrite, fprintf,


etc.) la scrierea in canale interne.
Observatie:
Cele afirmate mai sus, despre blocarea apelurilor de citire sau de scriere in cazul
canalului gol, respectiv plin, corespund comportamentului implicit, blocant, al
canalelor interne.
Insa, exista posibilitatea modificarii acestui comportament implicit, intr-un
comportament neblocant, situatie in care apelurile de citire sau de scriere nu mai
ramin blocate in cazul canalului gol, respectiv plin, ci returneaza imediat valoarea -1,
si seteaza corespunzator variabila errno.
Modificarea comportamentului implicit in comportament neblocant se realizeaza prin
setarea atributului O_NONBLOCK pentru descriptorul corespunzator acelui capat al
canalului intern pentru care se doreste modificarea comportamentului, prin
intermediul functiei fcntl. Spre exemplu, apelul:
fcntl(p[1],F_SETFL,O_NONBLOCK);

va seta atributul O_NONBLOCK pentru capatul de scriere al canalului intern referit de


variabila p.
Atentie: functiile de nivel inalt (fread/fwrite, fscanf/fprintf, etc.) lucreaza bufferizat. Ca atare, la scrierea intr-un canal folosind functiile fwrite, fprintf, etc.,
informatia nu ajunge imediat in canal in urma apelului, ci doar in buffer-ul asociat
acelui descriptor, iar golirea buffer-ului in canal se va intimpla abia in momentul
umplerii buffer-ului, sau la intilnirea caracterului '\n' in specificatorul de format al
functiei de scriere, sau la apelul functiei fflush pentru acel descriptor.
Prin urmare, daca se doreste garantarea faptului ca informatia ajunge in canal imediat
in urma apelulului de scriere cu functii de nivel inalt, trebuie sa se apeleze, imediat
dupa apelul de scriere, si functia fflush pentru descriptorul asociat capatului de
scriere al acelui canal.

Exemplul 1:
Urmatorul program exemplifica modul de utilizare a unui canal intern pentru
comunicatia intre doua procese, cu observatia ca programul foloseste functiile I/O de
nivel scazut (i.e., ne-buffer-izate) read si write pentru a citi din canal, respectiv pentru
a scrie in canal.
Citeste sursa programului pipe-ex1.c.
Efectul acestui program:
Procesul tata citeste un sir de caractere de la tastatura, sir terminat cu
combinatia CTRL+D (i.e., caracterul EOF in Unix), si le transmite procesului fiu, prin
intermediul canalului, doar pe acelea care sunt litere mici. Iar procesul fiu citeste din
canal caracterele trasmise de procesul parinte si le afiseaza pe ecran.
Exemplul 2:
Programul urmator este un alt exemplu de utilizare a unui canal intern pentru
comunicatia intre doua procese, cu observatia ca programul foloseste functiile I/O de
nivel inalt (i.e., buffer-izate) fscanf si fprintf pentru a citi din canal, respectiv pentru
a scrie in canal.
Citeste sursa programului pipe-ex2.c.
Efectul acestui program:
Procesul tata citeste un sir de numere de la tastatura, sir terminat cu
combinatia CTRL+D (i.e., caracterul EOF in Unix), si le transmite procesului fiu, prin
intermediul canalului. Iar procesul fiu citeste din canal numerele trasmise de procesul
parinte si le afiseaza pe ecran.
Observatie: deoarece acest program foloseste functiile I/O de nivel inalt, este necesara
conversia descriptorilor de fisiere de la tipul int la tipul FILE*, lucru realizat cu
ajutorul primitivei fdopen.
Alta observatie: in acest exemplu, numerele au fost scrise in canal ca text (i.e., ca
secventa a cifrelor care le compun) si nu ca reprezentare binara. Din acest motiv ele
trebuie separate printr-un caracter care nu este cifra (in program s-a folosit caracterul
'\n', dar poate fi folosit oricare altul), cu scopul ca aceste numere sa poata fi citite la
destinatar in mod corect, fara ca cifrele lor sa se "amestece" intre ele.

Exercitii

Exercitiul 1:
Scrieti un program care sa determine capacitatea canalelor interne pentru sistemul
Unix pe care il utilizati.
Idee de rezolvare: programul va crea un canal intern si apoi va scrie in mod repetat in
el, fara a citi nimic, si contorizind numarul de octeti scrisi efectiv in canal, pina cind
apelul de scriere va esua datorita umplerii canalului (atentie: trebuie sa setati atributul
neblocant pentru capatul de scriere al canalului, pentru ca apelul de scriere sa nu
ramina blocat in momentul umplerii canalului).

Exercitiul 2:
Rezolvati exercitiul 1 (problema Suma) din lectia fork folosind, pentru comunicatia
intre procese, canale interne in loc de fisiere obisnuite.

Tipuri de comunicatie

Iata citeva tipuri de comunicatie inter-procese ce pot apare la comunicatia prin canale
interne, si care pot ridica anumite probleme la implementarea lor:
Mesaje de lungime variabila:
In cazul transmiterii de mesaje cu lungimea variabila, acestea trebuie formatate
astfel:
mesaj = header + mesajul propriu-zis ,
partea de header fiind un mesaj de lungime fixa ce contine lungimea partii de
mesaj propriu-zis.
Pentru citirea din canal, fiecare proces va trebui sa respecte urmatorul protocol:
intii se citeste un header (deci un mesaj de lungime fixa), si apoi un mesaj de
lungime variabila, determinata din header-ul abia citit.
Mesaje nelimitate in situatia in care sistemul permite doar mesaje de lungime
limitata:
In cazul in care mediul de comunicatie impune o anumita limitare a lungimii
maxime pe care o pot avea mesajele transmise prin acel mediu, si se doreste
totusi transmiterea unor mesaje de lungime mai mare decit limita admisibila,
atunci mesajele trebuie "impartite" in pachete (= mesaje de lungime fixa, cel
mult egala cu limita admisibila), si atunci:
pachet = header + continutul propriu-zis al pachetului ,
partea de header continind un identificator al pachetului in cadrul mesajului din
care face parte.
Acest identificator al pachetului este necesar pentru refacerea mesajului din

pachete la destinatie, deoarece ordinea de emisie nu este obligatoriu sa coincida


cu ordinea in care sunt receptionate pachetele la destinatie.
Mesaje cu mai multi destinatari posibili:
In cazul in care mai multe procese folosesc acelasi canal, mesajele trebuie
formatate astfel:
mesaj = header + mesajul propriu-zis ,
partea de header fiind un mesaj de lungime fixa ce contine lungimea partii de
mesaj propriu-zis, un identificator al emitatorului si un identificator al
destinatarului.
Pentru citirea din canal, fiecare proces va trebui sa respecte urmatorul protocol:
daca a citit un mesaj care nu-i era destinat lui, il va scrie inapoi in canal.
Bineinteles, pot apare si combinatii ale celor trei situatii descrise mai sus.
Observatie: aceste situatii pot apare nu numai la comunicatia prin canale interne, ci la
orice fel de comunicatie (prin canale externe, prin fisiere obisnuite, etc.).

=================================== EOF
===================================

S-ar putea să vă placă și