Laboratoare:Procese

Procese
Un concept cheie în orice sistem de operare este procesul. Un proces este un program în execuție. Procesele sunt unitatea primitivă prin care sistemul de operare alocă resurse utilizatorilor. Orice proces are un spațiu de adrese și unul sau mai multe fire de execuție. Putem avea mai multe procese ce execută același program, dar oricare două procese sunt complet independente.

Spațiile de adrese, regiștrii generali, PC (contor program), SP (indicator stivă), tabelele de fișiere deschise, lista de semnale (blocate, ignorate sau care așteaptă să fie trimise procesului), handler-ele pentru semnale, informațiile referitoare la sistemele de fișiere (directorul rădăcină, directorul curent), toate acestea NU sunt partajate, ci aparțin fiecărui proces în parte. Aceste informații necesare pentru rularea programului sunt ținute de sistemul de operare într-o structură numită Process Control Block, câte una pentru fiecare proces existent în sistem.

În momentul lansării în execuție a unui program, în sistemul de operare se va crea un proces pentru alocarea resurselor necesare rulării programului respectiv. Fiecare sistem de operare pune la dispoziție apeluri de sistem pentru crearea unui proces, terminarea unui proces, așteptarea terminării unui proces precum și apeluri pentru duplicarea descriptorilor de resurse între procese ori închiderea acestor descriptori.

În general un proces rulează într-un mediu specificat printr-un set de variabile de mediu. O variabilă de mediu este o pereche NUME=valoare. Un proces poate să verifice sau să seteze valoarea unei variabile de mediu printr-o serie de apeluri de bibliotecă.

Pe sisteme de 32 de biți fiecare proces are un spațiu de adrese de 4 GiB din care 2 (sau în anumite configurații 3) GiB sunt disponibili pentru alocare procesului, iar ceilalți 2 (respectiv 1) GiB fiind rezervat sistemului de operare (codul kernelului și al driverelor, date, cache-uri, etc.). Așadar fiecare proces "vede" sistemul de operare în spațiul său de adrese însă nu poate accesa zona respectivă decât prin intermediul apelurilor de sistem (comutând procesorul în modul de lucru privilegiat). Pe sisteme de 64 de biți spațiul total de adrese este de 16 EiB, iar pe sisteme de 16 biți de doar 64 KiB (procesoarele x86 pe 16 biți puteau adresa 220 = 1 MiB de memorie folosind la adresare doi regiștri de 16 biți întrucât, deși era procesor pe 16 biți, avea 20 de linii de adresă).

Procese în Linux
Apelurile de sistem puse la dispoziție de Linux pentru gestionarea proceselor sunt: fork și exec pentru crearea unui proces și respectiv modificarea imaginii unui proces, wait și waitpid pentru așteptarea terminării unui proces și exit pentru terminarea unui proces. Pentru copierea descriptorilor de fișier Linux pune la dispoziție apelurile de sistem dup  și dup2</tt>. Pentru citirea, modificarea ori ștergerea unei variabile de mediu, biblioteca standard C pune la dispoziție apelurile getenv</tt>, setenv</tt>, unsetenv</tt> precum și un pointer la tabela de variabile de mediu environ</tt>.

Rularea unui program executabil
Modul cel mai simplu prin care se poate crea un nou proces este prin folosirea funcției de bibliotecă system</tt>:

Apelul acestei funcții are ca efect execuția ca o comandă shell a comenzii reprezentate prin șirul de caractere command</tt>. Să luăm ca exemplu următorul program C:

'''Exemplu 1. sys1.c'''

care este echivalent cu

Încă un exemplu:

'''Exemplu 2. sys2.c'''

program C care este echivalent cu

$ sh -c "cd /etc/rc.d/rc$RUNLEVEL.d/; ls -la"

Implementarea system</tt>: se creează un nou proces cu fork</tt>; procesul copil execută prin intermediul exec</tt> programul sh</tt> cu argumentele -c "comanda"</tt>, timp în care părintele așteaptă terminarea procesului copil.

Crearea unui proces
În UNIX singura modalitate de creare a unui proces este prin apelul de sistem fork</tt>: Efectul este crearea unui nou proces - procesul copil, copie a celui care a apelat fork</tt> - procesul părinte. Copilul primește un nou PID</tt> de la sistemul de operare. Secvența clasică de creare a unui proces este prezentată în continuare:

'''Exemplu 3. ex_fork.c'''

După cum se observă din comentariile de mai sus, apelul de sistem fork</tt> întoarce <tt>PID</tt>-ul noului proces în procesul părinte și valoarea 0 în procesul copil. Pentru aflarea <tt>PID</tt>-ului procesului curent ori al procesului părinte se va apela una din funcțiile de mai jos.

Funcția <tt>getpid</tt> întoarce <tt>PID</tt>-ul procesului apelant:

Funcția <tt>getppid</tt> întoarce <tt>PID</tt>-ul procesului părinte al procesului apelant:

Înlocuirea imaginii unui proces
Familia de funcții <tt>exec</tt> va executa un nou program, înlocuind imaginea procesului curent, cu cea dintr-un fișier (executabil). Spațiul de adrese al procesului va fi înlocuit cu unul nou, creat special pentru execuția fișierului. De asemenea vor fi reinițializați regiștrii IP (EIP/RIP - contorul program) și SP (ESP/RSP - indicatorul stivă) și regiștrii generali. Măștile de semnale ignorate și blocate sunt setate la valorile implicite, ca și handler-ele semnalelor. <tt>PID</tt>-ul și descriptorii de fișiere care nu au setat flagul <tt>CLOSE_ON_EXEC</tt> rămân neschimbați (implicit flagul <tt>CLOSE_ON_EXEC</tt> nu este setat).

Presupunem ca vrem sa apelam comanda <tt>ls -la</tt>:

Se observa ca primul argument este insusi numele programului, iar ultimul este <tt>NULL</tt>.

<tt>execl</tt> nu caută programul dat ca parametru în <tt>PATH</tt>, astfel că acesta trebuie însoțit de calea completă. Versiunea <tt>execlp</tt> caută programul și în <tt>PATH</tt>.

Există, de asemenea, versiunile <tt>execv</tt>, care primesc argumentele programului de rulat ca vector, in loc de argumente variabile ale functiei.

Folosirea oricărei funcții din familia <tt>exec</tt> necesită includerea header-ului <tt>unistd.h</tt>.

Așteptarea terminării unui proces
Familia de funcții <tt>wait</tt> suspendă execuția procesului apelant până când procesul (procesele) specificate în argumente fie s-au terminat fie au fost oprite (<tt>SIGSTOP</tt>).

Valoarile uzuale ale argumentului <tt>pid</tt> sunt identificatorul unui proces copil (spre exemplu, returnat de <tt>fork</tt>) sau -1, în cazul în care se dorește așteptarea oricărui proces copil.

Parametrul <tt>options</tt> permite specificarea diferitelor opțiuni.

Funcția va întoarce <tt>PID</tt>-ul procesului a cărui stare e raportată; infomațiile de stare sunt depuse ca <tt>int</tt> la adresa indicată prin argumentul <tt>status</tt>.

