laborator 3 procese

10
Laborator 3. 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. Pentru citirea, modificarea ori ștergerea unei variabile de mediu, biblioteca standard C pune la dispoziţie apelurile getenv, setenv, unsetenv precum și un pointer la tabela de variabile de mediu environ. Rularea unui program executabil Modul cel mai simplu prin care se poate crea un nou proces este prin folosirea funcţiei de bibliotecă system: int system(const char *command); Apelul acestei funcţii are ca efect execuţia ca o comandă shell a comenzii reprezentate prin șirul de caractere command. Să luăm ca exemplu următorul program C: Exemplu 1. sys1.c #include <stdlib.h> int main(int argc, char **argv) { system("ls -la $HOME"); } care este echivalent cu $ sh -c "ls -la $HOME" Încă un exemplu: Exemplu 2. sys2.c #include <stdlib.h> int main(int argc, char **argv) { system("cd /etc/rc.d/rc$RUNLEVEL.d/; ls -la"); } program C care este echivalent cu $ sh -c "cd /etc/rc.d/rc$RUNLEVEL.d/; ls -la" Implementarea system: se creează un nou proces cu fork; procesul copil execută prin intermediul exec programul sh cu argumentele -c "comanda", 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. Efectul este crearea unui nou proces - procesul copil, copie a celui care a apelat fork - procesul părinte. Copilul primește un nou PID de la sistemul de operare. Secvenţa clasică de creare a unui proces este prezentată în continuare:

Upload: daedalus-moon

Post on 16-Dec-2015

8 views

Category:

Documents


2 download

DESCRIPTION

Procese in Linux

