Catalin
Moderator
Inregistrat: acum 16 ani
Postari: 19
|
|
Comunicare intre procese folosind pipes
Introducere in Linux
Ce este Linux.
Linux este o copie a sistemului de operare UNIX, care poate rula pe un calculator cu procesor Intel 386 sau mai bun. Linux nu este UNIX, UNIX fiind un software aflat sub copyright (trebuie cumparata o licenta pentru a-l putea utiliza legal). A fost rescris de la zero pentru a elimina necesitatea platii licentei. Totusi el se bazeaza in intregime pe comenzile si "look and feel"-ul UNIX, deci cine stie Linux stie si UNIX si invers. Linux este un sistem multiuser si multitasking, adica mai multi utilizatori pot rula mai multe programe in acelasi timp. Are suport pentru retea (TCP/IP), Internet, ba chiar este unul dintre cele mai folosite sisteme de operare pentru servere internet si intranet. Linux este sub licenta GPL ceea ce inseamna urmatoarele: - este disponibil in cod sursa gratuit. - oricine vrea sa aduca modificari, sau sa foloseasca anumite parti este liber sa o faca, dar cu conditia ca produsul nou obtinut sa fie sub aceeasi licenta (adica sa fie gratuit si cu sursele la vedere).
Scurt istoric. Cum a aparut Linux
In 1991 Linus Torvalds (pe atunci student) a scris prima versiune de Linux. Apoi a facut publice sursele pe internet, si o multime de oameni au inceput sa-i raspunda, sa-i aduca imbunatatiri, noi sugestii, etc. Intre timp acest sistem de operare a devenit complex, au aparut (si mai apar) noi facilitati, iar performanta este remarcabila. In prezent sunt estimati peste 8 milioane de utilizatori Linux, iar nucleul Linuxului are peste 200 de autori. Pe langa acesti 200 de autori ar trebui adaugate cele cateva mii de persoane care testeaza si gasesc buguri.
Ce tip de calculator poate rula Linux ?
Pentru a rula Linux aveti nevoie de minim un calculator i386 cu 4 Mb RAM si 50 Mb spatiu liber pe harddisk. Daca doriti sa folositi si sistemul X-Windows, minimul de memorie RAM este de 8 MB. Este posibil ca unele programe complexe sa necesite foarta multa memorie. Linux are posibilitatea de a simula memoria RAM folosind un fisier de swap pe harddisk, dar este mult mai lent. In plus excesul de memorie este folosit pentru accelerarea operatiilor pe disk, deci cu cat mai multa memorie RAM, cu atat Linux-ul dvs. are performante mai bune.
Programe disponibile sub Linux:
Majoritatea distributiilor Linux includ compilatoare pentru diverse limbaje de programare, utilitare pentru retea (email, telnet, ftp, www), creare si manipulare documente, tiparire, arhivare, si multe altele. Pe langa acestea exista disponibile pe Internet o mare varietate de programe, unele gratuite, altele nu, iar mai nou marile case de software au inceput sa porteze softurile lor pe Linux.
Iata cateva exemple: • Staroffice (foarte asemanator cu MS-Office, gratuit pentru folosire non-comerciala) • WordPerfect (Cunoscutul procesor de text, disponibil si sub Linux) • Netscape Navigator, Opera (browsere Web) • Oracle (baze de date) • Mathematica (calcul simbolic si nu numai) Adrese utile:
Adresele web de mai jos constituie un foarte bun punct de start. Pentru a le accesa aveti nevoie de un calculator conectat la Internet si echipat cu un browser www (Netscape, Opera).
Informatii despre Linux si programe disponibile in Linux: • • • •
Informatii despre distributii Linux: • • • • htttp://www.caledra.org • • Cerintele Hardware
De la inceput, Linuxul a avut nevoie de resurse hardware minime pentru a rula. Acest lucru nu s-a schimbat in timp. Linuxul poate functiona pe sisteme hard foarte limitate insa a fi capabil sa rulezi Linuxul nu este totuna cu a crea un sistem server. Evident, cu cat este mai bun hardware-ul cu atat performanta obtinuta la rularea sistemului de operare va fi mai mare. Cu toate acestea feriti-va de ultimele descoperiri in domeniu pentru ca de multe ori acestea nu sunt suportate de Linux. De exemplu, daca o placa video cu ultimele facilitati a aparut pe piata ieri nu va asteptati ca maine Linux-ul sa o si recunoasca. Uneori producatorii hardware ingreuneaza accesul dezvoltatorilor Linux la informatiile specifice necesare pentru a scrie modulele driver si astfel devine dificil ca acestia sa implementeze rapid ultimele produse hardware.
Pregatirea instalarii
Red Hat Linux, distributia Linux la care va face referire materialul in continuare, se poate instala prin intermediul serviciilor FTP, NFS sau SMB (Samba) atunci cand CD-ROM-ul Red Hat se afla pe un alt computer. Aceste metode sunt foarte sensibile si de putie ori functioneaza fara erori. Pentru a economisi timp si efort este recomandabil sa realizati instalarea folosind CD-ROM-ul de pe calculatorul pe care instalati Linux-ul. Deoarece aceasta este medoda recomandata, vom considera in continuare ca faceti instalarea de pe CD-ul propriu. Pasul urmator in pregatirea instalarii il constituie determinarea necesitatii unei dischete boot. Daca aveti un sistem de calcul care suporta bootarea de pe CD-ROM atunci nu aveti nevoie de o astfel de discheta. Insa daca CD-ul dumneavoastra cu distributia Red Hat nu este bootabil sau sistemul nu suporta bootarea (incarcarea) de pe CD-ROM aveti nevoie de o discheta de boot.
Crearea dischetelor de bootare Crearea unei dishete de boot sub sistemul de operare Windows se face in felul urmator:
1. Sa presupunem ca CD-ROM-ul este drive-ul D: si ca aveti deja CD-ul in unitate. Rulati urmatoarea comanda de la un prompt MS-DOS:
d:/dosutils/rawrite
2. Utilitarul rawrite va afisa urmatoarele:
Enter disk image source file name:
Please insert a formated diskette into drive A: and press the enter key
3. Introduceti ca sursa \images\boot.img. Rawrite va afisa urmatorul prompt:
Enter target diskette drive:
4. Introduceti litera corespunzatoare (A: sau a: ) 5. Acum ar trebui sa introduceti o discheta goala, formatata si sa apasati tasta Enter ca sa continue scrierea dischetei. 6. Utilitatea rawrite va scrie fisierul boot.img pe discheta.
Crearea unei dishete de boot sub sistemul de operare Linux se face in felul urmator:
1. Montati CD-ROM-ul Red Hat ca de obicei (de ex. mount /dev/cdrom /mnt/cdrom). Considerand ca ati efectuat montarea in directorul /mnt/cdrom schimbati directorul curent in /mnt/cdrom/images, acolo unde este tinuta imaginea de bootare. 2. Considerand ca discul floppy este /dev/fd0 (standard) si ca utilizati o discheta de 1,44 MB, rulati urmatoarea comanda:
dd if=boot.img of=/dev/fd0 bs=1440K
In felul acesta ar trebui sa va creati discheta de boot. Va puteti crea, de asemenea, o discheta suplimentara doar inlocuind ca fisier intrare (input file) boot.img cu supp.img (if=supp.img). Odata ce aveti discheta puteti instala Red Hat Linux de pe CD.
Introducere in Unix Unix este denumirea generica a unei largi familii de sisteme de operare orientate pe comenzi, multi-user si multi-tasking, dezvoltat pentru prima data in anii '70 la compania AT&T si Universitatea Berkeley. In timp, a devenit un sistem de operare foarte raspindit in intreaga lume, atit in mediile de cercetare si de invatamint (universitare), cit si in mediile industriale/comerciale. Ce inseamna sistem de operare orientat pe comenzi ? Sistemul poseda un interpretor de comenzi, ce are aceeasi sarcina ca si command.com-ul din MS-DOS, si anume aceea de a prelua comenzile introduse de utilizator, de a le executa si de a afisa rezultatele executiei acestora. Ce inseamna sistem de operare multi-user ? Un astfel de sistem este caracterizat prin faptul ca exista conturi utilizator, ce au anumite drepturi si restrictii de acces la fisiere si la celelalte resurse ale sistemului. De aceea, se utilizeaza mecanisme de protectie, cum ar fi parolele pentru conturile utilizator. De asemenea, un astfel de sistem permite conectarea la sistem si lucrul simultan a mai multor utilizatori. Ce inseamna sistem de operare multi-tasking ? Intr-un astfel de sistem se executa simultan mai multe programe (programele aflate in executie sunt numite procese). Observatie: de fapt, cind Unix-ul este utilizat pe calculatoare uni-procesor, in asemenea situatie executia simultana (concurenta) a proceselor nu este true-parallelism, ci se face tot secvential, prin interleaving (intretesere), si anume prin mecanismul de time-sharing: timpul procesor este impartit in cuante de timp, si fiecare proces existent in sistem primeste periodic cite o cuanta de timp in care i se aloca procesorul si deci este executat efectiv, apoi este intrerupt si procesorul este alocat altui proces care se va executa pentru o cuanta de timp din punctul in care ramasese, apoi va fi intrerupt si un alt proces va primi controlul procesorului s.a.m.d. Mecanismul acesta de stabilire a modului de alocare a procesorului proceselor din sistem, se bazeaza pe una din strategiile: round-robin, prioritati statice, prioritati dinamice, s.a. In cazul Unix-ului, se utilizeaza strategia cu prioritati dinamice. Unix-ul este un sistem de operare multi-user si multi-tasking ce ofera utilizatorilor numeroase utilitare interactive. Pe linga rolul de sistem de exploatare, scopul lui principal este de a asigura diferitelor taskuri (procese) si diferitilor utilizatori o repartizare echitabila a resurselor calculatorului (memorie, procesor/procesoare, spatiu disk, imprimanta, programe utilitare, accesul la retea, etc.) si aceasta fara a necesita interventia utilizatorilor. Unix-ul este inainte de toate un mediu de dezvoltare si utilizatorii au la dispozitie un numar foarte mare de utilitare pentru munca lor: editoare de text, limbaje de comanda (shell-uri), compilatoare, depanatoare (debugger-e), sisteme de prelucrare a textelor, programe pentru posta electronica si alte protocoale de acces Internet, si multe alte tipuri de utilitare. Pe scurt, un sistem Unix este compus din: • un nucleu (kernel), ce are rolul de a gestiona memoria si operatiile I/O de nivel scazut, precum si planificarea si controlul executiei diferitelor task-uri (procese). Este intrucitva similar BIOS-ului din DOS. • un ansamblu de utilitare de baza, cum ar fi: o diferite shell-uri(= interpretoare de limbaje de comanda); o comenzi de manipulare a fisierelor; o comenzi de gestiune a activitatii sistemului (a proceselor); o comenzi de comunicatie intre utilizatori sau intre sisteme diferite; o editoare de text; o compilatoare de limbaje (C, Fortran, s.a.) si un link-editor; o utilitare generale de dezvoltare de programe: debugger-e, arhivatoare, gestionare de surse, generatoare de analizoare lexicale si sintactice, etc. o diferite utilitare filtru (= programe ce primesc un fisier la intrare, opereaza o anumita transformare asupra lui si scriu rezultatul ei intr-un fisier de iesire), spre exemplu: filtru sursa Pascal->sursa C, filtru fisier text DOS->fisier text Unix si invers, etc. Nota: fisierele text sub DOS se deosebesc de fisierele text sub Unix prin faptul ca sfirsitul de linie se reprezinta sub DOS prin 2 caractere CR+LF (cod ASCII: 13+10), pe cind sub Unix se reprezinta doar prin caracterul LF.
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: • p[0] va fi descriptorul de fisier deschis pentru capatul Read al canalului; • p[1] va fi descriptorul de fisier deschis pentru capatul Write al canalului; 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 tip FILE*). 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 crearea pipe-ului, 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 primitiva pipe 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,First-Out), 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. o 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: 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 buffer-izat. 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. Programul - 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.
Codul sursa- /* Exemplu de utilizare a unui pipe intern pentru comunicatia intre 2 procese, folosind functii I/O de nivel scazut. */ #include<stdio.h> #include<errno.h>
extern int errno;
#define NMAX 1000
void main() { int pid, p[2]; char ch;
/* creare pipe intern */ if(pipe(p) == -1) { fprintf(stderr,"Error: can't open a channel, errno=%d\n",errno); exit(1); }
/* creare proces fiu */ if( (pid=fork()) == -1) { fprintf(stderr,"Error: can't create a child!\n" ); exit(2); } if(pid) { /* in tata */ /* tatal isi inchide capatul Read */ close(p[0]);
/* citeste caractere de la tastatura, pentru terminare: CTRL+D (i.e. EOF in Unix), si le transmite doar pe acelea care sunt litere mici */ while( (ch=getchar()) != EOF) if((ch>='a') && (ch<='z')) write(p[1],&ch,1);
/* tatal isi inchide capatul Write, pentru ca fiul sa poata citi EOF din pipe */ close(p[1]);
/* asteapta terminarea fiului */ wait(0); } else { /* in fiu */ char buffer[NMAX]; int nIndex = 0;
/* fiul isi inchide capatul Write */ close(p[1]);
/* fiul citeste caracterele din pipe si salveaza in buffer, pina depisteaza EOF, apoi afiseaza continutul bufferului. */ while( read(p[0],&ch,1) != 0) if(nIndex < NMAX) buffer[nIndex++] = ch;
buffer[ (nIndex==NMAX) ? NMAX-1 : nIndex ] = '\0'; printf("Fiu: am citit buffer=%s\n",buffer);
/* fiul isi inchide capatul Read */ close(p[0]); /* Obs: nu mai era nevoie de acest close explicit, deoarece oricum toti descriptorii sunt inchisi la terminarea programului. */ } }
Prezentarea powerpoint a proiectului o puteti lua de la link-ul urmator:
|
|