Starea procesului interogat se poate afla examinând <tt>status</tt> cu macrodefiniții precum <tt>WEXITSTATUS</tt>, care întoarce codul de eroare cu care s-a încheiat procesul copil, evaluând cei mai nesemnificativi 8 biți. Prin convenție, un cod de eroare egal cu 0 semnifică succes.

Există o variantă simplificată care așteaptă orice proces copil să se termine:

Este echivalentă cu:

Pentru a folosi <tt>wait</tt> sau <tt>waitpid</tt> trebuie incluse header-ele <tt>sys/types.h</tt> și <tt>sys/wait.h</tt>.

Terminarea unui proces
Pentru terminarea procesului curent, Linux pune la dispoziție apelul de sistem <tt>exit</tt>. Dintr-un program C există trei moduri de invocare a acestui apel de sistem:
 * apelul <tt>_exit</tt> (POSIX.1-2001):


 * apelul <tt>_Exit</tt> din biblioteca standard C (conform C99):


 * apelul <tt>exit</tt> din biblioteca standard C (conform C89, C99):

<tt>_exit</tt>(2) și <tt>_Exit</tt>(2) sunt funcțional echivalente (doar că sunt definite de standarde diferite):
 * procesul apelant se va termina imediat.
 * toți descriptorii de fișier ai procesului sunt închiși, copiii procesului sunt "înfiați" de <tt>init</tt>, iar părintelui procesului îi va fi trimis un semnal <tt>SIGCHLD</tt>. Procesului părinte îi va fi întoarsă valoarea <tt>status</tt> ca rezultat al unei funcții de așteptare (<tt>wait</tt> sau <tt>waitpid</tt>).

În plus <tt>exit</tt>(3):
 * va șterge toate fișierele create cu <tt>tmpfile</tt>
 * va scrie bufferele streamurilor deschise și le va închide.

Notă: Conform ISO C, un program care se termină cu un <tt>return x;</tt> din <tt>main</tt> va avea același comportament ca și unul care apelează <tt>exit(x)</tt>.

Pentru terminarea unui alt proces din sistem, se va trimite un semnal către procesul respectiv prin intermediul apelului de sistem <tt>kill</tt>. Mai multe detalii despre <tt>kill</tt> și semnale în laboratorul de semnale.

my_system
Un exemplu de folosire a primitivelor <tt>exec</tt>, <tt>fork</tt>, <tt>exit</tt> și <tt>wait</tt> (sau de rularea a unui program) îl reprezintă chiar reimplementarea apelului de bibliotecă <tt>system</tt>

'''Exemplu 4. my_system.c'''

Copierea descriptorilor de fișier
<tt>dup</tt> duplică descriptorul de fișier <tt>oldfd</tt> și întoarce noul descriptor de fișier, sau -1 în caz de eroare:

<tt>dup2</tt> duplică descriptorul de fișier <tt>oldfd</tt> în descriptorul de fișier <tt>newfd</tt>; dacă <tt>newfd</tt> există, mai întâi va fi închis fișierul asociat. Întoarce noul descriptor de fișier, sau -1 în caz de eroare:

Descriptorii de fișier sunt, de fapt, indecși în tabela de fișiere deschise. Tabela este populată cu pointeri către o structură cu informațiile despre fișiere. Duplicarea unui descriptor de fișier înseamnă duplicarea intrării din tabela de fișiere deschise (adică 2 pointeri de la poziții diferite din tabelă vor indica spre aceeași structură din sistem asociată fișierului). Din acest motiv, toate informațiile asociate unui fișier (lock-uri, cursor, flaguri) sunt partajate de cei doi file descriptori. Ceea ce înseamnă că operațiile ce modifică aceste informații pe unul din file descriptori (de ex. <tt>lseek</tt>) sunt vizibile și pentru celălat file descriptor (duplicat). Atentie! Există și o excepție: flagul <tt>CLOSE_ON_EXEC</tt> nu este partajat (acest flag nu este ținut în structura menționată mai sus).

Moștenirea descriptorilor de fișier după operații <tt>fork</tt>/<tt>exec</tt>
Descriptorii de fișier ai procesului părinte se moștenesc în procesul copil în urma apelului <tt>fork</tt>. După un apel <tt>exec</tt> descriptorii de fișier sunt păstrați de asemenea, mai puțin aceia dintre ei care au setat flagul <tt>CLOSE_ON_EXEC</tt>.

Pentru a seta flagul <tt>CLOSE_ON_EXEC</tt> se folosește funcția <tt>fcntl</tt> cu un apel de genul:

Pentru a putea folosi funcția <tt>fcntl</tt> trebuie incluse header-ele <tt>unistd.h</tt> și <tt>fcntl.h</tt>.

Trei dintre descriptorii de fișier sunt mai importanți:
 * <tt>STDIN_FILENO</tt>
 * toate programele obișnuite conțin acest file descriptor care are valoarea 0; el reprezintă intrarea standard; tot ce utilizatorul scrie la terminal, programul va putea citi din acest file descriptor
 * <tt>STDOUT_FILENO</tt>
 * toate programele obișnuite au acest file descriptor care are valoarea 1; el reprezintă ieșirea standard; toate apelurile de genul <tt>printf</tt> vor genera output la terminal
 * <tt>STDERR_FILENO</tt>
 * toate programele obișnuite au acest file descriptor care are valoarea 2; el reprezintă ieșirea standard de eroare

Variabile de mediu
Un vector de pointeri la șiruri de caractere, ce conțin variabilele de mediu și valorile lor. Vectorul e terminat cu <tt>NULL</tt>. Șirurile de caractere sunt de forma <tt>"VARIABILA=VALOARE"</tt>.

<tt>getenv</tt> întoarce valoarea variabilei de mediu denumite <tt>name</tt>, sau <tt>NULL</tt> dacă nu există o variabilă de mediu denumită astfel:

<tt>setenv</tt> adaugă în mediu variabila cu numele <tt>name</tt> (dacă nu există deja) și îi setează valoarea la <tt>value</tt>. Dacă variabila există și <tt>replace</tt> e 0, acțiunea de setare a valorii variabilei e ignorată; dacă <tt>replace</tt> e diferit de 0, valoarea variabilei devine <tt>value</tt>:

<tt>unsetenv</tt> șterge variabila denumită <tt>name</tt> din mediu:

Depanarea unui proces
Pe majoritatea sistemelor de operare pe care a fost portat, <tt>gdb</tt> nu poate detecta când un proces realizează o operație <tt>fork</tt>. Atunci când programul este pornit, depanarea are loc exclusiv în procesul inițial, procesele copii nefiind atașate debugger-ului. În acest caz, singura soluție este introducerea unor întârzieri în executia procesului nou creat (de exemplu, prin apelul de sistem <tt>sleep</tt>), care să ofere programatorului suficient timp pentru a atașa manual <tt>gdb</tt>-ul la respectivul proces, presupunând că i-a aflat PID-ul în prealabil.

Pentru a atașa debugger-ul la un proces deja existent, se folosește comanda <tt>attach</tt>, în felul următor:

(gdb) attach PID 

Această metodă este destul de incomodă și poate cauza chiar o funcționare anormală a aplicației depanate, în cazul în care necesitățile de sincronizare între procese sunt stricte (de exemplu operații cu time-out).

