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.
Informațiile despre procese sunt ținute într-o structură numită Process Control Block (PCB), câte una pentru fiecare proces existent în sistem. Printre cele mai importante informații regăsim:
PID - identificatorul procesului
spațiu de adresă
registre generale, PC (contor program), SP (indicator stivă)
lista de semnale blocate, ignorate sau care așteaptă să fie trimise procesului
handler-ele de semnale
informațiile referitoare la sistemele de fișiere (directorul rădăcină, directorul curent)
Î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 lucrul cu procese: creare, terminare, așteptarea terminării. Totodată există apeluri pentru duplicarea descriptorilor de resurse între procese, ori închiderea acestor descriptori.
Procesele pot avea o organizare:
ierarhică - de exemplu pe Linux - există o structură arborescentă în care rădăcina este procesul init (pid = 1).
neierarhică - de exemplu pe Windows.
Î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ă. ( Linux, Windows )
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 pentru a fi citite de la celălalt capăt). Sistemul de operare garantează sincronizarea între operațiile de citire și scriere la cele două capete. ( Linux, Windows )
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 sunt accesibile doar prin moștenire. Aceste pipe-uri nu mai există după ce procesele și-au terminat execuția.
pipe-uri cu nume: au suport fizic - există ca fișiere cu drepturi de acces. Aceasta înseamnă că ele vor exista independent de procesul care le creează și pot fi folosite de procese neînrudite.
În UNIX un proces se creează folosind apelul de sistem fork:
pid_t fork(void);
Efectul este crearea unui nou proces (procesul copil), copie a celui care a apelat fork (procesul părinte). Procesul copil primește un nou process id (PID) de la sistemul de operare.
Atenție! - acestă funcție este apelată o dată și se întoarce (în caz de succes) de două ori, așa cum se poate vedea în figura din dreapta.
Pentru aflarea PID-ului procesului curent ori al procesului părinte se va apela una dintre funcțiile de mai jos.
Funcția getpid întoarce PID-ul procesului apelant:
pid_t getpid(void);
Funcția getppid întoarce PID-ul procesului părinte al procesului apelant:
Familia de funcții exec 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țializate registrele IP (contorul program) și SP (indicatorul stivă) și registrele generale. Măștile de semnale ignorate și blocate sunt setate la valorile implicite, ca și handler-ele semnalelor. PID-ul și descriptorii de fișier care nu au setat flag-ul CLOSE_ON_EXEC rămân neschimbați (implicit, flag-ul CLOSE_ON_EXEC nu este setat).
Se observă că primul argument este numele programului. Ultimul argument al listei de parametri trebuie să fie NULL, indiferent dacă lista este sub formă de vector (execv*) sau sub formă de argumente variabile (execl*).
execl și execv nu caută programul dat ca parametru în PATH, astfel că acesta trebuie însoțit de calea completă. Versiunile execlp și execvp caută programul și în PATH.
Toate funcțiile exec* sunt implementate prin apelul de sistem execve
Familia de funcții wait suspendă execuția procesului apelant până când procesul (procesele) specificat în argumente fie s-a terminat, fie a fost oprit (SIGSTOP).
pid_t waitpid(pid_t pid,int*status,int options);
Starea procesului interogat se poate afla examinând status cu macrodefiniții precum WEXITSTATUS, care întoarce codul de eroare cu care s-a încheiat procesul așteptat, evaluând cei mai nesemnificativi 8 biți.
Există o variantă simplificată, care așteaptă orice proces copil să se termine. Următoarele secvențe de cod sunt echivalente:
wait(&status);| waitpid(-1,&status,0);
În caz că se dorește doar așteptarea terminării procesului copil, nu și examinarea statusului:
Pentru terminarea procesului curent, Linux pune la dispoziție apelul de sistem exit. Dintr-un program C există trei moduri de invocare a acestui apel de sistem:
2. apelul _Exit din biblioteca standard C (conform C99):
void _Exit(int status);
3. apelul exit din biblioteca standard C (conform C89, C99):
void exit(int status);
_exit(2) și _Exit(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 init
părintelui procesului îi va fi trimis un semnal SIGCHLD. Tot acestuia îi va fi întoarsă valoarea status, ca rezultat al unei funcții de așteptare (wait sau waitpid).
În plus, exit(3):
va șterge toate fișierele create cu tmpfile()
va scrie bufferele streamurilor deschise și le va închide
Notă: Conform ISO C, un program care se termină cu return x din main() va avea același comportament ca unul care apelează exit(x).
Pentru terminarea unui alt proces din sistem, se va trimite un semnal către procesul respectiv prin intermediul apelului de sistem kill. Mai multe detalii despre kill și semnale în laboratorul de semnale .
dup duplică descriptorul de fișier oldfd și întoarce noul descriptor de fișier, sau -1 în caz de eroare:
int dup(int oldfd);
dup2 duplică descriptorul de fișier oldfd în descriptorul de fișier newfd; dacă newfd există, mai întâi va fi închis. Întoarce noul descriptor de fișier, sau -1 în caz de eroare:
int dup2(int oldfd,int newfd);
Descriptorii de fișier sunt, de fapt, indecși în tabela de fișiere deschise. Tabela este populată cu pointeri către structuri 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, flag-uri) sunt partajate de cei doi file descriptori. Aceasta înseamnă că operațiile ce modifică aceste informații pe unul dintre file descriptori (de ex. lseek) sunt vizibile și pentru celălalt file descriptor (duplicat). Atentie! Există și o excepție: flag-ul CLOSE_ON_EXEC nu este partajat (acest flag nu este ținut în structura menționată mai sus).
Descriptorii de fișier ai procesului părinte se moștenesc în procesul copil în urma apelului fork. După un apel exec, descriptorii de fișier sunt păstrați, excepție făcând cei care au flag-ul CLOSE_ON_EXEC setat.
fcntl
Pentru a seta flag-ul CLOSE_ON_EXEC se folosește funcția fcntl, cu un apel de forma:
fcntl(file_descriptor, F_SETFD, FD_CLOEXEC);
O_CLOEXEC
fcntl poate activa flag-ul FD_CLOEXEC doar pentru descriptori de fișier deja existenți. În aplicații cu mai multe fire de execuție, între crearea unui descriptor de fișier și un apel fcntl se poate interpune un apel exec pe un alt fir de execuție.
Cum, implicit, descriptorii de fișiere sunt moșteniți după un apel exec, deși programatorul a dorit ca acesta să nu poată fi accesat după exec, nu poate preveni apariția unui apel exec între creare și fcntl.
Pentru a rezolva această condiție de cursă, s-au introdus în Linux 2.6.27 (2008) versiuni noi a unor apeluri de sistem:
Aceste variante ale apelurilor de sistem adaugă câmpul flags, prin care se poate specifica O_CLOEXEC, pentru a crea și activa CLOSE_ON_EXEC în mod atomic. Numărul din numele apelului de sistem, specifică numărul de parametri ai apelului.
Apelurile de sistem care creează descriptori de fișiere care primeau deja un parametru flags (e.g. open) au fost doar extinse să accepte și O_CLOEXEC.
În cadrul unui program se pot accesa variabilele de mediu, prin evidențierea celui de-al treilea parametru (opțional) al funcției main, ca în exemplul următor:
int main(int argc,char**argv,char**environ)
Acesta desemnează un vector de pointeri la șiruri de caractere, ce conțin variabilele de mediu și valorile lor. Șirurile de caractere sunt de forma VARIABILA=VALOARE. Vectorul e terminat cu NULL.
getenv întoarce valoarea variabilei de mediu denumite name, sau NULL dacă nu există o variabilă de mediu denumită astfel:
char* getenv(constchar*name);
setenv adaugă în mediu variabila cu numele name (dacă nu există deja) și îi setează valoarea la value. Dacă variabila există și replace e 0, acțiunea de setare a valorii variabilei e ignorată; dacă replace e diferit de 0, valoarea variabilei devine value:
int setenv(constchar*name,constchar*value,int replace);
unsetenv șterge din mediu variabila denumită name:
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 anonim trebuie să aibă un grad de rudenie; de obicei, un proces care creează un pipe va apela după aceea fork, iar pipe-ul se va folosi pentru comunicarea între părinte și fiu. În orice caz, procesele care comunică prin pipe-uri anonime nu pot fi create de utilizatori diferiți ai sistemului.
Vectorul filedes conține după execuția funcției 2 descriptori de fișier:
filedes[0], deschis pentru citire;
filedes[1], deschis pentru scriere.
Mnemotehnică: STDIN_FILENO este 0 (citire), STDOUT_FILENO este 1 (scriere).
Observații:
citirea/scrierea din/în pipe-uri este atomică dacă nu se citesc/scriu mai mult de PIPE_BUF1) octeți.
citirea/scrierea din/în pipe-uri se realizează cu ajutorul funcțiilor read/write.
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:
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 pipe cu nume (FIFO) pot exista mai multe procese care scriu date.
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 write returnează eroare și variabila errno are valoarea EPIPE.
Atentie! Cea mai frecventă greșeală, relativ la lucrul cu pipe-urile, provine din neglijarea faptului că nu se trimite EOF 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 fork, nu uitați să închideți capetele pipe-ului în procesul părinte).
Elimină necesitatea ca procesele care comunică să fie înrudite. Astfel, fiecare proces își poate deschide pentru citire sau scriere fișierul pipe cu nume (FIFO), un tip de fișier special, care păstrează în spate caracteristicile unui pipe. Comunicația se face într-un sens sau în ambele sensuri. Fișierele de tip FIFO pot fi identificate prin litera p în primul câmp al drepturilor de acces (ls -l).
Apelul de bibliotecă pentru crearea pipe-urilor de tip FIFO este mkfifo:
int mkfifo(constchar*pathname, mode_t mode);
După ce pipe-ul FIFO a fost creat, acestuia i se pot aplica toate funcțiile pentru operații obișnuite pentru lucrul cu fișiere: open, close, read, write.
Modul de comportare al unui pipe FIFO după deschidere este afectat de flagul O_NONBLOCK:
dacă O_NONBLOCK nu este specificat (cazul normal), atunci un open 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.
dacă se specifică O_NONBLOCK, atunci deschiderea pentru citire revine imediat, dar o deschidere pentru scriere poate returna eroare cu errno având valoarea ENXIO, dacă nu există un alt proces care a deschis același FIFO pentru citire.
Atunci când se închide ultimul descriptor de fișier al capătului de scriere pentru un FIFO, se generează un „sfârșit de fișier” – EOF – pentru procesul care citește din FIFO.
Î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 CreateProcess.
BOOL bRes = CreateProcess(
NULL,// No module name"notepad.exe"// Command line
NULL,// Process handle not inheritable
NULL,// Thread handle not inheritable
FALSE,// Set handle inheritance to false0,// No creation flags
NULL,// Use parent's environment block
NULL,// Use parent's starting directory&si,// Pointer to STARTUPINFO structure&pi // Pointer to PROCESS_INFORMATION);// structure
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 handle-uri ale proceselor create de el, însă handle-urile pot fi duplicate între procese ceea ce duce la situația în care un proces deține handle-uri ale unor procese care nu sunt create de el, deci ierarhia implicită dispare.
Așteptarea inițializării procesului creat
Deoarece funcția CreateProcess se î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 WaitForInputIdle.
Funcția va cauza blocarea firului de execuție apelant până în momentul în care procesul hProcess și-a terminat inițializarea și așteaptă date de intrare. Funcția poate fi folosită oricând pentru a aștepta ca procesul hProcess 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 dwMilliseconds.
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 WaitForSingleObject ori WaitForMultipleObjects.
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.
Dacă procesul hProcess nu s-a terminat încă, funcția va întoarce în lpExitCode codul de terminare STILL_ACTIVE. Dacă procesul s-a terminat, se va întoarce codul său de terminare care poate fi:
Pentru terminarea procesului curent, Windows API pune la dispoziție funcția ExitProcess.
void ExitProcess(UINT uExitCode);
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.
Atenție!ExitProcess nu se ocupă de eliberarea resurselor bibliotecii standard C. Pentru a asigura o finalizare corectă a programului trebuie apelat exit.
Pentru terminarea unui alt proces din sistem se va apela funcția TerminateProcess.
Se va iniția terminarea procesului hProcess și a tuturor firelor sale de execuție și se vor revoca operațiile de intrare/ieșire neterminate după care funcția TerminateProcess va întoarce imediat. Toți descriptorii de resurse (handle) ai procesului sunt închiși. Funcția TerminateProcess este periculoasă și se recomandă folosirea ei doar în cazuri extreme, deoarece ea nu notifică DLL-urile de care este atașat procesul hProcess asupra detașării acestuia, lăsând astfel alocate eventualele date rezervate de DLL în spațiul de adrese al procesului.
Terminarea unui proces NU implică terminarea proceselor create de acesta.
După un apel CreateProcess, handle-urile din procesul părinte pot fi moștenite în procesul copil.
Atenție! Pentru ca un handle să poată fi moștenit în procesul creat, trebuie îndeplinite 2 condiții:
membrul bInheritHandle, al structurii SECURITY_ATTRIBUTES, transmise lui CreateFile, trebuie să fie TRUE
parametrul bInheritHandles, al lui CreateProcess, trebuie să fie TRUE.
Handle-urile moștenite sunt valide doar în contextul procesului copil.
Cei 3 descriptori speciali de fișier pot fi obținuți apelând funcția GetStdHandle:
HANDLE GetStdHandle(DWORD nStdHandle);
cu unul din parametrii:
STD_INPUT_HANDLE
STD_OUTPUT_HANDLE
STD_ERROR_HANDLE
Pentru redirectarea handle-urilor standard în procesul copil puteți folosi membrii hStdInput, hStdOutput, hStdError ai structurii STARTUPINFO, transmise lui CreateProcess. În acest caz, membrul dwFlags al aceleiași structuri trebuie setat la STARTF_USESTDHANDLES. Dacă se dorește ca anumite handle-uri să rămână implicite, li se poate atribui handle-ul întors de GetStdHandle.
STARTUPINFO si;
...
/* initialize process startup info structure */
ZeroMemory(&si,sizeof(si));
si.cb=sizeof(si);/* setup flags to allow handle inheritence (redirection) */
si.dwFlags|= STARTF_USESTDHANDLES;
Atenție! Pentru a realiza redirectarea corespunzător câmpurile hStdInput, hStdOutput, hStdError din structura STARTUPINFO trebuie inițializate.
Alte proprietăți ale procesului părinte care pot fi moștenite sunt variabilele de mediu și directorul curent. Nu vor fi moștenite handle-uri ale unor zone de memorie alocate de procesul părinte și nici pseudo-descriptori precum cei întorși de funcția GetCurrentProcess.
Handle-ul din procesul părinte și cel moștenit în procesul copil vor referi același obiect, exact ca în cazul duplicării. De asemenea, handle-ul moștenit în procesul copil are aceeași valoare și aceleași drepturi de acces ca și handle-ul din procesul părinte. Pentru a folosi handle-ul moștenit, procesul copil va trebui să-i cunoască valoarea și ce obiect referă. Aceste informații trebuie să fie pasate de părinte printr-un mecanism extern (IPC etc).
care va seta variabila lpName la valoarea specificată de lpValue. Funcția se va folosi și pentru ștergerea unei variabile de mediu prin transmiterea unui parametru lpValue = NULL. SetEnvironmentVariable are efect doar asupra variabilelor de mediu ale utilizatorului și nu poate modifica variabile de mediu globale.
Î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ă Environment Block-ul utilizatorului respectiv. Acest Environment Block este similar cu variabila environ, din Linux. Mai multe detalii aici:
Un utilizator are acces la propriul Environment Block prin apelul funcției GetEnvironmentStrings:
LPTCH GetEnvironmentStrings(void);
care îi va întoarce un pointer spre acesta, pe care îl poate elibera cu FreeEnvironmentStrings:
Și, bineînțeles, trebuie cunoscută parola utilizatorului respectiv.
Environment Block-ul, obținut prin CreateEnvironmentBlock, poate fi transmis ca parametru funcției CreateProcessAsUser, și se va distruge prin apelul funcției DestroyEnvironmentBlock:
Ca și pe Linux, pipe-urile anonime de pe Windows sunt unidirecționale. Fiecare pipe are două capete reprezentate de câte un handle: un handle de citire și un handle de scriere. Funcția de creare a unui pipe este CreatePipe:
CreatePipe(&hReadPipe,&hWritePipe,&sa,//pentru moștenire sa.bInheritHandle=TRUE0//dimensiunea default pentru pipe);
Atenție! Pentru a moșteni un pipe anonim, este nevoie ca parametrul bInheritHandle din structura LPSECURITY_ATTRIBUTES să fie setat pe TRUE.
CreatePipe creează atât pipe-ul, cât și handler-urile folosite pentru scriere/citire din/în pipe cu ajutorul funcțiilor ReadFile și WriteFile.
ReadFile 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.
WriteFile 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, WriteFile rămâne blocat până când alt proces sau thread folosește ReadFile pentru a face loc în buffer.
Pipe-urile anonime sunt implementate folosind un pipe cu nume unic. De aceea se poate pasa un handle al unui pipe anonim unei funcții care cere un handle al unui pipe cu nume.
În Windows, un pipe cu nume este un pipe unidirecțional (inbound ori outbound) 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 handle-uri și buffere.
Pipe-urile cu nume au următoarele caracteristici care le diferențiază de cele anonime:
sunt orientate pe mesaje - se pot transmite mesaje de lungime variabilă (nu numai byte stream);
sunt bidirecționale - două procese pot schimba mesaje pe același pipe;
pot exista mai multe instanțe ale aceluiași pipe
poate fi accesat din rețea - comunicația între două procese aflate pe mașini diferite este aceeași cu cea între procese aflate pe aceeași mașină.
Funcția returnează un handle către capătul serverului la pipe. Acest handle poate fi transmis funcției ConnectNamedPipe pentru a aștepta conectarea unui proces client la o instanță a unui pipe.
Un client se conectează transmițând numele pipe-ului la una din funcțiile CreateFile sau CallNamedPipe - ultima mai utilă pentru transmiterea de mesaje.
Un exemplu funcțional folosind pipe-uri cu nume se află aici
Mai multe detalii despre moștenirea pipe-urilor se pot găsi aici.
Detalii extra despre pipe-urile cu nume
Moduri de comunicare
Comunicația prin pipe-urile cu nume poate fi de tip:
mesaj
se scriu/citesc date sub formă de mesaje;
este necesară cunoașterea lungimii mesajului;
se scriu/citesc doar mesaje complete;
creat cu PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE.
flux de octeți
nu există nicio garanție asupra numărului de octeți care sunt citiți/scriși în orice moment;
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);
creat cu PIPE_TYPE_BYTE | PIPE_READMODE_BYTE (implicit).
Numire
Crearea unui pipe cu nume se poate face numai pe mașina locală(reprezentată prin primul .) cu un string de forma:
\\.\pipe\[path]pipename
Accesarea unui pipe cu nume se poate face folosind ca parametru un string de forma:
\\servername\pipe\[path]pipename
Funcții utile
GetNamedPipeHandleState - întoarce informații despre anumite atribute cum ar fi: tipul de comunicare (mesaj sau byte-stream), numărul de instanțe, dacă a fost deschisă în mod blocant sau neblocant.
De ce, pentru procesul indicat de executabilul orphan (coloana CMD), pid-ul procesului părinte (coloana PPID) devine 1?
Tiny-Shell
Intrați în directorul 3-tiny
Următoarele exerciții au ca scop implementarea unui shell minimal, care oferă suport pentru execuția unei singure comenzi externe cu argumente multiple și redirectări. Shell-ul trebuie să ofere suport pentru folosirea și setarea variabilelor de mediu.
Observație: Pentru a ieși din tiny shell folosiți exit sau CTRL+D
(1 punct) Execuția unei comenzi simple
Creați un nou proces care să execute o comandă simplă.
Funcția simple_cmd primește ca argument un vector de șiruri ce conține comanda și parametrii acesteia
Deschideți proiectul (fișierul .sln) și compilați primul subproiect: 1-bomb
Inspectați sursa 1-bomb.c. Ce credeți că face?
Înainte de a rula 1-bomb.exe, porniți Task Manager (CTRL + ALT + DEL; În mașina virtuală se va folosi CTRL + ALT + Insert)
Rulați 1-bomb.exe. Ce s-a întâmplat?
Tiny-Shell on Windows
Ne propunem să continuăm implementarea de Tiny-Shell
Important: Compilarea se va realiza din Visual Studio sau din command-prompt-ul de Visual Studio, iar rularea executabilului tiny.exe se va realiza din Cygwin.
Pentru a ajunge din Cygwin pe Desktop:
$ cd c:
$ cd Users/Student/Desktop/
(0.5 puncte) Executarea unei comenzi simple
Partea de executare a unei comenzi simple și a variabilelor de mediu este deja implementată.
Urmăriți în sursă funcția RunSimpleCommand.
Testați funcționalitatea prin comenzi de tipul:
./tiny
>ls-al>exit
(1.5 puncte) Redirectare
Realizați redirectarea tuturor HANDLE-relor
Hints:
Completați funcția RedirectHandle.
Atenție! Trebuie inițializate toate handle-rele structurii STARTUPINFO.
Puteți porni de la exemplul din documentația CreateNamedPipe.
Atenție: Dacă ReadFile întoarce FALSE, iar mesajul de eroare (ce întoarce GetLastError()) este ERROR_BROKEN_PIPE, înseamnă că au fost închise toate capetele de scriere.
(1 so karma) Magic
Intrați în directorul lin/5-magic și deschideți sursa magic.c
Completați doar condiția instrucțiunii if pentru a obține la rulare mesajul “Hello World”.