Sunteți pe pagina 1din 8

Comunicaţia între procese (IPC)

1.1. Mecanismul IPC


Comunicaţia între procese este un mecanism prin care diferite procese
interacţionează şi comunică date între ele. Aplicaţiile, care folosesc IPC pentru
interacţiune pot fi împărţite în două categorii: aplicatii server şi aplicaţii client. Aplicaţia
server asigură aplicaţiei client serviciile care i-au fost cerute. Există mai multe
mecanisme IPC care sunt suportate de WIN32 SDK. Pipe-urile sunt unele din aceste
mecanisme. Pipe-urile permit transferul de date între procese într-o manieră FIFO.
Aplicaţia care creeează pipe-ul se numeste aplicaţie pipe server iar, aplicaţia care se
conectează la ea se numeşte aplicaţie pipe client.

Figure 1-1 - Cum este organizat un pipe

FIFO înseamnă că procesele citesc datele în aceeasi ordine în care a fost scrisă.
Sistemul se asigură că datele nu se pierd la mijloc (unul dintre procese iese prematur).
Odată citită din pipe data este ştearsă din pipe, eliberând astfel spatiu pentru procesul care
scrie în pipe.

Există două tipuri de pipe: pipe-uri nenumite (anonime) şi pipe-uri numite.

Un pipe nenumit este o conductă de date care transfera datele între procesele
înrudite (exemplu: între procesul tată şi fiu). Nu suportă comunicaţia în retea şi sunt
întotdeauna orientate byte-stream.

Un pipe numit asigură comunicaţia într-un sens sau în ambele între pipe server şi
pipe client. Poate fi folosita sa interacţioneze între procese nu neapărat înrudite pe maşini
diferite, în retea. [3]

Pipe-ul numit mai este întâlnit şi sub denumirea de pipe FIFO. Numele unui pipe
numit este de fapt un fişier în sistemul de fişiere. Pipe-urile numite sunt vizibile cu
comanda ls ca orice alt fişier cu câteva diferenţe:
% ls -l fifo1
prw-r--r-- 1 mconstan e214 0 May 22 20:15 fifo1|

Litera p din stânga indică faptul că fifo1 este un pipe, deasemenea caracterul | din
coloana dreaptă.

Pe sistemele Linux mai vechi pipe-urile numite se creau prin comanda mknod. Pe
sistemele moderne acest lucru se face prin comanda mkfifo ce primeşte unul sau mai
multe nume de fişiere ca argumente şi creează pipe-uri cu aceste nume. De exemplu
pentru a crea un pipe numit cu numele pipe1 se dă comanda:
mkfifo pipe

Pentru a evidenţia cum lucrează un pipe numit se execută în console separate comenzile :
ls -l > pipe
cat < pipe

Rezultatul primei comenzi se va afişa în cea de-a doua consolă. [4]

1.2. IPC (Modelul Master-Slave)

Acest mod de comunicaţie reprezintă o relaţie tată-fiu. Un proces care creează un


alt proces se numeste tată, procesele create se numesc fii. Procesul fiu, odata creat nu mai
depinde de procesul tată şi ruleaza independent. Procesul fiu are spatiu său de adresa
virtuală care este independent de spatiul tatalui. Un fiu poate moşteni handler-urile
deschise de la tată. Un handler moştenit în procesul fiu referă la acelaşi obiect ca handler-
ul original al tatălui. Când un proces fiu moşteneste handler-ul, sistemul de operare
asigură numai acces la procesul fiu. Un proces fiu are un nou spaţiu de adresă virtuală, în
consecintă tatăl comunică valoarea handler-ului fiului. Acestă valoare poate fi trimisă
prin mai multe căi:

 Prin argument în linie de comandă

 Prin obiecte de mapare fişier

 Prin pipe-uri sau prin canale standard de I/O

Moştenirea handler-ului determină procesul fiu să aibă intrările pentru toate handler-urile
mostenibile (care sunt deschise) în propria sa tabelă obiect. Odată ce valoarea handler-
ului este trimisă fiului printr-un mecanism IPC, acesta poate folosi de asemenea
handlerul. [3]

2
1.3. Comunicare în ambele sensuri folosind pipe-uri
În sisteme mai complexe, se descoperă că comunicarea într-un sens este prea limitată. Se
doreşte astfel să se comunice în ambele sensuri: de la tată la fiu şi de la fiu la tată. Acest
lucru se face relativ uşor folosind două pipe-uri, câte una în fiecare sens. Insă acest mod
poate duce la apariţia unei situaţii numită deadlock (blocare).

