Sunteți pe pagina 1din 20

TARGET

Crearea unei aplicatii care sa execute CAT MAI RAPID si EFICIENT criptarea unui fisier folosind un algoritm propriu
organizat conform schemei de mai jos. Continutul fisierului va fi criptat pe blocuri
Input file

Operation
1

Output file
Temp
file
Operation
2

Pentru masurarea timpilor de executie, se poate utiliza comanda time pentru executia aplicatiei.

$time encrypt input.txt

sau

$/usr/bin/time encrypt input.txt

PROCESE

Un concept cheie în orice sistem de operare este procesul. Un proces este un program în
execuţie(instanţa execuţiei unui program de pe disc). 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
(thread-uri). Putem avea mai multe procese ce execută acelaşi program, dar oricare două procese sunt complet
independente.
Procesele sunt organizate în Unix într-o structură arborescentă asemenea organizării sistemului de
fişiere. Toate procesele active sunt de fapt descendenţi direcţi sau indirecţi ai unui singur proces, lansat la
pornirea sistemului prin comanda /etc/init. Comanda pstree permite vizualizarea arborelui curent de procese.
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 când procesul nu mai există.
Procesele sunt organizate în Unix într-o structură arborescentă asemenea organizării sistemului de
fişiere. Orice proces nou în Unix este creat de un proces anterior existent, dând naştere unei relaţii părinte-fiu.
Excepţie face procesul init, care este creat si utilizat chiar de nucleu, toate celelalte procese din sistem fiind
descendenţi direcţi sau indirecţi.
Comanda pstree permite vizualizarea arborelui curent de procese.
Unproces poate sa determine PID-ul părintelui prin apelul getppid().
Sistemul UNIX ţine evidenţa proceselor într-o structură de date internă numită tabelă de procese. Lista
proceselor din tabela de procese poate fi obţinută prin comanda ps.
Un proces poate fi asociat unui terminal, care este numit terminalul de control asociat procesului.
Acesta este moştenit de la procesul părinte 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 afişării comenzii ps prin
simbolul ? plasat în coloana TTY.
Process Control Block structură în sistemul de operare în care se regăsesc informaţii necesare pentru
rularea programului, câte una pentru fiecare proces existent în sistem:
- spaţiile de adrese şi regiştrii generali
- PC (contor program) şi SP (indicator stivă)
- tabelele de fişiere deschise (descriptori de fişiere)
- 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ăcina, directorul curent)
În general un proces rulează într-un mediu specificat printr-un set de variabile de sistem. O variabilă de
sistem este o pereche NUME=valoare. Un proces poate să verifice sau să seteze valoarea unei variabile de
sistem printr-o serie de apeluri de bibliotecă.
Î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
- terminarea
- aşteptarea terminării unui proces
- duplicarea descriptorilor de resurse între procese ori închiderea acestor descriptori.
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
- exit() pentru terminarea unui proces.
- dup() şi dup2() pentru copierea descriptorilor de fişier
- getenv(), setenv(), unsetenv() pentru citirea, modificarea ori ştergerea unei variabile de sistem
precum şi un pointer la tabela de variabile de sistem - environ.

Crearea unui proces


În UNIX singura modalitate de creare a unui proces este prin apelul de sistem fork:
pid_t fork(void);
La fiecare execuţie a acestui apel, se obţin două procese concurente, identice la început, dar cu nume
diferite. Apelul sistem fork realizează o copie a procesului iniţial, 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 părinte.
Aşadar, 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.
pid_t getpid(void) - returneaza PID-ul procesului curent
pid_t getppid(void) - returneaza PID-ul parintelui procesului curent
uid_t getuid(void) - returneaza identificatorul utilizatorului care a lansat procesul curent
gid_t getgid(void) - returneaza identificatorul grupului utilizatorului care a lansat
procesul curent
În noul proces (fiu) toate vechile variabile îşi păstrează valorile, toţi descriptorii de fişier sunt aceiaşi, se
moşteneşte acelaşi UID real şi GUID real, acelaşi ID de grup de procese, aceleaşi variabile de context.
Din momentul revenirii din apelul fork, procesele tată şi fiu se execută independent, concurând unul cu
celălalt pentru obţinerea resurselor. Procesul fiu îşi începe execuţia din locul unde rămăsese procesul tată. Nu
se poate preciza care dintre procese va porni primul. Este posibilă însă separarea execuţiei în cele două
procese prin testarea valorii întoarse de apelul fork.
Deoarece codul părintelui şi codul fiului sunt identice si pentru ca aceste procese vor rula în continuare
în paralel, trebuie făcută clar distincţia, în interiorul programului, între acţiunile ce vor fi executate de fiu şi cele
ale părintelui. Cu alte cuvinte, este nevoie de o metodă care să indice care este porţiunea de cod a părintelui şi
care a fiului.
Apelul de sistem fork este unul mai special prin faptul că, în caz de reuşită, se întoarce de 2 ori, câte o
dată în fiecare proces.
Fork() întoarce valoarea -1 în caz de eroare, PID-ul noului proces în procesul părinte şi valoarea 0 în
procesul copil. Pentru aflarea PID-ului procesului curent ori al procesului părinte se va apela una din funcţiile
menţionate anterior.
Secvenţa 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);
...
}

