Documente Academic
Documente Profesional
Documente Cultură
PROCESE Sisteme Operare
PROCESE Sisteme Operare
Identificarea proceselor
Fiecare proces are asociat un identificator unic numit identicator de proces
(PID). Un proces poate s determine propriul PID folosind apelul sistem getpid().
PID-ul unui proces nu poate fi schimbat, dar poate fi refolosit cnd procesul nu mai
exist.
Procesele sunt organizate n Unix ntr-o structur arborescent asemenea
organizrii sistemului de fiiere. Orice proces nou n Unix este creat de un proces anterior
existent, dnd natere unei relaii printe-fiu. Excepie face procesul init, care este creat
si utilizat chiar de nucleu, toate celelalte procese din sistem fiind descendeni direci sau
indireci.
Comanda pstree permite vizualizarea arborelui curent de procese.
Un proces poate sa determine PID-ul printelui prin apelul getppid().
Sistemul UNIX ine evidena proceselor ntr-o structur de date intern numit
tabel de procese. Lista proceselor din tabela de procese poate fi obinut prin
comanda ps.
Un proces poate fi asociat unui terminal, care este numit terminalul de control
asociat procesului. Acesta este motenit de la procesul printe la creare.
Un proces care nu este asociat cu un terminal de control este numit daemon.
Spoolerul de imprimant este un exemplu de astfel de proces. Un proces daemon este
identificat n rezultatul afirii comenzii ps prin simbolul ? plasat n coloana TTY.
Process Control Block structur n sistemul de operare n care se regsesc
informaii necesare pentru rularea programului, cte una pentru fiecare proces existent
n sistem:
-
lista de semnale (blocate, ignorate sau care ateapt s fie trimise procesului)
crearea
terminarea
pid_t getppid(void)
uid_t getuid(void)
gid_t getgid(void)
procesul curent
n noul proces (fiu) toate vechile variabile i pstreaz valorile, toi descriptorii de
fiier sunt aceiai, se motenete acelai UID real i GUID real, acelai ID de grup de
procese, aceleai variabile de context.
Din momentul revenirii din apelul fork, procesele tat i fiu se execut
independent, concurnd unul cu cellalt pentru obinerea resurselor. Procesul fiu i
ncepe execuia din locul unde rmsese procesul tat. Nu se poate preciza care dintre
procese va porni primul. Este posibil ns separarea execuiei n cele dou procese prin
testarea valorii ntoarse de apelul fork.
Deoarece codul printelui i codul fiului sunt identice si pentru ca aceste procese
vor rula n continuare n paralel, trebuie fcut clar distincia, n interiorul programului,
ntre aciunile ce vor fi executate de fiu i cele ale printelui. Cu alte cuvinte, este nevoie
de o metod care s indice care este poriunea de cod a printelui i care a fiului.
Apelul de sistem fork este unul mai special prin faptul c, n caz de reuit, se
ntoarce de 2 ori, cte o dat n fiecare proces.
Fork() ntoarce valoarea -1 n caz de eroare, 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 menionate anterior.
Secvena clasic de creare a unui proces este prezentat n continuare:
#include <sys/types.h>
#include <unistd.h>
...
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);
...
}
Apelul wait() este folosit pentru asteptarea terminarii copilului si preluarea valorii
returnate de acesta. Parametrul status este folosit pentru evaluarea valorii returnate,
folosind cateva macro-uri definite special. Apelul waitpid() este asemanator cu wait(), dar
ateapt terminarea unui anumit proces dat, in vreme ce wait() ateapt terminarea
oricrui proces copil.
Familia de apeluri wait suspend execuia procesului apelant pn cnd procesul
(procesele) specificate n argumente fie s-au terminat fie au fost oprite (SIGSTOP).
Valorile uzuale ale argumentului pid sunt identificatorul unui proces copil (spre
exemplu, returnat de fork) sau -1, n cazul n care se dorete ateptarea oricrui proces
copil.
Funcia va ntoarce PID-ul procesului a crui stare e raportat; informaiile de stare
sunt depuse ca int la adresa indicata prin argumentul status.
Options ofer posibilitatea procesului apelant de a se ntoarce din apelul waitpid,
fr
a
rmne
blocat
n
ateptarea
unui
proces
copil
(WNOHANG,
WUNTRACED,WCONTINUED studiai man page)
Exista o variant simplificat care ateapt orice proces copil s se termine:
pid_t wait(int *status);
//adugai
#include <stdlib.h>
void _Exit (int status);
void exit (int status);
mai
mult
flexibilitate
la
(1) execl ( const char *path, const char *arg0, ..., NULL);
(2) execv ( const char *path, char *argv[]);
(3) execlp( const cahr *filename, const char *arg0, ..., NULL);
(4) execvp( const cahr *filename, char *argv[]);
Se observ c primul argument este nsui numele programului, iar ultimul este
NULL. execl nu caut programul dat ca parametru n PATH, astfel c acesta trebuie
nsoit de calea complet.
Versiunea execlp cauta programul i n PATH.
Folosirea oricrei funcii din familia exec necesit includerea header-ului unistd.h.
Exemplu 2 - sys2.c
#include <stdlib.h>
int main(int argc, char **argv)
{
system("cd /etc/rc.d/rc$RUNLEVEL.d/; ls -la");
}
}
int main()
{
my_system("ls");
return 0;
}
Motenirea resurselor
Apelul sistem fork realizeaz o copie a procesului iniial, ca atare imaginea
proceselor n memorie este identic. Noul proces va avea propria lui zon de date,
propria lui stiv, propriul lui cod executabil, toate fiind copiate de la printe. Orice
modificare fcut, prin urmare, asupra unei variabile din procesul fiu, va rmne
invizibil procesului printe i invers.
Motenirea(duplicarea) segmentului de
cod
int main()
{
int c=2,status,pid=1;
while(c){
if(pid==1)
pid=fork();
if(pid>0){
wait(&status);
printf("parent %d done\n",getpid());
_exit(0);
}
printf("child %d running - c=%d\n",getpid(),c);
c--;
}
printf("child %d done\n",getpid());
return 0;
}
Motenirea(duplicarea) segmentului de
date
if(pid>0){
wait(&status);
printf("parent %d done - c=
%d\n",getpid(),c);
_exit(0);
}
Exemplu motenire/ateptare
int main()
{
int c=2,status;
while(c){
fork();
printf("proces PID=%d PPID=%d cu c=%d\n",getpid(),getppid(),c);
c--;
}
while(wait(&status)>0){}
printf("proces %d FINAL\n",getpid());
return 0;
}
COW Copy-On-Write
n general, la apelul fork(), toate resursele printelui sunt duplicate i atribuite i
copilului. Aceast abordare este ineficient n 2 situaii:
-
dac procesul copil ar apela imediat ce a fost creat exec(), ntreg programul ar fi
rescris i, prin urmare, toat zona de date i cod va fi fost duplicat inutil;
dac ambele procese ar folosi aceleai date i acelai cod, fr s aduc
modifice, ar fi inutil existena a 2 seturi resurse i ar fi suficient unul singur.
n Linux, apelul fork() este implementat mpreun cu tehnica COW care permite
amnarea sau chiar prevenirea duplicrii ineficiente a resurselor. Astfel, la crearea unui
nou proces, att copilul ct i printele partajeaz resursele care sunt marcate ntr-o aa
manier nct, duplicarea are loc abia n momentul n care unul din cele 2 procese
trebuie s modifice o zona de memorie. n cazul modificrilor aduse zonei de cod(apelul
exec() ), resursele nu mai sunt duplicate i se aloc altele pentru noul program ncrcat.
main.c a.out
int main()
{
int pid,fd,status;
char *buf[3]={"before fork\n","parent\n","child\n"};
fd=open("a.txt",O_WRONLY|O_TRUNC|O_CREAT,0644);
dup2(fd,STDOUT_FILENO);
printf("%s",buf[0]);
fflush(stdout);
pid=fork();
if (pid > 0) {
wait(&status);
printf("%s",buf[1]);
fflush(stdout);
} else {
//execlp("ls","-a",NULL);
printf("%s",buf[2]);
fflush(stdout);
_exit(0);
}
return 0;
}
Dup un apel exec descriptorii de fiier sunt pstrai de asemenea, mai puin aceia
dintre ei care au setat flagul O_CLOEXEC
Pentru a seta flagul O_CLOEXEC, fie se deschide fisierul cu flagul setat fie, daca
fisierul este deja deschis, se folosete funcia fcntl cu un apel de tipul:
fcntl(file_descriptor, F_SETFD, FD_CLOEXEC);
Pentru a putea folosi funcia fcntl trebuie incluse header-ele unistd.h i fcntl.h.
main.c a.out
#include
#include
#include
#include
#include
#include
<stdio.h>
<unistd.h>
<sys/wait.h>
<sys/types.h>
<sys/stat.h>
<fcntl.h>
progr.c progr
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
//#ifndef O_CLOEXEC
//# define O_CLOEXEC 02000000
//#endif
write(fd,argv[1],6);
return 0;
}
int main()
{
int pid,fd,status;
char *buf[3]={"before fork\n","parent\n","child\n"};
fd=open("a.txt",O_WRONLY|O_APPEND); // |
O_CLOEXEC);
//fcntl(fd,F_SETFD,FD_CLOEXEC);
write(fd,buf[0],12);
pid=fork();
if (pid > 0) {
wait(&status);
write(fd,buf[1],7);
} else {
execl("./progr","progr",buf[2],NULL);
_exit(1);
}
return 0;
}
Variabile de mediu
extern char **environ;
Exemplu
int main()
{
char * pPath;
pPath = getenv ("PATH");
if (pPath!=NULL)
printf ("The current path is: %s",pPath);
return 0;
}
Forking
Funciile de bibliotec fork(), vfork() i _clone() sunt implementate n Linux prin
apelul de sistem clone(), care, la rndul su apeleaz do_fork().
user-space
fork()
vfork()
_clone()
kernel-space
clone()
do_fork()
copy_process()
Copy_process() :
-
apeleaz
copy_flags()
PF_FORKNOEXEC;
pentru
actualizarea
flag-urilor;
se
seteaz
vfork()
Apelul vfork() are acelai efect cu meniunea c resursele procesului printe nu
sunt duplicate. Procesul copil se execut n spaiul de adrese al printelui, care devine
astfel blocat pn cnd copilul i ncheie execuia sau invoc apelul exec().
Avnd astfel garania c procesul copil i ncepe primul execuia are loc o utilizare
foarte eficient a resurselor n sensul c, n cazul execuiei apelului exec() pentru
ncrcarea unui nou program n cadrul procesului copil, exec() va fi chiar prima
instruciune executat dup ntoarcerea din fork()/vfork() i, astfel, nu se vor mai duplica
resurse inutil.
Comportamentul este nedefinit dac procesul creat nu termin execuia cu apelul
_exit().
Rulai exemplul de mai jos, adugnd/eliminnd secvenele comentate.
int main()
{
int pid,var=5;
printf("parinte? ->PID=%d PPID=%d-------var=%d\n",getpid(),getppid(),var);
pid=vfork(); //pid=fork();
if (pid > 0) {
//sleep(3);
printf("if -> parinte? ->PID=%d PPID=%d------- var =%d\n",getpid(),getppid(),var);
} else {
printf("if -> fiu? ->PID=%d PPID=%d------ var =%d\n",getpid(),getppid(),var);
var ++;
printf("if -> fiu? ->PID=%d PPID=%d------ var =%d\n",getpid(),getppid(),var);
execl("/bin/ls", "ls", "-la", NULL);
//_exit(0);
}
return 0;
}
Exiting
Un proces i poate termina execuia:
-
unde mode poate lua valoarea parent, caz n care debugger-ul continua depanarea
procesului parinte, sau valoarea child, i atunci noul proces creat va fi depanat n
continuare. Se poate observa c n aceast maniera 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 afieaza 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
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, sa considerm programul
urmtor:
Exemplu fork-debug
// forktest.c
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <sys/wait.h>
4 #include <unistd.h>
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 ca gdb
va urmri exclusiv execuia procesului printe:
$ gcc -O0 -g3 -o forktest forktest.c
$ gdb ./forktest
[...]
(gdb) run
Starting program: /home/student/forktest
The child process is executing...
Everything is done!
Program exited normally.