Deadlock-ul reprezintă o situaţie în care un grup de două sau mai multe procese aşteaptă
pentru un set de resurse care care sunt alocate altor procese din acelasi grup, ori după
evenimente care trebuie anuntate de alte procese din grup.

Aceasta situaţie poate fi întâlnită atunci când două procese comunică prin 2 pipe-uri. Sunt
prezentate 2 posibile scenarii:

 Amândouă pipe-urile sunt libere şi ambele procese încearcă să citească din


capătul de citire. Fiecare este blocat pe citire (pentru că pipe-ul este gol), şi vor
rămâne blocate nedefinit.

 Fiecare pipe are un buffer limitat asociat. Când un proces scrie în pipe, datele sunt
plasate în bufferul pipe-ului, până când este citit de procesul care citeste. Daca
bufferul este plin, apelul de sistem write(),se blocheaza pana se elibereaza
bufferul. Singura cale de eliberare este de a citi din buffer. Astfel daca ambele
procese scriu date, fiecare în capătul pipe-ul lui de scriere, ambele se vor bloca pe
apelul de sistem write(). Cum nici un alt proces nu mai citeste din pipe-uri, cele 2
procese vor intra într-o stare de deadlock. [1]

1.4. Exemple

Apelul pipe aşa cum reiese din manualul Linux:

NAME

pipe - create pipe

SYNOPSIS

#include <unistd.h>

3
int pipe(int filedes[2]);

DESCRIPTION

pipe creates a pair of file descriptors, pointing to a pipe inode, and places them in the
array pointed to by filedes. filedes[0] is for reading, filedes[1] is for writing.

RETURN VALUE

On success, zero is returned. On error, -1 is returned, and errno is set appropriately.

ERRORS

EMFILE Too many file descriptors are in use by the process.

ENFILE The system file table is full.

EFAULT filedes is not valid.

CONFORMING TO

SVr4, SVID, AT&T, POSIX, X/OPEN, BSD 4.3

SEE ALSO

read(2), write(2), fork(2), socketpair(2)

REFERENCED BY

csh(1), event(3), fifo(4), fstat(2), ksh(1), lstat(2), pdksh(1), popen(3), stat(2), syscall(2),
syscalls(2), tcsh(1) [5]

Un apel al funcţiei pipe() returnează o pereche de descriptori. Unul dintre aceşti


descriptori este conectat la capătul de scriere al pipe-ului iar, celălalt este conectat la
capătul de citire. Orice poate fi scris în pipe şi citit de la celalalt capăt în ordinea în care a
intrat. Pe majoritatea sistemelor, pipe-urile se umplu după ce s-a scris în ele aproximativ
10K fară a se citi nimic.

Un exemplu simplu de folosire a unui pipe nenumit (sau pipe anonim) sub Linux este
comanda:
ls | grep x

Când interpretorul examinează linia de comandă, găseşte caracterul | ce separă două


comenzi. Shell-ul execută ambele comenzi, conectând ieşirea prime la intrarea celei de a
două. Exemplul de mai sus foloseşte un pipe nenumit. Pipe-ul există doar în kernel şi nu
poate fi accesat de procese ce l-au creat, în acest caz shell-ul care l-a creat.

Un exemplu simplu de folosire a unui pipe numit (sau pipe FIFO ):

4
mkfifo pipe
ls -l > pipe
cat < pipe

Un exemplu care creează, scrie şi citeşte dintr-un pipe:


#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>

int main()
{
int pfds[2];
char buf[30];

if (pipe(pfds) == -1) // se testează reuşita apelului pipe


{
perror("pipe");
exit(1);
}

printf("writing to file descriptor #%d\n", pfds[1]);


write(pfds[1], "test", 5);
printf("reading from file descriptor #%d\n", pfds[0]);
read(pfds[0], buf, 5);
printf("read \"%s\"\n", buf);
}

Apelul pipe() primeşte un vector de 2 întregi (ints) că parametru. Presupunând că nu


apare nici o eroare, conectează 2 descriptori de fişiere şi îi întoarce într-un vector. Primul
element este capătul de citire iar, secundul cel de scriere.[ Figure 1 -1 ]

Vom folosi alt exemplu. Intâi procesul tata va crea un fiu. Vom apela fork(). Astfel fiul
va putea să trimită date spre capătul de scriere al pipe-ului, iar tatal le va primi la capătul
de citire astfel:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
int pfds[2];
char buf[30];