Din fericire, pe un număr limitat de sisteme, printre care și Linux, <tt>gdb</tt> permite depanarea comodă a programelor care creează mai multe procese prin <tt>fork</tt> și <tt>vfork</tt>. Pentru ca <tt>gdb</tt> să urmărească activitatea proceselor create ulterior, se poate folosi comanda <tt>set follow-fork-mode</tt>, în felul următor:

(gdb) set follow-fork-mode mode 

unde <tt>mode</tt> poate lua valoarea <tt>parent</tt>, caz în care debugger-ul continuă depanarea procesului părinte, sau valoarea <tt>child</tt>, și atunci noul proces creat va fi depanat în continuare. Se poate observa că în această manieră debugger-ul este atașat la un moment dat doar la un singur proces, neputând urmări mai multe simultan.

Cu toate acestea, <tt>gdb</tt> poate ține evidența tuturor proceselor create de către programul depanat, deși în continuare numai un singur proces poate fi rulat prin debugger la un moment dat. Comanda <tt>set detach-on-fork</tt> realizează acest lucru:

(gdb) set detach-on-fork mode 

unde <tt>mode</tt> poate fi <tt>on</tt>, atunci când <tt>gdb</tt> se va atașa unui singur proces la un moment dat (comportament implicit), sau <tt>off</tt>, caz în care <tt>gdb</tt> se atașează la toate procesele create în timpul execuției, și le suspendă pe acelea care nu sunt urmărite, în funcție de valoarea setării <tt>follow-fork-mode</tt>.

Comanda <tt>info forks</tt> afișează informații legate de toate procesele aflate sub controlul <tt>gdb</tt> la un moment dat: (gdb) info forks

De asemenea, comanda <tt>fork</tt> poate fi utilizată pentru a seta unul din procesele din listă drept cel activ (care este urmărit de debugger).

(gdb) fork fork-id 

unde <tt>fork-id</tt> este identificatorul asociat procesului, așa cum apare în lista afișată de comanda <tt>info forks</tt>.

Atunci când un anumit proces nu mai trebuie urmărit, el poate fi înlaturat din listă folosind comenzile <tt>detach fork</tt> și <tt>delete fork</tt>:

(gdb) detach fork fork-id  (gdb) delete fork fork-id 

Diferența dintre cele două comenzi este că <tt>detach fork</tt> lasă procesul să ruleze independent, în continuare, în timp ce <tt>delete fork</tt> îl încheie.

Pentru a ilustra aceste comenzi într-un exemplu concret, să considerăm programul următor:

'''Exemplu 5. forktest.c'''

Dacă vom rula programul cu parametrii impliciți de depanare, vom constata că <tt>gdb</tt> va urmări exclusiv execuția procesului părinte:

$ gcc -O0 -g3 -o forktest forktest.c $ gdb ./forktest [...] (gdb) run Starting program: /home/stefan/forktest The child process is executing... Everything is done! Program exited normally.

Punem câte un breakpoint în codul asociat procesului părinte, respectiv procesului copil, pentru a evidenția mai bine acest comportament:

(gdb) break 17 Breakpoint 1 at 0x8048497: file forktest.c, line 17. (gdb) break 27 Breakpoint 2 at 0x80484f0: file forktest.c, line 27. (gdb) run Starting program: /home/stefan/forktest The child process is executing... Breakpoint 2, main at forktest.c:27 27                     printf("Everything is done!\n"); (gdb) continue Continuing. Everything is done! Program exited normally.

Setăm debugger-ul să urmărească procesele copii, și observăm că de data aceasta celălalt breakpoint este atins: (gdb) set follow-fork-mode child (gdb) run Starting program: /home/stefan/forktest [Switching to process 6217] Breakpoint 1, main at forktest.c:17 17                     printf("The child process is executing...\n"); (gdb) continue Continuing. The child process is executing... Program exited normally. Everything is done!

Observați că ultimele două mesaje au fost inversate, față de cazul precedent: debugger-ul încheie procesul copil, apoi procesul părinte afișează mesajul de final (<tt>Everything is done!</tt>).

Procese în Windows
Apelurile <tt>Win32 API</tt> pe care Windows le pune la dispoziție pentru gestionarea proceselor sunt: <tt>CreateProcess</tt> și variații ale acesteia pentru crearea unui proces, <tt>WaitForSingleObject</tt> și alte funcții de așteptare pentru așteptarea terminării unui proces, <tt>ExitProcess</tt> pentru terminarea procesului curent și <tt>TerminateProcess</tt> pentru terminarea unui alt proces din sistem. Pentru duplicarea descriptorilor de resurse între procese se va apela funcția <tt>DuplicateHandle</tt>. Pentru citirea ori modificarea unei variabile de mediu, avem la dispoziție apelurile <tt>GetEnvironmentVariable</tt> și <tt>SetEnvironmentVariable</tt> precum și <tt>GetEnvironmentStrings</tt> care întoarce un pointer la tabela de variabile de mediu.

Crearea unui proces
În Windows atât crearea unui nou proces cât și înlocuirea imaginii lui cu cea dintr-un program executabil se realizează prin apelul funcției <tt>CreateProcess</tt>.

Exemplul de mai jos lansează în execuție <tt>notepad.exe</tt>:

API-ul Windows mai pune la dispoziție câteva funcții înrudite precum <tt>CreateProcessAsUser</tt>, <tt>CreateProcessWithLogonW</tt> ori <tt>CreateProcessWithTokenW</tt> care permit crearea unui proces într-un context de securitate diferit de cel al utilizatorului curent. Mai multe informații despre funcția <tt>CreateProcess</tt> găsiți în documentația de Platform SDK.

Pentru a se obține un descriptor (handle) al unui proces cunoscându-se <tt>PID</tt>-ul procesului respectiv, se va apela funcția <tt>OpenProcess</tt>:

iar pentru a obține un descriptor al procesului curent se va apela <tt>GetCurrentProcess</tt>:

Pentru a obține <tt>PID</tt>-ul procesului curent se va apela <tt>GetCurrentProcessId</tt>:

Spre deosebire de Linux, în Windows nu se impune o ierarhie a proceselor în sistem. Teoretic există o ierarhie implicită din modul cum sunt create procesele. Un proces deține descriptori (handle) ai proceselor create de el, însă descriptorii pot fi duplicați între procese ceea ce duce la situația în care un proces deține descriptori ai unor procese care nu sunt create de el, deci ierarhia implicită dispare.

Așteptarea inițializării procesului creat
Deoarece funcția <tt>CreateProcess</tt> întoarce imediat, fără a aștepta ca procesul nou creat să-și termine inițializările, este nevoie de un mecanism prin care procesul părinte să se sincronizeze cu procesul copil înainte de a încerca să comunice cu acesta. Windows pune la dispoziție funcția de așteptare <tt>WaitForInputIdle</tt>.

Funcția va cauza blocarea firului de execuție apelant până în momentul în care procesul <tt>hProcess</tt> și-a terminat inițializarea și așteaptă date de intrare. Funcția poate fi folosită oricând pentru a aștepta ca procesul <tt>hProcess</tt> să treacă în starea în care așteaptă date de intrare, nu doar la momentul creării sale. Funcției i se poate specifica o durată de așteptare prin intermediul parametrului <tt>dwMilliseconds</tt>.