Aşteptarea terminării unui proces


Rulaţi următorul exemplu. Ce observaţi? Cum poate fi remediată execuţia programului?
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int pid;
printf("\nbefore if -> PID=%d PPID=%d\n",getpid(),getppid());
if ((pid = fork()) < 0) {
printf("EROARE!!!");
} else if (pid == 0) {
printf("if -> fiu? ->PID=%d PPID=%d\n",getpid(),getppid());
} else {
printf("if -> parinte? ->PID=%d PPID=%d\n",getpid(),getppid());
}
printf("after if -> ? ->PID=%d PPID=%d\n",getpid(),getppid());
return 0;
}

Procesul părinte poate să aştepte terminarea (normala sau cu eroare) procesului copil folosind apelurile
sistem wait sau waitpid(wait3, wait4).
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
Apelul wait() este folosit pentru asteptarea terminarii executiei 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 aşteaptă terminarea unui anumit proces dat, in vreme
ce wait() aşteaptă terminarea oricărui proces copil.
Familia de apeluri wait suspendă execuţia procesului apelant până când 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 doreşte aşteptarea oricărui proces copil.
Funcţia va întoarce PID-ul procesului a cărui stare e raportată; informaţiile de stare sunt depuse ca int la
adresa indicata prin argumentul status.
Options oferă posibilitatea procesului apelant de a se întoarce din apelul waitpid, fără a rămâne blocat
în aşteptarea unui proces copil (WNOHANG, WUNTRACED,WCONTINUED – studiaţi man page)
Exista o variantă simplificată care aşteaptă orice proces copil să se termine:
pid_t wait(int *status); care este echivalentă cu waitpid(-1, &status, 0);
Pentru a folosi wait sau waitpid trebuie incluse header-ele sys/types.h şi sys/wait.h.

Un proces ce apelează wait sau waitpid poate:


a) să se blocheze (daca toţi copiii săi sunt în execuţie)
b) să primească starea de terminare a copilului (daca unul dintre copii s-a terminat)
c) să primească eroare (daca nu are procese copil).

Diferenţele intre cele doua funcţii constau in:


a) wait blochează procesul apelant până la terminarea execuţiei unui copil, în timp ce waitpid
are o opţiune, precizată prin argumentul options, care evită acest lucru (WNOHANG).
b) waitpid nu aşteaptă terminarea primului fiu, ci poate specifica prin argumentul
pid procesul fiu aşteptat.

Pentru a analiza starea în care s-a terminat un proces copil (normal sau eroare) există trei macrouri
excluse mutual, toate prefixate de WIF şi definite în fişierul sys/wait.h. Pelângă acestea, exista alte macrouri
pentru determinarea codului de exit, număr semnal, etc (studiaţi man page).
Aceste macrouri se aplica parametrului pstatusşi sunt ilustrate mai jos:
a) WIFEXITED (pstatus) – TRUE daca informaţia de stare, pstatus, provine de la un proces terminat
normal(apel exit sau return) - în acest caz, WEXITSTATUS(pstatus) extrage octetul mai puţin semnificativ pentru
a determina codul de exit al procesuluiterminat.
Completaţi exemplul anterior cu secvenţa de cod de mai jos:
pid_copil=wait(&status);
if(WIFEXITED(status))
printf("%d terminat normal cu codul %d\n",pid_copil,WEXITSTATUS(status)); //adăugaţi în procesul părinte