pipe(pfds);

if (!fork()) {
printf(" CHILD: writing to the pipe\n");
write(pfds[1], "test", 5);

5
printf(" CHILD: exiting\n");
exit(0);
} else {
printf("PARENT: reading from pipe\n");
read(pfds[0], buf, 5);
printf("PARENT: read \"%s\"\n", buf);
wait(NULL);
}
}

Rezultatul întors va arăta astfel:


PARENT: reading from pipe
CHILD: writing to the pipe
CHILD: exiting
PARENT: read "test"

În acest caz tatal încearcă sa citească din pipe înainte ca fiul să scrie în ea. Când acest
lucru se întâmplă, tatal trece în block sau sleep pâna urmeaza ca să sosească date pentru
citire. Tatăl încearcă să citească, trece în sleep, fiul scrie şi iese, tatăl se trezeste şi citeste
datele.

Exerciţiul 1:

Implementarea comenzii “ls | wc -l” în C.

Rezolvare:

Se vor folosi apelurile exec() şi dup(). Familia exec de funcţii înlocuieşte procesul curent
ce rulează cu orice proces îi este trimis de exec(). Acesta este funcţia care va fi folosită
pentru a executa ls şi wc –l. Apelul dup() primeste un descriptor de fisier şi îi face o copie
(clonă). Astfel se conectează stdout al comenzii ls cu stdin al comenzii wc. Iesirea stdout
a lui ls intră în pipe şi intrarea stdin alui wc intră în pipe.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
int pfds[2];

pipe(pfds);

if (!fork()) {
close(1); /* close normal stdout */
dup(pfds[1]); /* make stdout same as pfds[1] */
close(pfds[0]); /* we don't need this */

6
execlp("ls", "ls", NULL);
} else {
close(0); /* close normal stdin */
dup(pfds[0]); /* make stdin same as pfds[0] */
close(pfds[1]); /* we don't need this */
execlp("wc", "wc", "-l", NULL);
}
}

Funcţia close(1) eliberează descriptorul de fisier 1 (stdout), dup(pdfs[1]) copiază capătul


scriere al pipe-ului în primul descriptor liber, care este “1”, pentru că tocmai ce a fost
eliberat. În acest fel tot ce este scris în stdout ( descriptorul 1) de către ls va merge în
pdfs[1] (capătul de scriere al pipe-ului). În mod similar funcţionează şi wc numai că în
sens invers. [2]

Observaţie:

Orice proces cu permisiuni adecvate poate mai apoi citi sau scrie întru-un pipe numit.

In apelul open(2), procesul ce deschide pipe-ul se blochează până un alt proces


redeschide pipe-ul.

Pentru a deschide un pipe numit fară a-l bloca, apelul open(2) însumează masca
O_NDELAY ( din sys/fcntl.h) cu masca modului fişier selectat folosind booleanul sau
operaţia pe apelul open(2). Dacă nici un alt proces nu este conectat la pipe când open(2)
este apelat, se returnează -1 cu errno setat pe EWOULDBLOCK.

1.5. Concluzii
Probabil că modul cel mai bun de folosire al pipe-ului este cel mai banal: trimiterea
stdout al unei comenzi la stdin-ul alteia. Pentru alte moduri de folosire, pipe-urile sunt
destul de limitate, existând alte tehnici IPC mai avantajoase.

7
Bibliografie

[1] Little Unix Programmers Group (LUPG)'s Little Site


http://users.actcom.co.il/~choo/lupg/tutorials/multi-process/multi-process.html
[2] Beej's Guide to Unix Interprocess Communication
http://www.ecst.csuchico.edu/~beej/guide/ipc/
[3] Unleashing anonymous pipes – Part 1 By Dinesh Ahuja
http://www.codeproject.com/
[4] Introduction to Named PipesBy Andy Vaught
http://www2.linuxjournal.com/article/2156
[5] Interprocess Communication (IPC), Pipes
http://www.cs.cf.ac.uk/Dave/C/node23.html
[6] pipe(2) - Linux man page
[7] Pipe example
http://users.actcom.co.il/~choo/lupg/tutorials/multi-process/two-way-pipe.c
[8] Pipe example
http://www.ee.ic.ac.uk/docs/software/unix/programming/sys/transfer/pipe.html
[9] Interprocess Communication, Named Pipes
http://www.cs.manchester.ac.uk/solaris/sun_docs/C/solaris_9/SUNWdev/NETP
ROTO/p18.html

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