Sunt prezentate cinci exemple de utilizare procese Unix.
Exemplele sunt scrise in limbajul C.

Primul exemplu ilustreaza legaturile dintre un proces
parinte si un fiu al lui creat prin fork.

Al doilea exemplu prezinta diferite aspecte legate de
utilizarea apelurilor exec.

Ultimele trei exemple rezolva trei probleme specifice in care se
folosesc combinatii de apeluri sistem fork, exec, wait, exit, system


Exemplul 1, utilizari fork.
---------------------------

1. Sursa fork1.c:
#include <stdio.h>
#include <stdlib.h>
main() {
    int p;
    p=fork();
    if (p == -1) {perror("fork imposibil!"); exit(1);}    
    if (p == 0) {
        printf("Fiu: pid=%d, ppid=%d\n", getpid(), getppid());
    } else {
        printf("Parinte: pid=%d ppid=%d\n", getpid(), getppid());
        //wait(0);
        printf("Terminat fiul\n");      
    }
}

Rulare fork1 fara wait (este comentat):

Parinte: pid=2704 ppid=2197
Terminat fiul
Fiu: pid=2705, ppid=2704

Rulare fork1 cu wait:

Parinte: pid=2715 ppid=2197
Fiu: pid=2716, ppid=2715
Terminat fiul

Cauza: mesajul de terminare a fiului este dat de catre procesul
parinte. In absenta lui wait, este posibil ca parintele sa
primeasca controlul inaintea fiului si sa afiseze mesajul inainte
ca fiul sa isi afiseze mesajul lui. Daca wait apare, atunci
parintele asteapta efectiv terminarea fiului inainte de a
afisa mesajul de terminare.


2. Sursa fork2.c :
#include <stdio.h>
#include <stdlib.h>
main() {
    int p;
    printf("Parinte: pid=%d, ppid=%d\n", getpid(), getppid());
    p=fork();
    if(p==-1){perror("fork imposibil!"); exit(1);}
    if(p==0){
        printf("Fiu: pid=%d, ppid=%d\n", getpid(), getppid());
        //exit(2);
    }        
    printf("pid=%d, ppid=%d\n", getpid(), getppid());
}

Rulare fork2 fara exit(2) (este comentat):

Parinte: pid=2743, ppid=2197
pid=2743, ppid=2197
Fiu: pid=2744, ppid=2743
pid=2744, ppid=2743

Rulare fork2 cu exit(2):

Parinte: pid=2755, ppid=2197
pid=2755, ppid=2197
Fiu: pid=2756, ppid=2755

Cauza: ultimul print din sursa apartine atat parintelui cat
si fiului. Din cauza lui exit(2), in fiu nu se mai executa
acest ultim print.

3. Sursa fork3.c:
#include <stdio.h>
#include <stdlib.h>
main() {
    int p, i;
    p=fork();
    if (p == -1) {perror("fork imposibil!"); exit(1);}    
    if (p == 0) {
        for (i=0; i<10; i++)
            printf("%d. Fiu: pid=%d, ppid=%d\n", i, getpid(), getppid());
    } else {
        for (i=0; i<10; i++)
            printf("%d. Parinte: pid=%d ppid=%d\n", i, getpid(), getppid());
    }
}

Rezultatul rularii:

0. Parinte: pid=2708 ppid=1768
1. Parinte: pid=2708 ppid=1768
2. Parinte: pid=2708 ppid=1768
0. Fiu: pid=2715, ppid=2708
3. Parinte: pid=2708 ppid=1768
1. Fiu: pid=2715, ppid=2708
4. Parinte: pid=2708 ppid=1768
2. Fiu: pid=2715, ppid=2708
5. Parinte: pid=2708 ppid=1768
3. Fiu: pid=2715, ppid=2708
6. Parinte: pid=2708 ppid=1768
4. Fiu: pid=2715, ppid=2708
7. Parinte: pid=2708 ppid=1768
5. Fiu: pid=2715, ppid=2708
8. Parinte: pid=2708 ppid=1768
6. Fiu: pid=2715, ppid=2708
9. Parinte: pid=2708 ppid=1768
7. Fiu: pid=2715, ppid=2708
8. Fiu: pid=2715, ppid=2708
9. Fiu: pid=2715, ppid=2708

Cauza: amestecarea iesirilor fiului cu ale parintelui in
executia de mai sus este doar una dintre multele posibile.
Daca se ruleaza de mai multe ori, se vor observa de fiecare
data alte (posibile) amestecari. Totul depinde de care
dintre cele doua procese obtine primul accesul.