Așteptarea terminării unui process
Pentru a suspenda execuția procesului curent până când unul sau mai multe alte procese se termină, se va folosi una din funcțiile de așteptare <tt>WaitForSingleObject</tt> ori <tt>WaitForMultipleObjects</tt>.

Exemplul următor aşteaptă nedefinit terminarea procesului reprezentat de <tt>hProcess</tt>.

Funcțiilor li se pot specifica intervale de timeout prin parametrul <tt>dwMilliseconds</tt>. <tt>WaitForMultipleObjects</tt> permite așteptarea terminării mai multor procese.

Funcțiile de așteptare sunt folosite în cadrul mai general al mecanismelor de sincronizare între procese și vor fi prezentate în detaliu în laboratorul de sincronizare între procese.

Aflarea codului de terminare al procesului așteptat
Pentru a determina codul de eroare cu care s-a terminat un anumit proces, se va apela funcția <tt>GetExitCodeProcess</tt>:

Dacă procesul <tt>hProcess</tt> nu s-a terminat încă, funcția va întoarce în <tt>lpExitCode</tt> codul de terminare <tt>STILL_ACTIVE</tt>. Dacă procesul s-a terminat, se va întoarce codul său de terminare care poate fi respectiv:


 * parametrul pasat uneia din funcțiile <tt>ExitProcess</tt> sau <tt>TerminateProcess</tt> (<tt>exit</tt> din libc)
 * valoarea returnată de funcția <tt>main</tt> sau <tt>WinMain</tt> a procesului
 * codul de eroare al unei excepții netratate care a cauzat terminarea procesului.

Terminarea unui proces
Pentru terminarea procesului curent, Windows API pune la dispoziție funcția <tt>ExitProcess</tt>.

Procesul apelant și toate firele sale de execuție se vor termina imediat. Toate DLL-urile de care era atașat procesul sunt notificate și se apelează metode de distrugere a resurselor alocate de acestea în spațiul de adresă al procesului. Toți descriptorii de resurse (handle) ai procesului sunt închiși. Codul de terminare al procesului este setat la <tt>uExitCode</tt>.

Pentru terminarea unui alt proces din sistem se va apela funcția <tt>TerminateProcess</tt>.

Se va iniția terminarea procesului <tt>hProcess</tt> și a tuturor firelor sale de execuție și se vor revoca operațiile de intrare/ieșire neterminate după care funcția <tt>TerminateProcess</tt> va întoarce imediat. Toți descriptorii de resurse (handle) ai procesului sunt închiși. Funcția <tt>TerminateProcess</tt> este periculoasă și se recomandă folosirea ei doar în cazuri extreme, deoarece ea nu notifică DLL-urile de care este atașat procesul <tt>hProcess</tt> de detașarea acestuia, lăsând astfel alocate eventualele date rezervate de DLL în spațiul de adresă al procesului.

Așadar, metoda recomandată de terminare a unui proces este <tt>ExitProcess</tt>. ea asigurând terminarea grațioasă a procesului cu eliberarea tuturor resurselor alocate de proces direct sau prin intermediul unor DLL-uri.

Terminarea unui proces NU implică terminarea proceselor create de acesta.

Exec
Exemplificăm în continuare noțiunile prezentate până acum.

'''Exemplu 6. Exec.cpp'''

Duplicarea descriptorilor de resurse
Pentru duplicarea unui descriptor de resursă (handle) se va apela funcția <tt>DuplicateHandle</tt>. Descriptorul inițial și cel duplicat vor referi același obiect și rezultatele operațiilor efectuate asupra oricăruia dintre cei doi descriptori vor fi vizibile și din perspectiva celuilalt. Duplicarea unui descriptor de resursă poate fi de dorit în cazul în care vrem să obținem un descriptor de resursă cu drepturi de acces diferite față de cel inițial, sau dacă dorim obținerea unui descriptor care nu va fi moștenit în procesele copil dintr-unul care va fi moștenit.

Funcția va duplica descriptorul <tt>hSourceHandle</tt> din procesul <tt>hSourceProcessHandle</tt> prin crearea descriptorului <tt>lpTargetHandle</tt> în procesul <tt>hTargetProcessHandle</tt>. Noului descriptor îi vor fi atașate, pe cât posibil, drepturile de acces specificate prin <tt>dwDesiredAccess</tt>. În unele cazuri descriptorul duplicat poate să aibă drepturi mai puțin restrictive decât originalul. Prin <tt>bInheritHandle</tt> se specifică dacă descriptorul duplicat va fi moștenit de procesele ce urmează să fie create de procesul <tt>hTargetProcessHandle</tt>. Prin <tt>dwOptions</tt> se pot specifica opțiuni precum închiderea descriptorului original în momentul duplicării sau preluarea exactă a drepturilor de acces și ignorarea parametrului <tt>dwDesiredAccess</tt>.

Funcția <tt>DuplicateHandle</tt> poate fi invocată fie de procesul sursă, fie de procesul destinație (inclusiv atunci când procesul sursă este același cu procesul destinație). Să analizăm situația în care procesele sursă și destinație diferă. Dacă procesul destinație apelează <tt>DuplicateHandle</tt>, el nu are de unde să știe <tt>hSourceHandle</tt>, care va trebui să-i fie comunicat de procesul sursă printr-un mecanism de comunicare între procese (parametru în linia de comandă, variabilă de mediu, fișier extern, socket; pipe, memorie partajată, coadă de mesaje). Dacă procesul sursă apelează <tt>DuplicateHandle</tt>, acesta va trebui să-i transmită procesului destinație descriptorul obținut, printr-un mecanism de comunicare între procese.

Pentru închiderea unui descriptor se va apela funcția <tt>CloseHandle</tt>:

Apelul funcției invalidează descriptorul <tt>hObject</tt> și decrementează contorul de descriptori al obiectului asociat. Abia când ultimul descriptor spre obiect este închis (contorul de descriptori ajunge 0), obiectul este distrus și eliminat din sistem. Chiar dacă un proces se termină, obiectul asociat lui nu este eliminat din sistem până când nu este închis descriptorul procesului (procesul părinte apelează <tt>CloseHandle(ProcesCopil)</tt>). Dacă descriptorul unui proces este închis înainte de terminarea procesului, obiectul asociat procesului nu este distrus până când procesul respectiv nu se termină (altfel-spus, nu este necesară așteptarea copilului în părinte pentru a apela <tt>CloseHandle</tt>).

Moștenirea descriptorilor de resurse la <tt>CreateProcess</tt>
După un apel <tt>CreateProcess</tt> descriptorii de resurse din procesul părinte pot fi moșteniți în procesul copil. Pentru ca un descriptor de fișier sa poată fi moștenit in procesul creat, trebuie îndeplinite 2 condiții: Descriptorii moșteniți sunt valizi doar în contextul procesului copil.
 * membrul <tt>bInheritHandle</tt> al structurii <tt>SECURITY_ATTRIBUTES</tt> pasate lui <tt>CreateFile</tt> trebuie sa fie <tt>TRUE</tt>
 * parametrul <tt>bInheritHandles</tt> al lui <tt>CreateProcess</tt> trebuie sa fie <tt>TRUE</tt>.

