Introducere. Identificatorul de proces. u7y8yb
Controlul proceselor in Unix include :
-crearea proceselor;
-invocarea programelor;
-punerea in asteptare a unui proces in vederea terminarii unui proces fiu;
-terminarea proceselor;
Se reaminteste faptul ca fiecare proces are un identificator de proces unic, un numar intreg pozitiv. Acesta este denumit pe scurt pid (Process
IDentifier).
Intr-un sistem Unix exista citeva procese speciale :
-Procesul cu pid 0 este planificatorul de procese, numit swapper. Acest proces face parte din nucleu si este considerat un proces sistem.
-Procesul cu pid 1 este procesul init, invocat de nucleu la sfirsitul procedurii de incarcare a sistemului. Fisierul program pentru acest proces era /etc/init in versiunile vechi si /sbin/init in versiunile noi. Procesul init citeste fisierele de initializare dependente de sistem (fisierele de resurse /etc/rc*) si aduce sistemul intr-o stare stabila (de exemplu multiuser). Procesul init este un proces utilizator (spre deosebire de swapper), dar se executa cu drepturi de superuser. Procesul init nu dispare niciodata.
-In anumite implementari de Unix cu memorie virtuala, procesul cu pid 2 este
pagedaemon. Acest proces este un proces sistem si realizeaza paginarea in sisteme cu memorie virtuala.
Crearea unui proces
Crearea unui proces se realizeaza prin apelul functiei de sistem fork.
Procesul care apeleaza functia fork se numeste proces parinte, iar procesul
nou creat se numeste proces fiu.
Interfata functiei fork este urmatoarea :
#include <sys/types.h>
#include <unistd.h>
pid_t fork (void);
(returneaza 0 in procesul fiu, pid fiu in procesul parinte;(-1)in caz de eroare)
In versiunile mai vechi tipul functiei fork era int. Functia fork returneaza:
-pid fiu in procesul parinte si 0 in procesul fiu, in caz de succes;
-(-1) in caz de eroare, iar errno indica eroarea aparuta. Cazul de eroare poate fi cauzat de: a.exista deja prea multe procese in sistem; b.numarul total de procese pentru acel ID utilizator depaseste limita stabilita de sistem.
Un proces poate crea mai multi fii. Deoarece nu exista nici o functie care sa permita a determina pid-ul proceselor fiu, functia fork returneaza procesului parinte pid-ul procesului fiu. Functia fork returneaza 0 procesului
fiu deoarece un proces poate avea doar un singur proces parinte, care se poate afla prin apelul functiei getppid() (pid 0 este folosit de procesul swapper, deci nu poate fi pid-ul unui proces fiu!).
Procesul parinte si procesul fiu devin doua procese care se executa in mod concurent, incepind cu prima instructiune dupa apelul fork. Exemplul cel mai simplu este :
Ex 1 :
/*
A se compila cu: cc e1.c -oe1
*/
#include <stdio.h>
#include <sys/types.h>
int main (void)
A pid_t pid; pid=fork(); printf("Test de apel fork : %d\n",pid);
S
Rezultatul executiei acestui program este :
$e1
Test de apel fork : 226
Test de apel fork : 0
Procesul fiu este o copie a procesului parinte. Cu toate acestea cele doua procese nu partajeaza memoria aferenta zonelor de date, heap si stiva
(eventual, zona text). Multe din implementarile actuale nu realizeaza o copie
completa a zonelor de date heap si stiva din spatiul procesului parinte in spatiul procesului fiu, deoarece deseori apelul fork este urmat de un apel exec. Se foloseste o tehnica numita copy-on-write (COW). Aceste regiuni sint partajate de cele doua procese si sint protejate de nucleu. Aceste regiuni pot fi numai citite. La terminare, procesul fiu transmite procesului parinte semnalul SIGCLD. Procesul parinte poate sa astepte terminarea procesului fiu apelind functia de sistem wait sau waitpid si sa analizeze modul cum acesta
s-a terminat. Aceste functii vor fi prezentate ulterior.
Algoritmul functiei fork este urmatorul :
algoritm fork; input: - output: pid fiu in procesul parinte;
0 in procesul fiu;
(-1) in caz de eroare;
A
Control resurse disponibile;
Obtinerea in controlul proceselor a unei intrari libere si acordarea fiu a unui identificator (pid);
Control daca nu sint prea multe procese;
Copierea informatiilor din intrarea corespunzatoare parintelui in cea a fiului in tabela proceselor;
Incrementarea cu 1 in tabela inode-urilor a numarului de referiri pentru directorul curent si radacina schimbata (daca este cazul);
Incrementareea cu 1 in tabela fisierelor si a inode-urilor a numarului de referiri pentru fisierele deschise de procesul parinte;
Duplicarea in memorie a zonei u, segmentelor date, heap si stiva corespunzatoare procesului parinte si partajarea segmentului text;
Crearea stivei de sistem pentru procesul fiu, pentru a putea fi executat in continuare;
/* executia poate fi pentru procesul parinte sau fiu */ if(se executa procesul parinte)
A
Starea procesului fiu:=ready; return(pid proces fiu ) in procesul parinte;
S else/* se executa proces fiu */
A
Initializeaza cimpurile timp in zona u a procesului fiu; return(0) in procesul fiu;
S
S
Din descrierea algoritmului rezulta ca la crearea unui proces fiu au loc urmatoarele actiuni importante: a) Nucleul verifica daca exista resurse de memorie interna si externa suficiente pentru crearea procesului fiu (memorie pentru segmentele de date, heap, stiva
zona u, tabele de pagini etc.). In caz contrar functia fork returneaza eroare. b) Prin parcurgerea tabelei proceselor, incepind cu urmatoarea intare dupa ultima
acordata, se gaseste o intrare libera care este asignata procesului fiu.
Astfel se obtine si pid-ul procesului fiu. Ultima intrare din tabela proceselor nu poate fi asignata decit pentru un proces al superuserului. c) Se verifica numarul proceselor fiu ale parintelui. Daca acest numar nu este mai mare decit o valoare impusa procesul fiu este marcat ca fiind creat. Altfel functia de sistem fork reurneaza eroare. d) Se completeaza intrarea fiului in tabela proceselor cu anumite informatii luate din cea a parintelui: ID-ul utilizatorului real al uti lizatorului parinte si ID-ul utilizatorului efectiv, ID-ul utilizatorului efectiv, ID-ul grupului de procese si valoarea nice a parintelui (folosita pentru calculul prioritatii). Nucleul completeaza apoi intrarea procesului fiu cu alte informatii: ID-ul procesului parinte, prioritatea intiala, timpul de utilizare CPU etc. e) Nucleul incrementeaza cu 1 numarul de referiri din tabela i-node-urilor pentru directorul curent (care este cel al parintelui) si radacina, daca a fost schimbata cu functia chroot. f) Procesul fiu mosteneste fisierele deschise de procesul parinte pina in momentul apelului functiei fork. Ca urmare, vor fi incrementate cu 1 nr. de referiri din tabela fisierelor si a i-node-urilor pentru fisierele deschise de parinte.
Procesul parinte si procesul fiu partajeaza fisiewreele deschise de parinte in momentului apelului fork, intrucit intrarea in tabela fisierelor este comuna si folosesc acelasi deplasament. g) Nucleul creaza contextul procesului fiu prin duplicarea in memorie a zonei u, a segmentului de date si stiva utilizator si partajarea segmentului text ce apartine procesului parinte h) Nucleul creaza stiva nucleu pentru procesul fiu, completind numaratorul de instructiuni si celelalte registre salvate pe stiva, astfel ca procesul fiu sa poata fi reluat din acest punct. Astfel procesul fiu ajunge in starea
"ready " si conform planificarii va obtine procesorul. i) In procesul fiu functia fork returneaza 0, iar in procesul parinte returneaza
identificatorul de proces al fiului.
Din reprezentarea algoritmului rezulta ca procesul parinte si procesul fiu sunt diferite, avind identificatori diferiti, dar au multe atribute comune
(a se vedea mai jos).
Prin testarea valorii returnate de fork, se poate partitiona codul programului
in cele doua procese, fiecare proces executind codul corespunzator lui:
switch(fork())
A case -1: perror("fork"); exit(1); case 0: /* proces fiu */
default: /* proces parinte */
-
S
Programul urmator prezinta modul de utilizare a functiei fork:
Ex 2 :
/*
A se compila cu : cc e2.c err.o -oe2
*/
#include <sys/types.h> int gvar = 4; /* variabila externa */ int main(void)
A pid_t pid; int var = 7; /* variabila auto pe stiva */ printf("Inainte de fork\n"); if((pid=fork()) == -1) err_sys("Eroare fork"); else if (pid==0)A /* fiu */ gvar++; var+=2;
Selse sleep(2); printf("Proces (pid)=%d, gvar=%d var=%d\n", getpid(), gvar, var); exit(0);
S
In functie de algoritmul de planificare folosit de nucleu, unul dintre procese se va executa primul. In exemplul prezentat s-a intirziat cu 2 secunde procesul parinte pentru pentru a permite executia fiului prima data. Nu exista nici o garantie ca aceasta intirziere este suficienta.
Doua executii ale programului au afisat liniile:
$e2
Inainte de fork
Proces (pid)=184, gvar=5, var=9
Proces (pid)=183, gvar=4, var=7
$e2
Inainte de fork
Proces (pid)=188, gvar=5, var=9
Proces (pid)=187, gvar=4, var=7
Diferenta intre rezultate este cauzata de interactiunea dintre functia fork si functiile de I/E. Functiile standard de I/E lucreaza cu tampon.
Iesirea standard se face prin tampon linie daca ea este conectata la un terminal, altfel printr-un singur tampon. La executia simpla a programului acesta afiseaza o singura data argumentul functiei printf, deoarece tamponul de iesire este golit de caracterul '\n'. Daca insa iesirea este redirectata
in fisierul e2.buf argumentul functiei printf ramine in tampon, fiecare proces
adaugind linia proprie tamponului propriu. La terminarea proceselor continutul copiilor tamponului este golit.
Rezultatul valideaza si faptul ca redirectarea iesirii procesului parinte este mostenita de procesul fiu. Toti descriptorii deschisi in procesul parinte sint duplicati in procesul fiu. Cele doua procese partajeaza aceleasi intrari in tabela proceselor. Procesele partajeaza acelasi deplasament (cimpul offset) in fisier. Daca, spre exemplu, ambele procese scriu in iesirea standard, deplasamentul este mereu actualizat de procesul care realizeaza scrierea.
Gestiunea descriptorilor de fisier dupa un apel fork se poate face in doua moduri:
a: Procesul parinte asteapta terminarea procesului fiu. In acest caz parintele nu trebuie sa faca nimic cu descriptorii. Cind fiul se termina, toti descriptorii fisierelor de la care el a citit sau in care el a scris au deplasamentul actualizat.
b: Fiecare proces continua executia independent de celalalt. Se impune in acest caz ca fiecare proces sa inchida descriptorii nefolositi.
Pe linga fisierele deschise, procesul fiu mai mosteneste de la parinte:
- ID-ul utiliuzatorului real, ID-ul grupului real, ID-ul utilizatorului efectiv si ID-ul grupului efectiv;
- ID-i suplimentari de grup;
- ID-ul de sesiune;
- terminal de control;
- valorile fanioanelor ID utilizator setat si ID grup setat;
- directorul curent;
- directorul radacina;
- masca de semnale si actiunile atasate semnalelor;
- masca de creare fisiere (prin functia umask);
- mediul;
- indicatorul close-on-exec pentru descriptorii de fisier deschisi;
- segmente de memorie partajate;
- limite de resurse.
Diferentele intre parinte si fiu se reduc la:
* valoarea returnata de fork;
* pid si pid-ul proceselor parinte;
* pentru fiu valorile cimpurilor tms_utime, tms_stime, tms_ustime sint puse pe 0;
* alarmele nerezolvate sint sterse pentru fiu;
* zavoarele pe fisiere puse de parinte nu sint mostenite de fiu;
* setul de semnale nerezolvate pentru fiu este sters;
Utilizarea functiei fork:
1. Cind procesul se duplica pentru ca procesele parinte si fiu sa execute parti de cod distincte.
Acesta situatie apare la server-ele de retea - procesul parinte asteapta o cerere de la un client. La sosirea cererii, procesul parinte executa fork si lasa in seama procesului fiu rezolvarea ei. Parintele revine in asteptarea unei noi cereri.
2. Cind un proces executa un alt program.
Situatia apare la shell, cind procesul fiu apeleaza functia exec.
In acest ultim context, versiunile SVR4 si 4.3+BSD permit apelul functiei vfork. Aceasta functie, deoarece nu copiaza complet spatiul de adrese al procesului parinte (nu e nevoie ca executa exec sau exit), optimizeaza implementarea. O alta diferenta intre aceste doua functii este aceea ca vfork garanteaza ca procesul fiu se executa primul. Daca in exemplu e2.c se inlocuieste fork cu vfork, exit (din fiu) cu _exit si se elimina apelul sleep rezultatul este:
$e2
Inainte de fork
Proces (pid)=188, gvar=5 var=9
Apelul _exit nu goleste tampoanele de I/E.
Terminarea unui proces (functia de sistem exit)
Terminarea normala a unui proces de catre el insusi se realizeaza prin apelul functiei de sistem exit sau _exit. Prima este definita de ANSI C.
Deoarece standardul nu trateaza descriptorii de fisier, procesele multiple si controlul programelor, definitia aceste functii este incompleta pentru Unix.
Functia _exit, apelata de exit si definita de POSIX, trateaza detaliile specifice Unix-ului.
Interfata celor doua functii de sistem este:
void exit(int cod_exit); void _exit(int cod_exit);
unde:
cod_exit - Este o valoare intreaga ce se returneazza parintelui in vederea analizei.
Un proces se poate termina anormal prin apelul functiei abort sau la primirea unui semnal.
In urma executiei functiei de sistem exit, procesul respectiv intra in starea terminat.
Daca apelul functiei de sistem exit lipseste din program, apelul este totusi executat implicit cind se revine din functia main.
Functia _exit este apelata intern de catre nucleu pentru terminarea unui proces cind receptioneaza semnale ce nu sint tratate. In acest caz, _exit returneaza un cuvint de stare ce include si numarul de identificare a semnalului
Algoritmul functiei exit este urmatorul:
algoritm exit; input codul de retur pentru parintele procesului; output A
Ignora toate semnalele; if(procesul este lider al grupului de procese)
A
Trimite semnalul HANGUP la toate procesele din grup;
Seteaza numarul de grup pentru toate procesele din grup la valoarea 0;
S
Inchide toate fisierele deschise (apel close);
Elibereaza directorul curent (apel input);
Elibereaza radacina schimbata, daca exista (apel input);
Elibereaza regiunile asociate procesului (apel freereg);
Scrie inregistrarea curenta pe disc;
Stare proces:=terminat("zombie");
Identificatorul procesului parinte pentru toti fiii procesului apelant al functiei exit:=1; if(exista procese fiu in starea "zombie")
Trimite semnalul SIGCLD procesului 1;
Trimite SIGCLD parintelui procesului al functiei exit;
Switch context;
S
Din scrierea algoritmului rezulta ca la executia de catre un proces a functiei de sistem exit, au loc urmatoarele actiuni importante:
a: Ignorarea tuturor semnalelor care se transmit procesului;
b: Daca procesul este lider al grupului, nucleul trimite tuturor proceselor
din grup semnalul HANGUP si seteaza numarul lor de grup la valoarea 0.
Ultima actiune se face intrucit daca alt proces ar obtine identificatorul de proces al celui care a executat exit, atunci acesta ar deveni liderul de grup al vechiului grup, desi noul proces nu ar avea nimic de a face cu vechiul grup. Se reaminteste faptul ca un proces care are identificatorul egal cu cel al grupului sau de proces este socotit lider al grupului.
c: Inchiderea tuturor fiserelor deschise.
d: Eliberarea directorului curent, a radacinii schimbate prin chroot, a regiunilor asociate procesului (stiva, date, text).
e: Starea procesului apelant devine "terminat"("zombie"),
ocupind numai intrarea din tabela proceselor.
f: Tuturor proceselor fiu ale procesului care a executat exit li se schimba parintele, acesta devenind procesul init(1).
g: Trimiterea semnalului SIGCLD procesului parinte, pentru ca acesta sa poata analiza felul cum fiul s-a terminat (mai precis, parintele primeste codul de retur, daca procesul fiu se termina prin exit).
h: In final procesorul va fi atribuit unui alt proces.
Se observa ca sint esentiale trei cazuri:
* procesul parinte se termina inaintea procesului fiu;
* procesul fiu se termina inaintea procesului parinte;
* procesul fiu, mostenit de procesul init, se termina;
Procesul init devine parintele oricarui proces pentru care procesul parinte s-a terminat. Cind un proces se termina, nucleul parcurge toate procesele active pentru a vedea daca printre ele exista un proces care are ca parinte procesul terminat. Daca exista un astfel de proces, pid-ul procesului parinte devine 1 (pid-ul lui init). Nucleul garanteaza asfel ca fiecare proces
are un parinte.
Daca procesul fiu se termina inaintea procesului parinte, nucleul trebuie sa pastreze anumite informatii (pid, stare de terminare, timp de utilizare CPU) asupra modului in care fiul s-a terminat. Aceste informatii sint accesibile parintelui prin apelul wait sau waitpid. In terminologie Unix un proces care s-a terminat si pentru care procesul parinte nu a executat wait se numeste "zombie". In aceasta stare, procesul nu are nici un fel
de resurse alocate, ci doar intrarea sa in tabela proceselor. Nucleul poate descarca toata memoria folosita de proces si inchide fisierele deschise. Un proces
"zombie" se poate observa prin comanda Unix, ps, care afiseaza la
starea procesului litera 'Z'.
Ex 3 :
/* a se compila cu: cc e3.c err.o -oe3
*/
#include "hdr.h"
int main(void)
A pid_t pid;
if((pid=fork()) == -1) err_sys("Eroare fork 1"); else if(pid == 0) /* proces fiu */ exit(0);
/* procesul parinte */ sleep(3); system("ps"); exit(0);
S
Rezultatul afisat de acest program;
PID TTY STAT TIME COMMAND
54 v01 S 0:00 -bash
90 v01 S 0:00 e3
91 v01 Z 0:00 (e3)<zombie>
92 v01 R 0:00 ps
Daca un sistem care are ca parinte procesul init se termina, acesta nu devine "zombie", deoarece procesul init apeleaza una dintre functiile
wait pentru a analiza starea in care procesul a fost terminat. Prin aceasta comportare procesul init evita incarcarea sistemului cu procese "zombie".
Asteptarea unui proces (functia de sistem wait, waitpid)
Cind un proces se termina, normal sau anormal, procesul parinte este atentionat de nucleu prin transmiterea semnalului SIGCLD. Actiunea implicita
atasata acestui semnal este ignorarea sa. Un proces ce apeleaza wait sau
waitpid poate:
* sa se blocheze (daca toti fiii sai sint in executie);
* sa primeasca starea de terminare a fiului (daca unul dintre fii s-a terminat);
* sa primeasca eroare (daca nu are procese fiu).
Interfata celor doua functii wait si waitpid este urmatoarea:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int opt);
(ambele intorc pid, 0 (vezi waitpid) sau -1 in caz de eroare)
unde:
status - este un pointer spre locatia din spatiul de adrese al procesului unde sistemul depune informatia de stare la terminarea unui proces fiu.
Analizind informatia de stare redata pe 16 biti, se poate sti si cum s-a terminat procesul fiu. In acest sens exista urmatoarele conventii:
* daca procesul fiu s-a terminat prin exit, continutul informatiei de stare este urmatorul:
15 8 7 0
ÚAAAAAAAAAAAAAAAAAAAAAAAAAAAAA¿
³ cod_exit ³ 0 0 ... 0 ³
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAÙ
unde:
cod_exit - Este valoarea de apel a functiei exit.
* daca procesul fiu s-a terminat prin receptia unui semnal,continutul informatiei de stare este urmatorul:
unde: x - Este 1 daca semnalul a produs vidaj de memorie sau 0 in caz contrar. nr_demnal - Este numarul de identificare al semnalului care a cauzat terminarea procesului fiu.
* daca procesul fiu este stopat, continutul informatiei de stare este urmatorul:
15 8 7 6 0
ÚAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA¿
³ 0 0 ... 0 ³x³ nr_semnal ³
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAÙ unde: nr-semnal - Este identificatorul semnalului care a oprit procesul.
Diferentele intre cele doua functii constau in:
a. wait blocheaza procesul apelant pana la terminarea unui fiu, in timp ce waitpid are o optiune, precizata prin argumentul opt, care evita acest lucru.
b. waitpid nu asteapta terminarea primului fiu, ci poate specifica prin argumentul opt procesul fiu asteptat. c. waitpid permite controlul programelor prin argumentul opt.
Valoarea returnata de functia wait este numarul de identificare al procesului fiu care s-a terminat. Intotdeauna se poate preciza care fiu s-a terminat deorece functia ii returneaza pid-ul. Asteptarea unui anumit fiu se
poate realiza astfel:
while (wait (stare)!=pid); unde: pid - Este identificatorul procesului fiu asteptat sa se termine
(obtinut prin fork). Nu se poate insa preciza asteptarea terminarii unui anumit fiu. A se vedea mai jos waitpid.
In caz de eroare, wait returneaza (-1), iar variabila errno indica eroarea aparuta.
Algoritmul functiei wait este urmatorul: algoritm wait; input: pointer spre locatia unde se depune informatia de stare a procesului fiu; output: pid fiu si informatia sa de stare;
(-1) in caz de eroare;
A if(procesul in wait nu are procese fiu) return(eroare); forever
A if(procesul in wait are fii care s-au terminat-"zombie")
A
Selecteaza arbitrar un fiu in starea terminat;
Contabilizeaza timpul de utilizare CPU;
Elibereaza intrarea procesului fiu din tabela proceselor; return(pid fiu si stare sa);
S if(procesul in wait nu are procese fiu) return(eroare); sleep la o prioritate intreruptibila pana ce un proces fiu se termina;
S
S
Din descrierea algoritmului, rezulta ca la punerea unui proces in asteptarea terminarii unui fiu al sau, au loc urmatoarele actiuni importante:
a. Daca in momentul apelului functiei wait, procesul nu are procese fiu, functia wait returneaza eroare, asteptarea neavand sens.
b. Daca procesul are fii in starea terminat, atunci selecteaza unul din ei, ii contabilizeaza timpul CPU, ii elibereaza intrarea in tabela proceselor si apoi returneaza procesului apelant identificatorul procesului fiu selectat si codul de retur exit al acestuia.
c. Daca procesul are fii, dar nu in starea terminat ("zombie"), el
va fi trecut in starea wait cu o prioritate intreruptibila prin executarea functiei sleep. In momentul in care un fiu se termina, procesul parinte primeste semnalul SIGCLD. Fiind in stare wait si asteptind acest eveniment, bucla for va fi reluata cu primul if, gasindu-se de data acesta un proces fiu terminat.
Observatie:
In bucla for a fost necesara reluarea verificarii daca procesul apelant al functiei wait are fii, lucru care s-ar parea la prima vedere inutil, intrucat el a intrat in bucla numai daca avea fii. Verificarea a fost necesara, intrucat semnalul SIGCLD la terminarea unui fiu putea fi ignorat prin functia signal (SIGCLD, SIGIGN). Ignorarea semnalului SIGCLD nu duce la scoaterea din starea wait a procesului parinte.
Functia waitpid exista in SRV4 si 4.3+BSD. Argumentul, opt poate avea valorile:
WNOHANG - apelul nu se blocheaza daca fiul specificaqt prin pid nu este disponibil. In acest caz valoarea de retur este 0
WUNTRACED - daca implementarea permite controlul lucrarilor, starea fiecarui proces fiu oprit si neraportata este intoarsa.
Argumentul opt poate fi si 0 sau rezultatul unui SAU logic intre constantele simbolice WNOHANG si WUNTRACED.
In functie de argumentul pid, interpretarea functiei waitpid este:
- pid==-1
Se asteapta orice proces fiu (echivalent wait).
- pid>0
Se asteapta procesul pid.
- pid==0
Se asteapta orice proces cu ID-ul de grup de proces egal cu cel al apelantului.
- pid<-1
Se asteapta orice proces cu ID-ul de grup de proces egal cu valoarea absoluta a pid.
Apelul waitpid returneaza (-1) daca nu exista proces sau grup de procese cu pid-ul specificat sau pid-ul respectiv nu este al unui fiu de-al sau.
Conform POSIX.1, pentru a analiza starea in care s-a terminat un proces fiu exista trei macrouri excluse mutual, toate prefixe de WIF si definite in fisierul antet sys/wait.h. Pe langa aceasta, exista alte macrouri pentru determinarea codului de exit, numarului semnalului, etc.
Un exemplu util de folosire a acestor macrouri este functia print_exit,care permite afisarea informatiilor de stare.
Ex 4 :
/*
A se compila cu: cc e4.c -oe4
*/
#include <sys/types.h>
#include <sys/wait.h> extern char *sys_siglistai; void print_exit( int status)
A if ( WIFEXITED( status)) printf("Terminare normala, starea de\ exit=%d\n", WEXITSTATUS( status) ); else if ( WIFSIGNALED( status )) printf("Terminare Anormala, nimar\ semnal=%d=%s%s\n",WTERMSIG( status), sys_siglista WTERMSIG( status)i,
#ifdef WCOREDUMP
WCOREDUMP( status) ?
"( generat fisierul core":"");
#else
"");
#endif else if ( WIFSTOPPED( status)) printf("Proces fiu oprit, numar_semnal=%d%s\n,WSTOPSIG (status), sys_siglista WSTOPSIG (status)i );
S
Functia print_exit foloseste macroul WCOREDUMP daca acesta este definit. Folosirea variabilei sys_siglist, in versiunile noi, permite maparea numarului semnalului la denumirea sa. Daca aceasta variabila nu exista, trebuie inspectat fisierul antet signal.h pentru a afla denumirea semnalului.
Un program test pentru aceasta functie ar putea fi urmatorul:
Ex 5 :
/*
A se compila cu: cc e5.c err.o pr.o -oe5
*/
#include <sys/types.h>
#include <sys/wait.h> int main( void)
A pid_t pid; int status;
if((pid==fork())==-1) err_sys("Eroare fork"); else if(pid==0) exit(7); if(wait(&status) !=pid) err_sys("Eroare wait"); print_exit( status);
if((pid=fork())==-1) err_sys("Eroare fork"); else if ( pid==0) status /=0; /*se genereaza SIGFPE */ if( wait( &status) != pid) err_sys("Eroare wait"); print_exit( status);
if (( pid=fork())==-1) err_sys("Eroare fork"); else if (pid==0) abort(); /* se genereaza SIGABRT */ if (wait( &status)!=pid) err_sys("Eroare wait"); print_exit(status);
exit(0);
S
Programul creeaza pe rand trei procese fiu care sunt terminate diferit, procesul parinte afisind informatiile de stare. Rezultatul executiei este:
$e5
Terminare normala,starea de exit=7
Terminare anormala, numar semnal=8=Floating point exception
Terminare anormala, numar semnal=6=IOT trap/Abort
Uneori se doreste ca procesul parinte sa nu astepte terminarea unui proces fiu creat prin functia fork. In aceste cazuri, pentru ca procesul fiu
sa nu devina "zombie", este necesar ca apelul fork sa fie dublat.
Programul urmator ilustreaza acest lucru.
Ex 6 :
/*
A se compila cu: cc e6.c err.o pr.o -oe6
*/
#include <sys/types.h>
#include <sys/wait.h> int main( void)
A pid_t pid; if ((pid=fork()) ==-1) err_sys("Eroare fork 1"); else if (pid==0)A /* primul proces fiu */ if ((pid=fork()) ==-1) err_sys("Eroare fork 2"); else if (pid) /*primul fiu=parintele din fork 2 */ exit(0);
/* Al doilea fiu. Parintele acestui proces devine procesul init imediat ce parintele din al doilea fork executa apelul exit
*/ sleep( 3); printf("Al doilea fiu, pid parinte=%d\n",getppid()); exit(0);
S
/* se asteapta primul fiu */ if(waitpid( pid, NULL, 0) !=pid) err_sys("Eroare waitpid");
/*
Procesul parinte initial. Executia continua fara ca el sa fie parintele celui de-al doilea proces fiu.
*/ exit(0);
S
Al doilea proces fiu a fost pus in starea de asteptare pentru 3 secunde pentru a fi siguri ca primul fiu se termina inainte de afisarea identificatorului de proces al parintelui.
Executia acestui program afiseaza:
$e6
$Al diolea fiu, pid parinte=1
Shell-ul afiseaza prompter-ul la terminarea procesului,parinte initial, adica inainte ca ce de-al doilea fiu sa afiseze pid-ul parintelui sau.
In general nu se poate spune care din doua procese aflate in relatia parinte-fiu se executa primul. Chiar daca am sti care proces se executa primul, nu se poate preciza nimic despre succesinea la executie a celor doua
procese. Acest lucru depinde de incarcarea curenta a sistemului si de algoritmul de planificare folosit de nucleu. Pentru exemplul anterior, cu toate ca s-au prevazut cele 3 secunde intarziere, exista posibilitatea, daca sistemul era puternic incarcat, ca al doilea proces fiu sa se termine inaintea
primului.
In situatii limita se impune o sincronizare intre procese. Daca un proces asteapta terminarea unui fiu acesta apeleaza wait. Daca un fiu asteapta
terminarea parintelui, o bucla de forma:
while (getppid() !=1) sleep(1);
este suficienta. Inconvenientul acesteia ramane irosirea timpului CPU, deoarece apelantul verifica din secunda in secunda pid-ul. Pentru a sincroniza doua procese se considera un program de test care dupa ce a executat apelul fork afiseaza cate un sir in fiecare proces.
Ex 7:
/*
A se compila cu: cc e7.c err.o pr.o rut.o -oe7
*/
#include <sys/types.h>
#include "hdr.h" static void test(char *);
int main(void)
A pid_t pid;
/* config(); printf("Procesul parinte are pid-ul: %d\n",getpid());
*/ switch(pid=fork()) A case -1: err_sys("Eroare fork()"); break; case 0:
/* Wait_P(); */ test("Fiul a scris \n"); break; default: test("Procesul parinte a scris\n");
/* Tell_F(pid);*/ break;
S exit(0);
S
static void test(char *s)
A char *pc; int c; setbuf(stdout,NULL); for(pc=s;c=*pc++) putc(c,stdout); sleep(1.99999);
S
S
Scrierea in fisierul standard de iesire este fara tampon astfel incat fiecare caracter este imediat scris la iesire. Nucleul realizeaza executia intermixata a celor doua procese, rezultatul fiind:
PFrioucle sau ls cprairsi nte a scris
Codul prezentat contine cateva linii de comentariu esentiale daca se doreste sincronizarea celor doua procese. Considerand ca procesele se executa intr-o sesiune utilizator (nu superuser) si ca sincronizarea se face prin crearea unui fisier (cea mai simpla modalitate de sincronizare ) continutul acestor rutine ar putea fi:
/*
A se compila cu: cc rut.c -orut.o
*/
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
config()
A int fd; if( (fd=creat("@@sincro@@"",0))==-1) err_sys"(Eroare open"); printf("S-a creat fisierul @@sincro@@\n"); return;
S
void
Tell_F(pid_t pid)
A int fd; printf("Poate sa se execute acum procesul fiu: %d\n",pid); if( unlink("@@sincro@@")==-1) err_sys("Eroare unlink");
S
void
Wait_P(void)
A int fd;
while( (fd=creat("@@sincro@@",0))==-1 && errno==EACCES); if( fd==-1 || close(fd)==-1) err_sys(("Eroare creat()"); unlink("@@sicro@@");
S
Functia config creeaza fisierul @@sincro@@.Procesul fiu este pus in asteptare pana la terminarea procesului parinte prin apelul Wait_P. Prin acest apel fiul incearca sa creeze din nou acelasi fisier, lucru imposibil atata timp cat el exista (se reaminteste faptul ca procesele sunt obisnuite).
In momentul in care procesul parinte s-a terminat, acesta apeleaza Tell_F.
Prin acest apel fisierul @@sincro@@ este sters si procesul fiu termina apelul
Wait_P si afiseaza propriul mesaj. Rezultatul programului ,in conditiile eliminarii simbolurilor de delimitare comentariu, din exemplul 7, este:
S-a creat fisierul @@sincro@@
Procesul parinte are pid-ul:437
Procesul parinte a scris
Poate sa se execute acum procesul fiu: 438
Fiul a scris
In mod analog se poate aranja sincronizarea inversa.
Tema :
- exersati si imaginati sincronizari de programe cu fork - wait
- dindu-se programul planific.c, pentru simularea algoritmului de planificare RR, analizati si dezvoltati programul pentru toti algoritmii de planificare prezentati la curs