b) WIFSIGNALED (pstatus) – TRUE daca informaţia de stare, pstatus, provine de la un proces terminat
anormal (semnal netratat) - în acest caz WTERMSIG (pstatus) extrage numărul semnalului trimis procesului
terminat.
while(1){} //în procesul copil
>ps –ef
>kill –SIGUSR1 pid_copil //în terminal
if(WIFSIGNALED(status))
printf("%d terminat cu semnalul %d\n",pidex,WTERMSIG(status)); //în părinte

c) WIFSTOPPED (pstatus) – TRUE daca informaţia de stare, pstatus, provine de la un proces temporar
oprit - în acest caz WSTOPSIG (pstatus) extrage numărul semnalului care a oprit procesul.
while(1){} //în procesul copil
-------------------------------------
sleep(2);
kill(pid,SIGTSTP);
sleep(2);
pidex=waitpid(-1,&status,WUNTRACED);
if(WIFSTOPPED(status))
printf("%d oprit temporar cu semnalul %d\n",pidex,WSTOPSIG(status)); //în părinte

Terminarea unui proces


Pentru terminarea procesului curent, Linux pune la dispoziţie apelul de sistem exit. Dintr-un program
Cexistă trei moduri de invocare a acestui apel de sistem:
Apelul _exit (POSIX), şi apelurile _Exit şi exitdin biblioteca standard C.
#include <unistd.h> #include <stdlib.h>
void _exit (int status); void _Exit (int status);
void exit (int status);

Procesul apelant se va termina imediat. Toţi descriptorii de fişier ai procesului sunt închişi, copiii
procesului sunt "înfiaţi" de init, iar părintelui procesului îi va fi trimis un semnal SIGCHLD. Procesului părinte îi
va fi întoarsă valoarea status ca rezultat al unei funcţii de aşteptare (wait sau waitpid).
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.
FORK - waiting
int main()
{
int pid,pidex;
int status;
printf("\nbefore if -> PID=%d PPID=%d\n",getpid(),getppid());
if ((pid = fork()) < 0) {
printf("EROARE!!!");
} else if (pid == 0) {
printf("if -> fiu? ->PID=%d PPID=%d\n",getpid(),getppid());
while(1){}
} else {
printf("if -> parinte? ->PID=%d PPID=%d\n",getpid(),getppid());
//sleep(2);
//kill(pid,SIGTERM);
//sleep(2);
pidex=waitpid(-1,&status,WUNTRACED);
printf("--------\n");
if(WIFEXITED(status))
printf("%d terminat normal cu codul %d\n",pidex,WEXITSTATUS(status));
if(WIFSIGNALED(status))
printf("%d terminat cu semnalul %d\n",pidex,WTERMSIG(status));
if(WIFSTOPPED(status))
printf("%d oprit temporar cu semnalul %d\n",pidex,WSTOPSIG(status));
}
printf("after if -> ? ->PID=%d PPID=%d\n",getpid(),getppid());
return 0;
}

Înlocuirea imaginii unui proces


Raţiunea a două procese identice are sens dacă se poate modifica segmentul de date şi cel de cod al
procesului rezultat aşa încât să se poată încărca un nou program. Pentru acest lucru există apelul exec()
(împreuna cu familia de astfel de apeluri execl, execlp, execv si execvp).
Partea de sistem a procesului nu se modifica în nici un fel prin apelul exec. Practic, procesul fiu executa
cu totul altceva decât parintele sau. Dupa un apel exec reusit nu se mai revine în vechiul cod. Fisierele deschise
ale tatalui se regasesc deschise si la fiu dupa apelul exec si ca indicatorul de citire/scriere al fisierelor deschise
ramâne nemodificat, ceea ce poate cauza neplaceri în cazul în care tatal si fiul vor sa scrie în acelasi loc. Un
apel exec nereusit returneaza valoarea -1, dar cum alta valoare nu se returneaza ea nu trebuie testata.
Diferitele variante de exec() dau utilizatorului mai multă flexibilitate la transmiterea parametrilor.
Sintaxele lor sunt:
(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[]);

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ţializaţi regiştrii IP (EIP/RIP - contorul program) şi SP (ESP/RSP – indicatorul
stivă) şi regiştrii generali.
Presupunem că vrem să apelăm comanda ls -la: execl("ls", "ls", "-la", NULL);