Cei 3 descriptori speciali de fișier pot fi obținuți apelând funcția <tt>GetStdHandle</tt>: cu unul din parametrii:
 * <tt>STD_INPUT_HANDLE</tt>
 * <tt>STD_OUTPUT_HANDLE</tt>
 * <tt>STD_ERROR_HANDLE</tt>

Pentru redirectarea descriptorilor standard în procesul copil puteti folosi membrii <tt>hStdInput</tt>, <tt>hStdOutput</tt>, <tt>hStdError</tt> ai structurii <tt>STARTUPINFO</tt> pasate lui <tt>CreateProcess</tt>. În acest caz, membrul <tt>dwFlags</tt> al aceleiaşi structuri trebuie setat la <tt>STARTF_USESTDHANDLES</tt>. Dacă se doreşte ca anumiţi descriptori să rămână cei impliciţi, li se poate atribui handle-ul întors de <tt>GetStdHandle</tt>.

Alte proprietăți ale procesului părinte care pot fi moștenite sunt variabilele de mediu și directorul curent. Nu vor fi moșteniți descriptori ai unor zone de memorie alocate de procesul părinte și nici pseudo-descriptori precum cei întorși de funcția <tt>GetCurrentProcess</tt>.

Descriptorul din procesul părinte și cel moștenit în procesul copil vor referi același obiect exact ca în cazul duplicării. De asemenea, descriptorul moștenit în procesul copil are aceeași valoare și aceleași drepturi de acces ca și descriptorul din procesul părinte. Pentru a folosi descriptorul moștenit, procesul copil va trebui să-i cunoască valoarea și ce obiect referă. Aceste informații trebuie sa fie pasate de părinte printr-un mecanism extern (IPC, etc).

Variabile de mediu
În Windows există un set de variabile de mediu globale, valabile pentru toți utilizatorii; în plus, fiecare utilizator în parte are asociat un set propriu de variabile de mediu. Împreună, cele două seturi formează <tt>EnvironmentBlock</tt>-ul utilizatorului respectiv. Acest <tt>EnvironmentBlock</tt> este similar cu variabila <tt>environ</tt> din Linux.

Un utilizator are acces la propriul <tt>EnvironmentBlock</tt> prin apelul funcției <tt>GetEnvironmentStrings</tt>:

care îi va întoarce un pointer spre acesta, pe care îl poate elibera cu <tt>FreeEnvironmentStrings</tt>:

Pentru a afla valoarea unei variabile de mediu se va apela funcția <tt>GetEnvironmentVariable</tt>:

care va umple <tt>lpBuffer</tt> de dimensiune <tt>nSize</tt> cu valoarea variabilei <tt>lpName</tt>.

Pentru a seta o variabilă de mediu se va apela <tt>SetEnvironmentVariable</tt>:

care va seta variabila <tt>lpName</tt> la valoarea specificată de <tt>lpValue</tt>. Funcția se va folosi și pentru ștergerea unei variabile de mediu prin pasarea unui parametru <tt>lpValue=NULL</tt>. <tt>SetEnvironmentVariable</tt> are efect doar asupra variabilelor de mediu ale utilizatorului și nu poate modifica variabile de mediu globale.

Un proces copil va moșteni <tt>EnvironmentBlock</tt>-ul părintelui dacă acesta apelează <tt>CreateProcess</tt> cu parametrul <tt>lpEnvironment=NULL</tt>.

Se poate obține <tt>EnvironmentBlock</tt>-ul unui alt utilizator prin intermediul funcției <tt>CreateEnvironmentBlock</tt>:

Trebuie să pasăm <tt>hToken</tt>, tokenul asociat utilizatorului al cărui bloc vrem să-l aflăm, pe care putem să-l obținem prin apelarea funcției <tt>LogonUser</tt>:

Și, bineînțeles, trebuie cunoscută parola utilizatorului respectiv.

<tt>EnvironmentBlock</tt>-ul obținut prin <tt>CreateEnvironmentBlock</tt> poate fi pasat ca parametru funcției <tt>CreateProcessAsUser</tt> de exemplu, și se va distruge prin apelul funcției <tt>DestroyEnvironmentBlock</tt>:

= Pipe-uri =

Pipe-urile (canalele de comunicaţie) sunt mecanisme primitive de comunicare între procese. Un pipe poate conţine o cantitate limitată de date. Accesul la aceste date este de tip FIFO (datele se scriu la un capăt al pipe-ului şi sunt citite de la celălalt capăt). Sistemul de operare garantează sincronizarea între operaţiile de citire şi scriere la cele două capete.

Există două tipuri de pipe-uri:


 * pipe-uri anonime - pot fi folosite doar de procese înrudite (un proces părinte şi un copil sau doi copii) deoarece este accesibil doar prin moştenire. Aceste pipe-uri nu mai există după ce procesele şi-au terminat execuţia.
 * pipe-uri cu nume - există ca fişiere cu drepturi de acces. Aceasta înseamnă că ele vor exista în continuare independent de procesul care le creează şi pot fi folosite de procese neînrudite.

Apelul pipe
Pipe-ul este un mecanism de comunicare unidirecţională între două procese. În majoritatea implementărilor de UNIX un pipe apare ca o zonă de memorie de o anumită dimensiune în spaţiul nucleului. Procesele care comunică printr-un pipe trebuie să aibă un grad de rudenie; de obicei, un proces care creează un pipe va apela după aceea <tt>fork</tt>, iar pipe-ul se va folosi pentru comunicarea între părinte şi fiu. În orice caz procesele care comunică prin pipe nu pot fi create de utilizatori diferiţi ai sistemului.

Apelul de sistem pentru creare este <tt>pipe</tt>:

Parametrul <tt>filedes</tt> returnează 2 descriptori de fişier: <tt>filedes[0]</tt> deschis pentru citire şi <tt>filedes[1]</tt> deschis pentru scriere. Se consideră că ieşirea lui <tt>filedes[1]</tt> este intrare pentru <tt>filedes[0]</tt>.

Apelul returnează 0 în caz de succes şi -1 în caz de eroare.

Observaţii:


 * Citirea/scrierea din/în pipe-uri este atomică dacă nu se citesc/scriu mai mult de <tt>PIPE_BUF</tt> octeţi.


 * Citirea/scrierea din/în pipe-uri se realizează cu ajutorul funcţiilor <tt>read</tt> / <tt>write</tt>.

Majoritatea aplicaţiilor care folosesc pipe-uri închid în fiecare dintre procese capătul de pipe neutilizat în comunicarea unidirecţională. Dacă unul dintre descriptori este închis se aplică regulile:


 * 1) O citire dintr-un pipe pentru care descriptorul de scriere a fost închis, după ce toate datele au fost citite, va returna 0, ceea ce indică sfârşitul fişierului. Descriptorul de scriere poate fi duplicat astfel încât mai multe procese să poată scrie în pipe. De regulă, în cazul pipe-urilor anonime există doar două procese, unul care scrie şi altul care citeşte pe când în cazul fişierelor FIFO pot exista mai multe procese care scriu date.
 * 2) O scriere într-un pipe pentru care descriptorul de citire a fost închis cauzează generarea semnalului SIGPIPE. Dacă semnalul este captat şi se revine din rutina de tratare, funcţia de sistem <tt>write</tt> returnează eroare şi variabila <tt>errno</tt> are valoarea <tt>EPIPE</tt>.