TRANSCRIPT

  • Laborator 3. Procese n Linux

    Apelurile de sistem puse la dispoziie de Linux pentru gestionarea proceselor sunt: fork i exec pentru crearea unui proces i respectiv modificarea imaginii unui proces, wait i waitpid pentru ateptarea terminrii unui proces i exit pentru terminarea unui proces. Pentru copierea descriptorilor de fiier Linux pune la dispoziie apelurile de sistem dup i dup2. Pentru citirea, modificarea ori tergerea unei variabile de mediu, biblioteca standard C pune la dispoziie apelurile getenv, setenv, unsetenv precum i un pointer la tabela de variabile de mediu environ.

    Rularea unui program executabil

    Modul cel mai simplu prin care se poate crea un nou proces este prin folosirea funciei de bibliotec system:

    int system(const char *command);

    Apelul acestei funcii are ca efect execuia ca o comand shell a comenzii reprezentate prin irul de caractere command. S lum ca exemplu urmtorul program C:

    Exemplu 1. sys1.c

    #include int main(int argc, char **argv) { system("ls -la $HOME"); } care este echivalent cu $ sh -c "ls -la $HOME"

    nc un exemplu:

    Exemplu 2. sys2.c

    #include int main(int argc, char **argv) { system("cd /etc/rc.d/rc$RUNLEVEL.d/; ls -la"); } program C care este echivalent cu $ sh -c "cd /etc/rc.d/rc$RUNLEVEL.d/; ls -la"

    Implementarea system: se creeaz un nou proces cu fork; procesul copil execut prin intermediul exec programul sh cu argumentele -c "comanda", timp n care printele ateapt terminarea procesului copil.

    Crearea unui proces

    n UNIX singura modalitate de creare a unui proces este prin apelul de sistem fork. Efectul este crearea unui nou proces - procesul copil, copie a celui care a apelat fork - procesul printe. Copilul primete un nou PID de la sistemul de operare. Secvena clasic de creare a unui proces este prezentat n continuare:

  • Exemplu 3. ex_fork.c

    #include #include ... switch (pid = fork()) { case -1: /* fork failed */ printf("fork failed\n"); exit(-1); case 0: /* child starts executing here */ ... default: /* parent starts executing here */ printf("created process with pid %d\n", pid); ... }

    Dup cum se observ din comentariile de mai sus, apelul de sistem fork ntoarce PID-ul noului proces n procesul printe i valoarea 0 n procesul copil. Pentru aflarea PID-ului procesului curent ori al procesului printe se va apela una din funciile:

    pid_t getpid(void);

    Funcia getpid ntoarce PID-ul procesului apelant.

    pid_t getppid(void);

    Funcia getppid ntoarce PID-ul procesului printe al procesului apelant.

    nlocuirea imaginii unui proces

    Familia de funcii exec va executa un nou program, nlocuind imaginea procesului curent, cu cea dintr-un fiier (executabil). Spaiul de adrese al procesului va fi nlocuit cu unul nou, creat special pentru execuia fiierului. De asemenea vor fi reiniializai regitrii IP (EIP/RIP - contorul program) i SP (ESP/RSP - indicatorul stiv) i regitrii generali. Mtile de semnale ignorate i blocate sunt setate la valorile implicite, ca i handler-ele semnalelor. PID-ul i descriptorii de fiiere care nu au setat flagul CLOSE_ON_EXEC rmn neschimbai (implicit flagul CLOSE_ON_EXEC nu este setat).

    int execv(const char *filename, char *const argv[]);

    Va executa programul descris de pointerul ctre irul de caractere filename; vectorul argv conine pointeri ctre iruri de caractere ce descriu argumentele cu care programul reprezentat de filename va fi executat; ultimul element al vectorului trebuie s fie setat pe NULL. Calea ctre program trebuie s fie complet (adica nu se caut n PATH). Primul argument va fi numele programului.

    int execl(const char *filename, const char *arg0, ...);

    De data aceasta argumentele vor fi date ca parametri ai funciei. Ultimul parametru trebuie s fie (char *)NULL. Primul argument va fi numele programului.

    int execve(const char *filename, char *const argv[], char *const env[]);

    Are aceeai comportare ca execv numai c mai primete un vector de pointeri la iruri de caractere care descriu variabilele de mediu. Ultimul element al vectorului env trebuie s fie setat pe NULL. irurile de caractere trebuie s fie de forma "VARIABILA=VALOARE".

    int execle(const char *filename, const char *arg0, ..., char *const env[]);

  • Are aceeai comportare ca execl numai c mai primete un vector de pointeri la iruri de caractere care descriu variabilele de mediu. Ultimul element al vectorului env trebuie s fie setat pe NULL. irurile de caractere trebuie s fie de forma "VARIABILA=VALOARE".

    int execvp(const char *filename, char *const argv[]);

    Similar cu execv doar c adresa programului nu trebuie s fie absolut, programul va fi cutat n toate directoarele specificate n variabila de mediu PATH.

    int execlp(const char *filename, const char *arg0, ...);

    Similar cu execl doar c adresa programului nu trebuie s fie absolut, programul va fi cutat n toate directoarele specificate n variabila de mediu PATH.

    Folosirea oricrei funcii din familia exec necesit includerea header-ului unistd.h.

    Ateptarea terminrii unui proces

    Familia de funcii wait suspend execuia procesului apelant pn cnd procesul (procesele) specificate n argumente fie s-au terminat fie au fost oprite (SIGSTOP).

    pid_t waitpid(pid_t pid, int *status, int options);

    Valoarea argumentului pid poate fi:

    > 0 i atunci trebuie s fie PID-ul unui proces copil 0 (WAIT_MYGRP) i atunci identific orice proces copil din grupul de procese din care face

    parte procesul apelant -1 (WAIT_ANY) orice proces copil < -1 cnd valoarea absolut a parametrului reprezint identificatorul unui grup; funcia

    ntoarce numai cnd cel puin un proces copil aparinnd grupului respectiv se termin sau este oprit

    Parametrul options poate fi setat cu unul sau mai multe din urmtoarele flaguri (combinate prin SAU pe bii):

    WNOHANG dac niciunul dintre procesele specificate nu s-a terminat, se iese imediat din apelul funciei waitpid (procesul care a apelat waitpid nu se blocheaz) WUNTRACED waitpid se ntoarce i dac unul dintre procesele specificate s-a oprit, iar informaii despre aceast oprire nu au fost nc raportate; dac este setat acest flag, waitpid se ntoarce i pentru procesele copil care s-au oprit, dar care nu se afl printre procesele "urmrite" de procesul apelant; informaii despre copiii "urmrii" sunt furnizate chiar dac acest flag nu este precizat WCONTINUED pentru versiuni de kernel mai noi dect 2.6.10, funcia ntoarce i atunci cnd procesul copil iniial blocat ncepe s-i continue execuia ca urmare a primirii semnalului SIGCONT

    Funcia va ntoarce PID-ul procesului a crui stare e raportat; infomaiile de stare sunt depuse ca int la adresa indicat prin argumentul status. Funcia se blocheaz dac niciunul din procesele specificate nu s-a terminat (sau dac niciunul dintre ele nu s-a oprit i este setat WUNTRACED, respectiv pornit atunci cnt WCONTINUED este precizat), sau returneaz imediat 0 dac i WNOHANG este prezent. n caz de eroare se ntoarce -1, iar errno este setat pe:

    EINTR apelul a fost ntrerupt de un semnal ECHILD nu s-a specificat un PID de proces copil valid EINVAL s-a specificat un flag invalid pentru options.

  • Starea procesului interogat se poate afla examinnd status cu urmtoarele macrodefiniii:

    WIFEXITED(status) ntoarce o valoare diferit de 0 dac procesul interogat s-a terminat prin exit WEXITSTATUS(status) dac WIFEXITED(status) e diferit de 0, arat codul de terminare a procesului ; evalueaza doar cei 8 biti cel mai putin semnificativi din status WIFSIGNALED(status) ntoarce o valoare diferit de 0 dac procesul interogat s-a terminat datorit unui semnal netratat WTERMSIG(status) dac WIFSIGNALED(status) e diferit de 0, arat numrul semnalului datorit cruia procesul s-a terminat WCOREDUMP(status) ntoarce o valoare diferit de 0 dac procesul interogat a generat un core WIFSTOPPED(status) ntoarce o valoare diferit de 0 dac procesul interogat este oprit WSTOPSIG(status) dac WIFSTOPPED(status) e diferit de 0, ntoarce numrul semnalului care a oprit procesul

    Observaie Aceste macrodefiniii primesc ca argument buffer-ul n care se afl informaia de stare (adic un int), i nu un pointer ctre acest buffer.

    pid_t wait(int *status);

    Varianta simplificat care ateapt orice proces copil s se termine. Este echivalent cu:

    waitpid(-1, &status, 0);

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

    Terminarea unui proces

    Pentru terminarea procesului curent, Linux pune la dispoziie apelul de sistem exit. Dintr-un program C exist trei moduri de invocare a acestui apel de sistem:

    apelul _exit (POSIX.1-2001): #include void _exit (int status);

    apelul _Exit din biblioteca standard C (conform C99): #include void _Exit (int status);

    apelul exit din biblioteca standard C (conform C89, C99): #include void exit (int status);

    _exit(2) i _Exit(2) sunt funcional echivalente (doar c sunt definite de standarde diferite):

    procesul apelant se va termina imediat. toi descriptorii de fiier ai procesului sunt nchii, copiii procesului sunt "nfiai" de init, iar

    printelui procesului i va fi trimis un semnal SIGCHLD. Procesului printe i va fi ntoars valoarea status ca rezultat al unei funcii de ateptare (wait sau waitpid).

    n plus exit(3):

  • va terge toate fiierele create cu tmpfile() va scrie bufferele streamurilor deschise i le va nchide.

    Not: Conform ISO C, un program care se termin cu un return x; din main() va avea acelai comportament ca i unul care apeleaz exit(x).

    Pentru terminarea unui alt proces din sistem, se va trimite un semnal ctre procesul respectiv prin intermediul apelului de sistem kill. Mai multe detalii despre kill i semnale n laboratorul de semnale.

    my_system

    Un exemplu de folosire a primitivelor exec, fork, exit i wait (sau de rularea a unui program) l reprezint chiar reimplementarea apelului de bibliotec system

    Exemplu 4. my_system.c

    #include #include #include #include int my_system(const char *command) { int pid, status; switch ((pid=fork())) { case -1: //error forking. return -1; case 0: { const char *argv[] = {"/bin/bash", "-c", command, NULL}; execv("/bin/bash", (char *const *)argv); /* exec se poate ntoarce doar cu cod de eroare (de ex. cnd nu se gsete fiierul de executat - n cazul nostru /bin/bash. n caz de eroare, terminm procesul copil */ exit(-1); } } //doar procesul printe ajunge aici, i doar dac fork() s-a terminat cu succes waitpid(pid, &status, 0); return status; } int main() { my_system("ls"); return 0; }

    Copierea descriptorilor de fiier

    int dup(int oldfd);

    Duplic descriptorul de fiier oldfd i ntoarce noul descriptor de fiier, sau -1 n caz de eroare.

  • int dup2(int oldfd, int newfd);

    Duplic descriptorul de fiier oldfd n descriptorul de fiier newfd; dac newfd exist, mai nti va fi nchis fiierul asociat. ntoarce noul descriptor de fiier, sau -1 n caz de eroare.

    Descriptorii de fiier sunt, de fapt, indeci n tabela de fiiere deschise. Tabela este populat cu pointeri ctre o structur cu informaiile despre fiiere. Duplicarea unui descriptor de fiier nseamn duplicarea intrrii din tabela de fiiere deschise (adic 2 pointeri de la poziii diferite din tabel vor indica spre aceeai structur din sistem asociat fiierului). Din acest motiv, toate informaiile asociate unui fiier (lock-uri, cursor, flaguri) sunt partajate de cei doi file descriptori. Ceea ce nseamn c operaiile ce modific aceste informaii pe unul din file descriptori (de ex. lseek) sunt vizibile i pentru cellat file descriptor (duplicat). Atentie! Exist i o excepie: flagul CLOSE_ON_EXEC nu este partajat (acest flag nu este inut n structura menionat mai sus).

    Pentru a putea folosi dup i dup2 trebuie inclus header-ul unistd.h.

    Motenirea descriptorilor de fiier dup operaii fork/exec

    Descriptorii de fiier ai procesului printe se motenesc n procesul copil n urma apelului fork. Dup un apel exec descriptorii de fiier sunt pstrai de asemenea, mai puin aceia dintre ei care au setat flagul CLOSE_ON_EXEC.

    Pentru a seta flagul CLOSE_ON_EXEC se folosete funcia fcntl cu un apel de genul:

    fcntl(file_descriptor, F_SETFD, FD_CLOEXEC);

    Pentru a putea folosi funcia fcntl trebuie incluse header-ele unistd.h i fcntl.h.

    Trei dintre descriptorii de fiier sunt mai importani:

    STDIN_FILENO toate programele obinuite conin 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 STDOUT_FILENO toate programele obinuite au acest file descriptor care are valoarea 1; el reprezint ieirea standard; toate apelurile de genul printf vor genera output la terminal STDERR_FILENO toate programele obinuite au acest file descriptor care are valoarea 2; el reprezint ieirea standard de eroare

    Variabile de mediu

    char **environ;

    Un vector de pointeri la iruri de caractere, ce conin variabilele de mediu i valorile lor. Vectorul e terminat cu NULL. irurile de caractere sunt de forma "VARIABILA=VALOARE".

    char* getenv(const char *name);

    ntoarce valoarea variabilei de mediu denumite name, sau NULL dac nu exist o variabil de mediu denumit astfel.

    int setenv(const char *name, const char *value, int replace);

    Adaug n mediu variabila cu numele name (dac nu exist deja) i i seteaz valoarea la value. Dac variabila exist i replace e 0, aciunea de setare a valorii variabilei e ignorat; dac replace e diferit de 0, valoarea variabilei devine value.

  • int unsetenv(const char *name);

    terge variabila denumit name din mediu.

    Depanarea unui proces

    Pe majoritatea sistemelor de operare pe care a fost portat, gdb nu poate detecta cnd un proces realizeaz o operaie fork(). Atunci cnd programul este pornit, depanarea are loc exclusiv n procesul iniial, procesele copii nefiind ataate debugger-ului. n acest caz, singura soluie este introducerea unor ntrzieri n executia procesului nou creat (de exemplu, prin apelul de sistem sleep()), care s ofere programatorului suficient timp pentru a ataa manual gdb-ul la respectivul proces, presupunnd c i-a aflat PID-ul n prealabil.

    Pentru a ataa debugger-ul la un proces deja existent, se folosete comanda attach, n felul urmtor:

    (gdb) attach PID

    Aceast metod este destul de incomod i poate cauza chiar o funcionare anormal a aplicaiei depanate, n cazul n care necesitile de sincronizare ntre procese sunt stricte (de exemplu operaii cu time-out).

    Din fericire, pe un numr limitat de sisteme, printre care i Linux, gdb permite depanarea comod a programelor care creeaz mai multe procese prin fork() i vfork(). Pentru ca gdb s urmreasc activitatea proceselor create ulterior, se poate folosi comanda set follow-fork-mode, n felul urmtor:

    (gdb) set follow-fork-mode mode

    unde mode poate lua valoarea parent, caz n care debugger-ul continu depanarea procesului printe, sau valoarea child, i atunci noul proces creat va fi depanat n continuare. Se poate observa c n aceast manier debugger-ul este ataat la un moment dat doar la un singur proces, neputnd urmri mai multe simultan.

    Cu toate acestea, gdb poate ine evidena tuturor proceselor create de ctre programul depanat, dei n continuare numai un singur proces poate fi rulat prin debugger la un moment dat. Comanda set detach-on-fork realizeaz acest lucru:

    (gdb) set detach-on-fork mode

    unde mode poate fi on, atunci cnd gdb se va ataa unui singur proces la un moment dat (comportament implicit), sau off, caz n care gdb se ataeaz la toate procesele create n timpul execuiei, i le suspend pe acelea care nu sunt urmrite, n funcie de valoarea setrii follow-fork-mode.

    Comanda info forks afieaz informaii legate de toate procesele aflate sub controlul gdb la un moment dat:

    (gdb) info forks

    De asemenea, comanda fork poate fi utilizat pentru a seta unul din procesele din list drept cel activ (care este urmrit de debugger).

    (gdb) fork fork-id

    unde fork-id este identificatorul asociat procesului, aa cum apare n lista afiat de comanda info forks.

  • Atunci cnd un anumit proces nu mai trebuie urmrit, el poate fi nlaturat din list folosind comenzile detach fork i delete fork:

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

    Diferena dintre cele dou comenzi este c detach fork las procesul s ruleze independent, n continuare, n timp ce delete fork l ncheie.

    Pentru a ilustra aceste comenzi ntr-un exemplu concret, s considerm programul urmtor:

    Exemplu 5. forktest.c

    _ 1 #include 2 #include 3 #include 4 #include 5 6 7 int main(int argc, char **argv) { 8 pid_t childPID = fork(); 9 10 if (childPID < 0) { 11 // An error occured 12 fprintf(stderr, "Could not fork!\n"); 13 return -1; 14 } else if (childPID == 0) { 15 16 // We are in the child process 17 printf("The child process is executing...\n"); 18 sleep(2); 19 20 } else { 21 22 // We are in the parent process 23 if (wait(NULL) < 0) { 24 fprintf(stderr, "Could not wait for child!\n"); 25 return -1; 26 } 27 printf("Everything is done!\n"); 28 29 } 30 31 return 0; 32 } -

    Dac vom rula programul cu parametrii implicii de depanare, vom constata c gdb va urmri exclusiv execuia procesului printe:

    $ 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 cte un breakpoint n codul asociat procesului printe, respectiv procesului copil, pentru a evidenia 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.

    Setm debugger-ul s urmreasc procesele copii, i observm c de data aceasta cellalt 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!

    Observai c ultimele dou mesaje au fost inversate, fa de cazul precedent: debugger-ul ncheie procesul copil, apoi procesul printe afieaz mesajul de final (Everything is done!).

    Exerciii de laborator

    Linux

    1. (0.5 puncte) Realizai un program care s execute o comand transmis ca parametru folosind funcia de bibliotec system.

    2. (1 punct) Realizai un program care execut linia de comand cu fork/exec. Primul argument al programului va fi numele unui program de lansat, urmtoarele fiind pasate mai departe ca argumente programului respectiv.

    3. (1 punct) Realizai un program care s verifice c n urma unui fork spaiile de adres din procesele printe i copil sunt identice, dar continutul memoriei poate s difere.

    4. (1 punct) Realizai un program care s verifice c: o file descriptorii se motenesc la fork o file descriptorii care au setat flagul CLOSE_ON_EXEC sunt nchii la un apel exec.

    Indiciu: stdout. 5. (2 puncte) Scriei un program care execut dou (sau mai multe) comenzi separate de

    ";". Exemplu: ./program "ls;pwd" execut ls, atept ca acesta s returneze i apoi execut pwd. Se consider apeluri ale programelor fr argumente.

    o Indiciu: pentru a parsa irul de comenzi separate prin ';', putei folosi strchr sau strtok.

    6. (1.5 puncte) Modificai programul de la exerciiul 2 astfel nct s execute comenzi cu ieirea redirectat

    ntr-un fiier. De exemplu secvena: ./program "ls" ar trebui s pun toat ieirea comenzii ls ntr-un fiier. Indiciu: imediat dupa fork dar nainte de exec, obinei un file descriptor la

  • fiier (newfd) i apoi facei un dup2. De asemenea, n general cnd se folosete o secven fork/dup/exec (n procesul copil), procesul printe trebuie s nchid file descriptorul pe care s-a fcut dup.