Se observă că primul argument este însuşi numele programului, iar ultimul este NULL. execl nu caută
programul dat ca parametru în PATH, astfel că acesta trebuie însoţit de calea completă.
Versiunea execlp cauta programul şi în PATH.
Folosirea oricărei funcţii din familia exec necesită includerea header-ului unistd.h.

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
Exemplu 2 - sys2.c
#include <stdlib.h>
int main(int argc, char **argv) #include <stdlib.h>
{ int main(int argc, char **argv)
system("ls -la $HOME"); {
} system("cd /etc/rc.d/rc$RUNLEVEL.d/; ls -la");
care este echivalent cu }
program C care este echivalent cu
$ sh -c "ls -la $HOME"
$ 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"(shell-ul creează la rândul său un proces nou pentru execuţia
comenzii), timp în care părintele aşteaptă terminarea procesului copil.
my_system
Un exemplu de folosire a primitivelor exec, fork, exit şi wait (sau de rulare a unui program) îl reprezintă
chiar reimplementarea apelului de bibliotecă system.
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. când nu se gaseşte fişierul de
executat - în cazul nostru /bin/bash. În caz de eroare, terminăm procesul copil */
exit(-1);
}
}
//doar procesul părinte ajunge aici, şi doar dacă fork() s-a terminat cu succes
waitpid(pid, &status, 0);// obţinem codul de eroare cu care s-a terminat copilul
if (WIFEXITED(status))
printf("Child %d terminated normally, with code %d\n", pid, WEXITSTATUS(status));
return status;
}
int main()
{
my_system("ls");
return 0;
}

Moştenirea resurselor
Apelul sistem fork realizează o copie a procesului iniţial, 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 părinte. Orice modificare făcută, prin urmare, asupra unei variabile din procesul fiu, va rămâne
invizibilă procesului părinte şi invers.
Moştenirea(duplicarea) segmentului de cod Moştenirea(duplicarea) segmentului de date
int main()
{
int c=2,status,pid=1;
while(c){
if(pid==1)
pid=fork();
if(pid>0){ if(pid>0){
wait(&status); wait(&status);
printf("parent %d done\n",getpid()); printf("parent %d done - c=%d\n",getpid(),c);
_exit(0); _exit(0);
} }
printf("child %d running - c=%d\n",getpid(),c);
c--;
}
printf("child %d done\n",getpid());
return 0;
}
Exemplu – moștenire/așteptare
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;
}

O greşeală frecventă este întâlnită în cazul folosirii unei secvenţe repetitive pentru crearea unui număr
fix de procese copil. Fără un control bun al execuţiei secvenţei se pot crea mult mai multe procese.

În exemplul următor se doreşte crearea a 3 procese copil.


int main()
{
int i;//int pid=1;
for(i=0;i<3;i++){
//if (pid>0)
pid=fork();
}
printf("proces %d cu parinte %d\n",getpid(),getppid());
while(1){}
return 0;
}
COW – Copy-On-Write

În general, la apelul fork(), toate resursele părintelui sunt duplicate şi atribuite şi copilului. Această
abordare este ineficientă în 2 situaţii:

- 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 aceleaşi date şi acelaşi cod, fără să aducă modifice, ar fi inutilă
existenţa a 2 seturi resurse şi ar fi suficient unul singur.

În Linux, apelul fork() este implementat împreună cu tehnica COW care permite amânarea sau chiar
prevenirea duplicării ineficiente a resurselor. Astfel, la crearea unui nou proces, atât copilul cât şi părintele
partajează resursele care sunt marcate într-o aşa manieră încât, duplicarea are loc abia în momentul în care
unul din cele 2 procese trebuie să modifice o zona de memorie. În cazul modificărilor aduse zonei de
cod(apelul exec() ), resursele nu mai sunt duplicate şi se alocă altele pentru noul program încărcat.

Rulati urmatorul exemplu si explicati rezultatul:


#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
int pid,status,t;
int var=2;
printf("\nbefore if ­> PID=%d PPID=%d\n",getpid(),getppid());
if ((pid = fork()) < 0) 
{
printf("EROARE!!!");
} else if (pid == 0) 
{
sleep(3);
printf("if ­> fiu? ­>PID=%d PPID=%d\n",getpid(),getppid());
printf("\n\tfiu ­> var=%d la adresa %p\n",var,&var);
var++;
printf("\n\tfiu ­> var=%d la adresa %p\n",var,&var);
} else 
{
printf("if ­> parinte? ­>PID=%d PPID=%d\n",getpid(),getppid());
printf("\n\tparinte ­> var=%d la adresa %p\n",var,&var);
t=wait(&status);
if(t==pid)
printf("\nchiled done\n");
printf("\n\tparinte ­> var=%d la adresa %p\n",var,&var);
}
printf("after if ­> ? ­>PID=%d PPID=%d\n",getpid(),getppid());
return 0;
}

Moştenirea descriptorilor de fişier dupa operații fork/exec


Procesul fiu va mosteni de la parinte toti descriptorii de fisier deschisi de catre acesta, asa ca orice
prelucrari ulterioare in fisiere vor fi efectuate in punctul in care le-a lasat parintele.

main.c  a.out main.c  a.out


int main() int main()
{ {
int pid,fd,status; int pid,fd,status;
char *buf[3]={"before fork\n","parent\n","child"}; char *buf[3]={"before fork\n","parent\n","child\n"};
fd=open("a.txt",O_WRONLY|O_TRUNC|O_CREAT,0644); fd=open("a.txt",O_WRONLY|O_TRUNC|O_CREAT,0644);
write(fd,buf[0],12); dup2(fd,STDOUT_FILENO);
pid=fork(); printf("%s",buf[0]);
if (pid > 0) { fflush(stdout);
write(fd,buf[1],7); pid=fork();
wait(&status); if (pid > 0) {
} else { wait(&status);
write(fd,buf[2],6); printf("%s",buf[1]);
_exit(0); fflush(stdout);
} } else {
return 0; //execlp("ls","-a",NULL);
} printf("%s",buf[2]);
fflush(stdout);
_exit(0);
}
return 0;
}
După un apel exec descriptorii de fişier sunt păstraţi de asemenea, mai puţin 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 foloseşte funcţia fcntl cu un apel de tipul:

fcntl(file_descriptor, F_SETFD, FD_CLOEXEC);


Pentru a putea folosi funcţia fcntl trebuie incluse header-ele unistd.h şi fcntl.h.

main.c  a.out progr.c  progr


#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <sys/wait.h> #include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h> int main(int argc,char *argv[])
#include <fcntl.h> {
int fd=3;
//#ifndef O_CLOEXEC write(fd,argv[1],6);
//# define O_CLOEXEC 02000000 return 0;
//#endif }

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;
Un vector de pointeri la şiruri de caractere, ce conţin variabilele de mediu şi valorile lor. Vectorul e
terminat cu NULL. Șirurile de caractere sunt de forma "VARIABILA=VALOARE".

getenv întoarce valoarea variabilei de mediu denumite name, sau NULL daca nu există o variabilă de
mediudenumita astfel:

char* getenv(const char *name);


setenv adauga în mediu variabila cu numele name (daca nu exista deja) şi îi seteaza valoarea la value.

Daca variabila exista şi replace e 0, actiunea de setare a valorii variabilei e ignorata; daca replace e
diferitde 0, valoarea variabilei devine value:
int setenv(const char *name, const char *value, int replace);
unsetenv sterge variabila denumita name din mediu:

int unsetenv(const char *name);


Exemplu
int main()
{
char * pPath;
pPath = getenv ("PATH");
if (pPath!=NULL) printf ("The current path is: %s",pPath);
return 0;
}

Forking NU
Funcţiile de bibliotecă fork(), vfork() şi _clone() sunt implementate în Linux prin apelul de sistem clone(),
care, la rândul său apelează do_fork().

user-space kernel-space

fork() 
vfork()  clone()  do_fork()  copy_process()
_clone() 

Copy_process() :

- apelează dup_task_struct(), care creează o nouă stivă precum şi două structuri – una pentru proces
(task_struct) şi alta pentru firul de execuţie aferent (thread_info) cu valori identice cu ale procesului
părinte; în acest moment cele 2 procese sunt identice;

- verifică disponibilitatea alocării unei identităţi unice procesului creat;

- apelează copy_flags() pentru actualizarea flag-urilor; se setează PF_FORKNOEXEC;