Cea mai frecventă greşeală relativ la lucrul cu pipe-urile constă în faptul că nu se trimite <tt>EOF</tt> prin pipe (citirea din pipe nu se termină) decât dacă sunt închise TOATE capetele de scriere din TOATE procesele care au deschis descriptorul de scriere în pipe (în cazul unui <tt>fork</tt>, nu uitaţi să închideţi capetele pipe-ului în procesul părinte).

Alte funcţii utile: <tt>popen</tt>, <tt>pclose</tt>.

FIFO (Pipe-uri cu nume)
Elimină necesitatea ca procesele care comunică să fie înrudite deoarece acestea nu trebuie să îşi transmită descriptorii. Astfel, fiecare proces îşi poate deschide pentru citire sau scriere fişierul pipe cu nume (FIFO) care este un tip de fişier special care păstrează caracteristicile unui pipe. Comunicaţia se face într-un sens sau în ambele sensuri. Fişierele de tip FIFO pot fi localizate ca având litera <tt>p</tt> în primul câmp al drepturilor de acces (ls -l).

Apelul de sistem pentru crearea FIFO este:

Parametrii sunt:


 * <tt>pathname</tt> reprezintă numele de cale al fişierului FIFO.
 * <tt>mode</tt> reprezintă un întreg ce indică drepturile de acces ale fişierului FIFO.

Apelul returnează 0 în caz de succes si -1 în caz de eroare.

Observaţii: după ce FIFO a fost creat, acestuia i se pot aplica toate funcţiile pentru operaţii cu fişiere: <tt>open</tt>, <tt>close</tt>, <tt>read</tt>, <tt>write</tt> la fel ca şi altor fişiere.

Modul de comportare al unui FIFO după deschidere este afectat de flagul <tt>O_NONBLOCK</tt> astfel:


 * 1) Dacă <tt>O_NONBLOCK</tt> nu este specificat (cazul normal), atunci un <tt>open</tt> pentru citire se va bloca până când un alt proces deschide acelaşi FIFO pentru scriere. Analog, dacă deschiderea este pentru scriere, se poate produce blocare până când un alt proces efectuează deschiderea pentru citire.
 * 2) Dacă se specifică <tt>O_NONBLOCK</tt>, atunci deschiderea pentru citire revine imediat, dar o deschidere pentru scriere poate returna eroare cu <tt>errno</tt> având valoarea <tt>ENXIO</tt>, dacă nu există un alt proces care a deschis acelaşi FIFO pentru citire.

Atunci când ultimul proces care scrie într-un FIFO îl închide, se va genera un "sfârşit de fişier" pentru procesul care citeşte din FIFO.

Pipe-uri anonime
Sunt pipe-uri unidirecţionale create cu ajutorul funcţiei <tt>CreatePipe</tt>. Funcţia returnează două handlere: unul pentru citirea din pipe şi unul pentru scrierea în pipe.

<tt>hReadPipe</tt> <tt>hWritePipe</tt> sunt iniţializaţi cu capetele de citire, respectiv de scriere ale pipe-ului. În caz de succes se returnează o valoare diferită de zero şi zero în caz de eroare. Pentru informaţii detaliate asupra erorii se foloseşte <tt>GetLastError</tt>. Iată un exemplu de apel:

Observaţii: <tt>CreatePipe</tt> creează pipe-ul, stabilind pentru bufferul de stocare dimensiunea specificată. <tt>CreatePipe</tt> creează de asemenea handlere pe care procesul le foloseşte pentru a citi/scrie din/în pipe cu ajutorul funcţiilor <tt>ReadFile</tt> şi <tt>WriteFile</tt>.

<tt>ReadFile</tt> se termină în unul din cazurile: o operaţie de scriere a luat sfârşit la capătul de scriere în pipe, numărul de octeţi cerut a fost citit sau a apărut o eroare.

Când un proces foloseşte <tt>WriteFile</tt> pentru a scrie într-un pipe anonim, operaţia de scriere se termină atunci când toţi octeţii au fost scrişi. Dacă bufferul pipe-ului este plin înainte ca toţi octeţii să fie scrişi, <tt>WriteFile</tt> rămâne blocat pâna ce alt proces sau thread foloseşte <tt>ReadFile</tt> pentru a face loc.

Pipe-urile anonime sunt implementate folosind un pipe cu nume unic. De aceea se poate pasa un handle către un pipe anonim unei funcţii care cere un handle către un pipe cu nume.

Pipe-uri cu nume
Un pipe cu nume este un pipe unidirecţional sau bidirecţional ce realizează comunicaţia între un server pipe şi unul sau mai mulţi clienţi pipe. Se numeşte server pipe procesul care creează un pipe cu nume şi client pipe procesul care se conectează la pipe. Pentru a face posibilă comunicarea între server şi mai mulţi clienţi prin acelaşi pipe se folosesc instanţe ale pipe-ului. O instanţă a unui pipe foloseşte acelaşi nume, dar are propriile handlere şi buffere.

Cel mai simplu server pipe ar fi acela care creează o singură instanţă a unui pipe şi comunică cu un singur client.

Moduri de comunicare
Comunicaţia prin pipe-urile cu nume poate fi de tip flux de octeţi sau de tip mesaj.

Un pipe de tip mesaj va trimite şi va citi date sub formă de mesaje. Deci este nevoie să se ştie lungimea mesajului şi se vor citi respectiv scrie doar mesaje complete.

Pentru un pipe de tip flux de octeţi nu există nicio garanţie asupra numărului de bytes care sunt citiţi / scrişi în orice moment. Astfel se pot transmite date fără să se ţină seama de conţinut pe când prin pipe-urile de tip mesaj comunicaţia are loc în unităţi discrete (mesaje).

Pipe-urile cu nume în Windows au un nume unic care le distinge în lista de obiecte cu nume din sistem. Un server specifică un nume pentru pipe la crearea lui prin apelarea funcţiei <tt>CreateNamedPipe</tt>. Clienţii specifică numele pipe-ului când apelează <tt>CreateFile</tt> pentru conectarea la o instanţă a unui pipe.

Funcţia <tt>CreateNamedPipe</tt> creează o instanţă a unui pipe cu nume şi returnează un handle pentru operaţii cu pipe-ul. Un proces server utilizează această funcţie fie pentru a crea prima instanţă a unui pipe cu nume şi a-i stabili atributele de bază fie pentru a crea o nouă instanţă a unui pipe cu nume existent.

<tt>lpName</tt> este un pointer către un şir care identifică în mod unic pipe-ul de forma următoare: <tt>\\.\pipe\nume_pipe</tt>. <tt>nume_pipe</tt> poate include orice caracter în afară de backslash, inclusiv numere şi caractere speciale. Lungimea maximă a numelui este de 256 caractere. Numele nu este case sensitive. În caz de succes se returnează un handle către capătul serverului al unei instanţe a pipe-ului. În caz de eroare se returnează <tt>INVALID_HANDLE_VALUE</tt>. Pentru mai multe detalii se foloseşte <tt>GetLastError</tt>. Iată un exemplu de apel:

Cu ajutorul funcţiei <tt>ConnectNamedPipe</tt> serverul pipe aşteaptă conectarea unui proces client la o instanţă a unui pipe.

<tt>hNamedPipe</tt> este handle-ul către capătul serverului al unei instanţe a pipe-ului. Acest handle este returnat de funcţia <tt>CreateNamedPipe</tt>. În caz de succes funcţia returnează o valoare diferită de zero, altfel returnează zero. Pentru informaţii detaliate se apelează <tt>GetLastError</tt>.

Dacă un client se conectează înainte de apelul <tt>ConnectNamedPipe</tt>, funcţia returnează zero şi <tt>GetLastError</tt> returnează <tt>ERROR_PIPE_CONNECTED</tt>. Acest lucru se poate întâmpla dacă un client se conectează în intervalul dintre apelul funcţiei <tt>CreateNamedPipe</tt> şi apelul <tt>ConnectNamedPipe</tt>. În această situaţie, conexiunea dintre client şi server este bună, chiar dacă funcţia returnează zero.

Observaţie: Comportarea funcţiei depinde de modul în care a fost creat handle-ul pipe-ului: blocant sau neblocant.

Dacă handle-ul pipe-ului este în modul blocant, <tt>ConnectNamedPipe</tt> nu se termină până când un client s-a conectat.

Dacă handle-ul pipe-ului este în modul neblocant, <tt>ConnectNamedPipe</tt> se termină imediat. În acest mod funcţia returnează o valoare diferită de zero prima dată când este apelată pentru o instanţă a pipe-ului care este deconectată de la un client anterior. Aceasta indică faptul că pipe-ul este acum disponibil pentru conexiunea cu un nou client. În toate celelalte cazuri pentru modul neblocant, valoarea întoarsă este zero. În aceste situaţii <tt>GetLastError</tt> returnează <tt>ERROR_PIPE_LISTENING</tt> dacă niciun client nu s-a conectat, <tt>ERROR_PIPE_CONNECTED</tt> dacă un client s-a conectat şi <tt>ERROR_NO_DATA</tt> dacă un client şi-a închis handle-ul de pipe dar serverul nu s-a deconectat. O conexiune validă între client şi server va exista numai după ce s-a primit "eroarea" <tt>ERROR_PIPE_CONNECTED</tt>.

Mod de lucru
Prima dată un server pipe apelează <tt>CreateNamedPipe</tt>, specificând numărul maxim de instanţe ale pipe-ului care pot exista simultan. Serverul poate apela <tt>CreateNamedPipe</tt> în mod repetat pentru a crea noi instanţe ale pipe-ului atât timp cât nu se depăşeşte numărul maxim. Dacă funcţia se termină cu succes fiecare apel returnează un handle către capătul serverului al unei instanţe a pipe-ului cu nume.

Imediat ce un server pipe creează o instanţă a unui pipe, un client pipe se poate conecta la ea apelând <tt>CreateFile</tt> sau <tt>CallNamedPipe</tt>. Dacă o instanţă a pipe-ului este disponibilă, <tt>CreateFile</tt> returnează un handle către capătul clientului al instanţei pipe-ului. Dacă nu există instanţe disponibile un client pipe poate folosi funcţia <tt>WaitNamedPipe</tt> pentru a aştepta până când un pipe devine disponibil. Un server poate determina când un client se conectează la o instanţă a pipe-ului apelând <tt>ConnectNamedPipe</tt>. Dacă handle-ul pipe-ului este în modul blocant, <tt>ConnectNamedPipe</tt> nu se termină până când un client s-a conectat.

Când un client şi serverul termină de folosit o instanţă a pipe-ului, serverul trebuie să apeleze mai întâi <tt>FlushFileBuffers</tt>, pentru a se asigura că toţi octeţii sau toate mesajele scrise în pipe sunt citite de client. <tt>FlushFileBuffers</tt> nu se termină până când clientul nu a citit toate datele din pipe. Serverul apelează apoi <tt>DisconnectNamedPipe</tt> pentru a închide conexiunea cu un client pipe. Această funcţie invalidează handle-ul clientului în cazul în care nu a fost deja închis. Orice dată necitită din pipe este distrusă. După deconectarea clientului, serverul apelează funcţia <tt>CloseHandle</tt> pentru a închide handle-ul său către instanţa pipe-ului. Ca o alternativă, serverul poate folosi <tt>ConnectNamedPipe</tt> pentru a permite unui nou client să se conecteze la această instanţă a pipe-ului.

Câteva funcţii unice sistemului Windows sunt disponibile pentru lucrul cu pipe-uri. Funcţia <tt>PeekNamedPipe</tt> poate fi folosită pentru citirea dintr-un pipe fără a scoate datele din el. Funcţia <tt>TransactNamedPipe</tt> scrie un mesaj de cerere şi citeşte un mesaj de răspuns într-o singură operaţie, îmbunătaţind performanţa reţelei. Poate fi folosită cu pipe-urile bidirecţionale dacă handle-ul pipe-ului deţinut de procesul apelant este setat în mod de citire. Un proces poate obţine informaţii despre un pipe cu nume apelând <tt>GetNamedPipeInfo</tt>, care returnează tipul pipe-ului, dimensiunile bufferelor de intrare şi ieşire şi numărul maxim de instanţe ce pot fi create pentru pipe-ul respectiv. <tt>GetNamedPipeHandleState</tt> dă informaţii despre modurile de citire şi aşteptare ale handle-ului pipe-ului, numărul curent de instanţe ale pipe-ului, şi alte informaţii pentru pipe-urile care comunică printr-o reţea. <tt>SetNamedPipeHandleState</tt> setează modurile de citire şi aşteptare ale unui handle de pipe. Pentru clienţii pipe ce comunică cu un server la distanţă, funcţia controlează de asemenea şi numărul maxim de bytes ce pot fi adunaţi sau timpul maxim de aşteptare până la transmiterea unui mesaj (presupunând că handle-ul clientului nu a fost deschis cu modul write-through activat).

= Quiz =

Pentru auto-evaluare răspundeți la întrebările din acest quiz.

= Exerciţii =

Linux

 * utilizând programul <tt>top</tt> sau <tt>htop</tt> (<tt>apt-get install htop</tt> pentru sistemele Debian) urmăriți procesele aflate în execuție

Windows

 * cu <tt>Windows Task Manager</tt> (în Windows, combinația de taste Ctrl+Shift+Esc) sau instalând <tt>Process Explorer</tt> de la Sysinternals urmăriți procesele aflate în execuție

Precizări

 * Exerciţiile sunt independente de platformă.
 * Puteţi întrebuinţa macro-ul <tt>CHECK</tt>, definit în <tt>utils.h</tt>, pentru testarea valorilor întoarse de funcţii.
 * Exerciţiile 1-4 decurg unul din altul. De exemplu, după ce aţi rezolvat task-ul 1, copiaţi fişierul <tt>1.c </tt>din directorul <tt>1</tt> în directorul <tt>2</tt> şi redenumiţi-l <tt>2.c</tt>. La exerciţiile 2-4 nu uitaţi să aşteptaţi în părinte terminarea copilului.