Exemplul 2: utilizari simple execl, execlp, execv:
--------------------------------------------------

Urmatoarele trei programe, desi diferite, au acelasi efect.
Toate trei folosesc o comanda de tip exec, spre a lansa din ea:

ls -l

Cele trei surse, din care pe moment se vor ignora comentariile, sunt:
                 ----------------------------------------------
                          
Sursa execl.c:
#include<stdio.h>
#include<unistd.h>
main() {
    printf("Urmeaza rezultatul executiei comenzii ls:\n");
    execl("/bin/ls", "/bin/ls", "-l", NULL);
    //execl("/bin/ls","/bin/ls","-l","execl.c", "fork1.c", "xx", NULL);
    //execl("/bin/ls","/bin/ls","-l","*.c", NULL);
    printf("Aici nu se ajunge decat in urma unei erori exec\n");
}

Sursa execlp.c:
#include<stdio.h>
#include<unistd.h>
main() {
    printf("Urmeaza rezultatul executiei comenzii ls:\n");
    execlp("ls", "ls", "-l", NULL) == -1;
    printf("Aici nu se ajunge decat in urma unei erori exec\n");
//    if (execlp("ls","ls","-l",NULL) == -1) {
//      printf("Aici nu se ajunge decat in urma unei erori exec\n");
//      perror("Aici se impune un mesaj explicativ; sistemul raspunde");
//    }
}

Sursa execv.c:
#include<stdio.h>
#include<unistd.h>
main() {
    char* argv[3];
    argv[0] = "/bin/ls";
    argv[1] = "-l";
    argv[2] = NULL;
    printf("Urmeaza rezultatul executiei comenzii ls:\n");
    execv("/bin/ls",argv);
    printf("Aici nu se ajunge decat in urma unei erori exec\n");
}
 
Efectul oricaruia dintre programele de mai sus este:

Urmeaza rezultatul executiei comenzii ls:
total 184
-rwxr-xr-x 1 florin florin 7176 2011-03-17 16:47 a.out
-rwxrwxrwx 1 florin florin  340 2011-03-17 16:43 execl.c
-rwxrwxrwx 1 florin florin  404 2011-03-17 16:43 execlp.c
-rwxrwxrwx 1 florin florin  370 2011-03-17 16:43 execv.c
-rwxrwxrwx 1 florin florin  364 2011-03-17 15:45 fork1.c
-rw-r--r-- 1 florin florin  353 2011-03-17 16:06 fork2.c
-rw-r--r-- 1 florin florin  386 2011-03-17 16:10 fork3.c

1.
Primul program foloseste excl. De aceea comanda se specifica
cu calea completa /bin/ls. Urmeaza lista argumentelor din
linia de comanda (de aceea apare dublarea primului argument).

Al doilea foloseste exclp, deci comanda este cautata in
directoarele din PATH, de aceea se specifica doar ls.

Al treilea foloseste execv. La fel ca primul, specifica
calea absoluta. Acest program pregateste in prelabil un
tablou cu trei pointeri la stringuri in care se pun cele
doua argumente ale liniei de comanda si pointerul NULL
ce marcheaza sfarsitul tabloului. Compilatorul in mod
automat aloca spatiu de memorie pentru fiecare constanta
string. In urma atribuirilor a[0] = --- si a[1] = ---
se atribuie adresele stringurilor respective.
Daca este cazul, un astfel de tablou se poate aloca dinamic
in zona heap (prin malloc), dupa care se va initializa cu
valorile corespunzatoare prin metodele specifice limbajului C.

2.
Se poate observa, in urma rularilor, ca nu se va afisa textul
din cel de-al doilea printf, indiferent de programul lansat.
Este suficient sa se schimbe primul argument al exec, din
ls in xxxx spre exemplu, si se va obtine:

Urmeaza rezultatul executiei comenzii ls:
Aici nu se ajunge decat in urma unei erori exec

3.
In spiritul obsevatiei de la 2, din dorinta de a prezenta
programele cat mai simple, am "incalcat" o regula de aur
in programarea C:
"Sa se testeze de fiecare data rezultatul intors de o
functie C sau de un apel sistem!"
In consecinta, fiecare apel exec ar trebui sa se faca asa
cum apare in comentariile de la execlp.c:

    if (execlp("ls","ls","-l",NULL) == -1) {
        printf("Aici nu se ajunge decat in urma unei erori exec\n");
        perror("Aici se impune un mesaj explicativ; sistemul raspunde");
    }

Vezi si man exec.
Inlocuind un apel exec cu o secventa de tipul de mai sus si
inlocuind ls cu xxxx se obtine:

Aici nu se ajunge decat in urma unei erori exec
Aici se impune un mesaj explicativ; sistemul raspunde: No such file or directory

4.
In sursa execl.c apar doua apeluri execl comentate. Inlocuind
pe rand apelul execl cu unul din cele comentate, se va obtine:

Urmeaza rezultatul executiei comenzii ls:

/bin/ls: cannot access xx: No such file or directory
-rwxrwxrwx 1 florin florin 340 2011-03-17 17:39 execl.c
-rwxrwxrwx 1 florin florin 364 2011-03-17 15:45 fork1.c

Urmeaza rezultatul executiei comenzii ls:
/bin/ls: cannot access *.c: No such file or directory

In primul caz se obtine efectul lansarii comenzii:

ls -l execl.c fork1.c xx

iar fisierul xx nu exista in directorul curent.

In cazul al doilea este vorba de comanda:

ls -l *.c

care insa nu este interpretata asa cum ne-am astepta!
-----------------------------------------------------
De ce? Din cauza faptului ca specificarea *.c reprezinta
o specificare generica de fisier, dar numai shell "stie"
acest lucru si el (shell) inlocuieste aceasta specificare,
in cadrul uneia dintre etapele de tratare a liniei de comanda.
La fel stau lucrurile cu evaluarea variabilelor de mediu,
${---}, inlocuirea dintre apostroafele inverse ` --- `,
redirectarea I/O standard etc.
Aceste procesari, specifice shell, NU sunt tratate de catre exec
Daca este necesar, aceste procesari trebuie facute
in programul C inainte de apelul exec!
In schimb, apelurile system le trateaza: testati system("ls -l *.c")


Exemplul 3: Cate perechi de numere nenule au suma numar par?
-----------------------------------------------------

Problema este trivial de simpla, insa potrivita pentru a
exemplifica utilizarea fork, wait si exit.

Enuntul problemei: Se dau la linia de comanda n perechi de
numere intregi. Programul va crea n procese fii, fiecare
primind doua argumente consecutive din linia de comanda.
Oricare dintre fii intoarce codul de retur:
    0 daca perechea are suma para,
    1 daca suma este impara,
    2 daca unul dintre argumente este nul sau nenumeric.
Parintele asteapta terminarea fiilor si va afisa rezultatul.

Sursa paritate.c este:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
main(int argc, char* argv[]) {
    int pare = 0, impare = 0, nenum = 0, i, n1, n2;
    for (i = 1; i < argc-1; i += 2) {
        if (fork() == 0) {
            n1 = atoi(argv[i]);   // atoi intoarce 0
            n2 = atoi(argv[i+1]); // si la nenumeric
            if (n1 == 0 || n2 == 0) exit(2);
            if ((n1 + n2) % 2 == 0) exit(0);
            else                    exit(1);
            // Aici se termina fiecare fiu
        }
    }
    // Parintele asteapta terminarile fiilor
    for (i = 1; i < argc-1; i += 2) {
        wait(&n1);
        switch (WEXITSTATUS(n1)) {
            case 0: pare++;break;
            case 1: impare++;break;
            default: nenum++;
        }
    }    
    printf("Pare %d, impare %d, nenumerice %d\n",pare, impare, nenum);
}

La terminare, fiecare fiu intoarce codul de retur corespunzator.
Parintele primeste in intregul n1 o configuratie de biti
intre care se afla si valoarea codului de retur.
Functia (de fapt macroul) WEXITSTATUS extrage valoarea codului.


Exemplul 4: Un program care compileaza si ruleaza alt program.
--------------------------------------------------------------

Exemplul care urmeaza are acelasi efect ca si scriptul sh:
#!/bin/sh
if gcc -o ceva $1
then ./ceva
else echo "erori de compilare"
fi

Noi nu il vom implementa in sh, ci vom folosi un program C.

Sursa compilerun.c a acestuia este:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include <sys/wait.h>
main(int argc, char* argv[]) {
    char com[200];
    strcpy(com, "gcc -o ceva "); // fabricat comanda
    strcat(com, argv[1]);
    if (WEXITSTATUS(system(com)) == 0)
        execl("./ceva","./ceva", NULL);
    printf("Erori de compilare\n");
}

Compilarea lui se face

gcc -o comprun compilerun.c

Executia se face, de exemplu, prin

./comprun fork1.c

Ca efect, daca compilarea sursei argument (fork1.c) este corecta,
atunci compilatorul gcc creeaza fisierul ceva si intoarce
cod de retur o, dupa ceva este lansat prin execl.
Daca esueaza compilarea, se va tipari doar mesajul.