- apelează alloc_pid() pentru a atribui un PID nou procesului creat;

- în funcţie de parametrii transmişi la apelul clone(), se vor copia sau partaja resurse precum fişiere
deschise, informaţii despre sistemul de fişiere, rutine de tratare a semnalelor, spaţiul de adrese;

- returnează un pointer către noul proces creat.

vfork()
Apelul vfork() are acelaşi efect cu menţiunea că resursele procesului părinte nu sunt duplicate. Procesul
copil se execută în spaţiul de adrese al părintelui, care devine astfel blocat până când copilul îşi încheie
execuţia sau invocă apelul exec().

Având astfel garanţia că procesul copil îşi începe primul execuţia are loc o utilizare foarte eficientă a
resurselor în sensul că, în cazul execuţiei apelului exec() pentru încărcarea unui nou program în cadrul
procesului copil, exec() va fi chiar prima instrucţiune executată după întoarcerea din fork()/vfork() şi, astfel, nu
se vor mai duplica resurse inutil.

Comportamentul este nedefinit dacă procesul creat nu termină execuţia cu apelul _exit().
Rulaţi exemplul de mai jos, adăugând/eliminând secvenţele 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 execuţia:

- involuntar – când primeşte un semnal sau o excepţie pe care nu o poate trata;

- voluntar – la apelul exit().

Indiferent de situaţie, apelul de sistem implicat este do_fork() care:


- marchează task_struct ca aparţinând unui proces în curs de ieşire;
- anulează toate timerele active;
- eliberează zona de memorie; dacă nu este partajată cu un alt proces, kernelul o eliberează complet;
- scoate procesul din cozile de aşteptare ale semafoarelor;
- eliberează structurile pentru lucrul cu fişiere (descriptori);
- setează,în cadrul task_struct, valoarea de ieşire dată de funcţia exit() sau alt mecanism de
terminare;
- transmite un semnal de ieşire părintelui; dacă acesta nu răspunde îşi transferă “copiii” către init şi
setează starea de ieşire în task_struct la valoarea EXIT_ZOMBIE;
- anulează intrările din coada de aşteptare a programatorului de procese (nu mai trebuie programat
pentru execuţie).
În acest moment, toate obiectele asociate cu procesul sunt eliberate, acesta nemaiputând fi rulat (este
în starea zombie). Singurele resurse ocupate sunt structurile thread_info şi task_struct, singurul scop al
existenţei acestora fiind acela de a oferi informaţii pentru procesul părinte.
Structurile sunt eliberate dupăce părintele preia informaţiile de ieşire, moment la care este eliberat şi
PID-ul procesului terminat.

Zombie
int main()
{
int pid;
printf("parinte? ->PID=%d PPID=%d\n",getpid(),getppid());
pid=fork();
if (pid > 0) {
while(1){}
} else {
printf("if -> fiu? ->PID=%d PPID=%d\n",getpid(),getppid());
_exit(0);
}
return 0;
}
>top –pPID_COPIL

Depanarea unui proces

Pe majoritatea sistemelor de operare pe care a fost portat, gdb nu poate detecta când un proces
realizează ooperaţie fork(). Atunci când programul este pornit, depanarea are loc exclusiv în procesul initial,
proceselecopii nefiind ataşate debugger-ului. În acest caz, singura soluţie este introducerea unor întârzieri în
execuţiaprocesului nou creat (de exemplu, prin apelul de sistem sleep()), care sa ofere programatorului
suficienttimp pentru a ataşa manual gdb-ul la respectivul proces, presupunând ca i-a aflat PID-ul în prealabil.

Pentru a ataşa debugger-ul la un proces deja existent, se folosete comanda attach, în felul urmator:
(gdb) attach PID

Aceasta metoda este destul de incomoda şi poate cauza chiar o funcţionare anormală a aplicaţiei
depanate, încazul î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, gdb permite depanarea comodă
aprogramelor care creează mai multe procese prin fork() şi vfork(). Pentru ca gdb să urmărească activitatea
proceselor create ulterior, se poate folosi comanda set follow-fork-mode, în felul urmator:
(gdb) set follow-fork-mode mode

unde mode poate lua valoarea parent, caz în care debugger-ul continua depanarea procesului parinte,
sauvaloarea child, şi atunci noul proces creat va fi depanat în continuare. Se poate observa că în această
manieradebugger-ul este ataşat la un moment dat doar la un singur proces, neputând urmări mai multe
simultan.