Linux / Windows
Utilizaţi arhiva de sarcini a laboratorului.


 * 1) (0.5p) <tt>system</tt>
 * 2) * Realizaţi un program care să execute o comandă transmisă ca parametru folosind funcţia de bibliotecă system.
 * 3) * Presupunând că programul vostru gestionează doar primul argument din linia de comandă, cum procedaţi pentru a trimite parametri comenzii respective? (ex: <tt>ls -la</tt>)?
 * 4) * Hints:
 * 5) ** Citiţi secţiunea Rularea unui program executabil
 * 6) ** Puteți folosi label-ul <tt>error</tt> și instrucțiunea <tt>goto</tt> pentru a ieși cu eroare.
 * 7) (1.5p) <tt>fork</tt>/<tt>exec</tt>, <tt>CreateProcess</tt>
 * 8) * Realizaţi un program care execută linia de comandă cu <tt>fork</tt>/<tt>exec</tt>, respectiv <tt>CreateProcess</tt>.
 * 9) * Primul argument al programului va fi numele unui program de lansat, următoarele fiind transmise mai departe ca argumente programului respectiv.
 * 10) * Hints:
 * 11) ** Linux: Citiţi secţiunea my_system
 * 12) ** Windows: Citiţi secţiunea Exec
 * 13) ** Recomandăm folosirea <tt>execvp</tt> și copierea argumentelor într-un nou vector.
 * 14) ** Nu uitați să așteptați în părinte procesul copil.
 * 15) (1.5p) Redirectare
 * 16) * Modificați programul de la exercițiul 2 astfel încât să execute comenzi cu ieșirea redirectată în fișierul <tt>3.out</tt>.
 * 17) * De exemplu secvența: <tt>./program "ls"</tt> ar trebui să pună toată ieșirea comenzii ls într-un fișier.
 * 18) * Dacă fișierul nu există va trebui creat. Dacă există trebuie trunchiat.
 * 19) * Hints:
 * 20) ** Linux
 * 21) *** imediat după <tt>fork</tt> dar înainte de <tt>exec</tt>, obțineți un file descriptor la fișier și apoi faceți un <tt>dup2</tt>.
 * 22) *** Citiţi secţiunea Copierea descriptorilor de fișier.
 * 23) ** Windows
 * 24) *** <tt>CreateProcess</tt> primește, prin intermediul argumentului de tipul <tt>LPSTARTUPINFO</tt>, handle-urile <tt>hStdInput</tt>, <tt>hStdOutput</tt> și <tt>hStdError</tt>.
 * 25) *** <tt>LPSTARTUPINFO</tt> este pointer la o structură de tipul <tt>STARTUPINFO</tt>.
 * 26) *** Citiţi secţiunea Moștenirea descriptorilor de resurse la CreateProcess.
 * 27) *** Nu uitați să activați flag-ul de moștenire a descriptorului la crearea fișierului folosind <tt>CreateFile</tt> (structura <tt>SECURITY_ATTRIBUTES</tt>).
 * 28) (2p) Pipe anonim, variabile de mediu.
 * 29) * Modificaţi programul de la exerciţiul 3 astfel încât să utilizeze un pipe anonim, al cărui capăt de citire să corespundă intrării standard a copilului.
 * 30) * In procesul copil rulaţi, în locul parametrului primit la exerciţiul 3, programul <tt>child</tt> din arhivă.
 * 31) * Părintele va scrie în pipe un şir de caractere pe care <tt>child</tt> îl va afişa la <tt>stdout</tt>.
 * 32) * Observaţi că procesul copil afişează valoarea variabilei de mediu <tt>MYVAR</tt>.
 * 33) * Definiţi-o în părinte cu ce valoare doriţi, dar înaintea creării copilului.
 * 34) * Hints:
 * 35) ** Linux
 * 36) *** Citiţi secţiunea Apelul pipe.
 * 37) *** În procesul părinte închideți capătul de citire al pipe-ului, iar în procesul copil capătul de scriere.
 * 38) *** Citiţi secţiunea Variabile de mediu.
 * 39) ** Windows
 * 40) *** Este necesar ca handle-ul de scriere sa nu poată fi moştenit in procesul creat (altfel, copilul, neavând acces la el, nu-l va putea închide si nu se va mai trimite EOF prin pipe).
 * 41) *** Folosiţi funcţia <tt>SetHandleInformation</tt> pentru împiedicarea moştenirii. Citiţi mai multe aici.
 * 42) *** Citiţi secţiunea Pipe-uri anonime.
 * 43) *** Citiţi secţiunea Variabile de mediu.

Pentru acasă

 * 1) Pipe cu nume
 * 2) * Realizaţi două programe denumite <tt>client</tt> si <tt>server</tt> care interacţionează printr−un pipe cu nume.
 * 3) * FIFO−ul se numeşte <tt>myfifo</tt>. Dacă nu există, este creat de server.
 * 4) * Serverul trebuie rulat înaintea oricărui client.
 * 5) * Clientul primeşte ca argument la rulare un număr n. Va interoga, prin intermediul FIFO−ului, serverul, pentru a afla daca n este prim sau nu.
 * 6) * Serverul va afişa rezultatul la ieşirea standard.
 * 7) * Hints:
 * 8) ** Linux: Citiţi secţiunea FIFO (Pipe-uri cu nume).
 * 9) ** Windows
 * 10) *** Puteţi porni de la exemplul din documentaţia <tt>CreateNamedPipe</tt>.
 * 11) *** Citiţi secţiunea Pipe-uri cu nume.

= Soluţii =

Soluţii exerciţii laborator 3

= Resurse utile =


 * 1) Wikipedia - Fork
 * 2) Opengroup - Fork
 * 3) About Fork and Exec
 * 4) YoLinux Tutorial - Fork, Exec and Process Control
 * 5) Wikipedia - Windows Handles and Data Types
 * 6) MSDN: Processes and Threads
 * 7) C++ CreateProcess example
 * 8) MSDN: Managing Heap Memory
 * 9) Windows XP and 2003 Server Boot.ini options

= Note =


 * 1) Variabila de mediu $HOME este expandată de interpretorul de comenzi la directorul utilizatorului; presupunând că lucrăm cu utilizatorul tavi, atunci o posibilă expandare a variabilei $HOME ar putea fi /home/tavi
 * 2) În directorul /etc/rc.d/ se găsesc scripturi de inițializare a sistemului; variabila de mediu $RUNLEVEL este setată la bootare la una din valorile: 1 (single user), 2 (multi-user cu rețea), 3 (multi user), 5 (multi-user grafic - X11); pentru mai multe informații: man inittab
 * 3) Apelul de sistem <tt>ptrace</tt> permite unui proces să observe și să controleze execuția altui proces; procesul care "urmărește" poate să inspecteze și să modifice imaginea executată de procesul "urmărit"; pentru mai multe informații: man ptrace
 * 4) În general, pentru a redirecta output-ul unei comenzi într-un fișier shell-ul folosește următoarea sintaxă: <tt>comanda [argumente] &gt; nume_fisier</tt>. Mai multe informații în laboratorul următor.
 * 5) Verificați documentația Microsoft în legătură cu opțiunile disponibile pentru Boot.ini.