Cu toate acestea, gdb poate ţine evidenţa tuturor proceselor create de către programul depanat, deşi
încontinuare numai un singur proces poate fi rulat prin debugger la un moment dat. Comanda
setdetach-on-fork realizează acest lucru:
(gdb) set detach-on-fork mode

unde mode poate fi on, atunci când gdb se va ataşa unui singur proces la un moment dat
(comportamentimplicit), sau off, caz în care gdb se ataşează la toate procesele create în timpul execuţiei, şi le
suspendă peacelea care nu sunt urmărite, în funcţie de valoarea setării follow-fork-mode.
Comanda info forks afişeaza informaţii legate de toate procesele aflate sub controlul gdb la un
momentdat:
(gdb) info forks

De asemenea, comanda fork poate fi utilizată pentru a seta unul din procesele din listă drept cel activ
(careeste urmărit de debugger).
(gdb) fork fork-id

unde fork-id este identificatorul procesului, aşa cum apare în lista afişata de comandainfoforks.

Atunci când un anumit proces nu mai trebuie urmărit, el poate fi înlăturat din listă folosind
comenziledetach fork şi delete fork:
(gdb) detach fork fork-id

(gdb) delete fork fork-id

Diferenţa 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 considerăm programul următor:

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 impliciţi de depanare, vom constata ca gdb va urmări exclusiv
execuţia procesului părinte:
$ 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.

Plasăm 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/student/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 copil, şi observăm că de data aceasta celălalt breakpoint
este atins:
(gdb) set follow-fork-mode child
(gdb) run
Starting program: /home/student/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 ca ultimele două mesaje au fost inversate, faţă de cazul precedent: debugger-ul încheie
procesulcopil, apoi procesul părinte afişează mesajul de final (Everything is done!).
Encrypt1.c
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int fd1, fd2,fd3;

int r=1;
int w=0;

char* crypt(char*);
int checksum(char*);
int check;

int main()
{
char * buffer,buff;
fd1= open( "input.txt", O_RDONLY);
fd2 = open( "output1.txt", O_WRONLY);
fd3=open("temp.txt",O_RDWR);

buffer=(char*)malloc(4);
buff=(char*)malloc(4);

while(r!=0)
{
r=read(fd1, buffer, 4);
buffer = crypt(buffer);
write(fd3, buffer, 4);
write(fd2, buffer, 4);

read(fd3,buff,4);
check=checksum(buff);
w=write(fd2, &check, 4);
printf("S­au scris %d \n", w);
}
sleep(1);
}

char* crypt(char* buff1)
{
int i=0;
for(i=0;i<strlen(buff1);i++)
{
buff1[i]=buff1[i]+10;
}
return buff1;
}

int checksum(char* buff2)
{
int sum=0;
int i=0;
for(i=0;i<strlen(buff2);i++)
{
sum+=buff2[i];
}
return sum;
}

Encrypt2.c
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/signal.h>
int fd1, fd2,fd3;

int r=1;
int w=0;

char* crypt(char*);
int checksum(char*);
int check;
int pid;

int main()
{
char * buffer,buff;
fd1= open( "input.txt", O_RDONLY);
fd2 = open( "output2.txt", O_WRONLY);
fd3=open("temp.txt",O_RDWR);

buffer=(char*)malloc(4);
buff=(char*)malloc(4);
pid=fork();

if(pid==0)
{
while(1)
{
read(fd3,buff,4);
check=checksum(buff);
w=write(fd2, &check, 4);
printf("S­au scris %d \n", w);
}
}

if(pid>0)
{
while(read(fd1, buffer, 4)!=0)
{

buffer = crypt(buffer);
write(fd3, buffer, 4);
write(fd2, buffer, 4);

}
sleep(1);
kill(pid,SIGKILL);
}
}

char* crypt(char* buff1)
{
int i=0;
for(i=0;i<strlen(buff1);i++)
{
buff1[i]=buff1[i]+10;
}
return buff1;
}

int checksum(char* buff2)
{
int sum=0;
int i=0;
for(i=0;i<strlen(buff2);i++)
{
sum+=buff2[i];
}
return sum;
}

S-ar putea să vă placă și