![]() |
la nascita di un nuovo processo (fork()) |
![]() |
l'attesa di un processo figlio (wait()) |
![]() |
la trasformazione di un processo corrente (funzioni del gruppo exec()) |
![]() |
l'invio di segnali fra processi (libreria signal.h) |
![]() |
la condivisione di file a scopo di comunicazione o sincronizzazione fra processi (pipe) |
Generalita' sui processi (Unix)
Con il termine processo si
intende il programma in esecuzione con l'impegno di piu' risorse (CPU, memoria,
file system, ...) che deve condividere con gli altri processi presenti sul
medesimo calcolatore.
L'esecuzione di un programma puo' generare piu' di un
processo (vedi fork()).
Ogni processo Unix e' contraddistinto da un identificatore di processo (process ID) indicato con pid e da un identificatore del processo padre che viene indicato con ppid (parent process ID).
Il processo padre (parent process) e' il processo che genera un
altro processo, detto processo figlio (child process).
Piu'
propriamente il processo padre dovrebbe essere chiamato processo genitore
dall'inglese (parent=genitore), ma il termine padre si
adatta ugualmente bene allo scopo.
Il pid e' un numero univoco in un determinato istante, fornito dal sistema operativo e necessario ad identificare un processo in "corso" sul sistema.
Il ppid e' l'identificativo del padre del processo che si sta' esaminando. In altri termini il ppid e' il pid del processo che ha generato il processo considerato.
Un processo si puo' trovare in uno dei seguenti stati:
![]() |
Ready | E' pronto per essere eseguito. |
---|---|---|
![]() |
Running | E' in esecuzione su una CPU del sistema. |
![]() |
Sleeping | Attende un evento. |
![]() |
Swapped | Parte del processo e' stato trasferito su disco, per liberare la memoria per altri processi. |
![]() |
Terminated | Il processo e' terminato. Invio del segnale SIGCHLD al parent. |
![]() |
Zombie | Il processo ha terminato la sua esecuzione, ma il parent non ha raccolto il segnale SIGCHLD. Il processo mantiene ancora allocate delle risorse. |
La fork() restituisce al parent o un valore negativo in caso di errore
(il child process non viene generato), o un valore positivo corrispondente al
pid del child. A titolo di esempio riporto un breve spezzone di codice: In caso che venga rilevato un errore durante la creazione del nuovo processo,
il codice deve essere in grado di gestirlo correttamente. Normalmente il processo padre esegue le istruzioni del programma, fino ad
incontrare la chiamata alla fork() (istruzioni in blu). A questo punto il
processo padre genera un nuovo processo. Da questo momento i 2 processi hanno
vita indipendente e il risultato tornato dalla fork() e' differente per
ciascun processo.
Fork di un processo
Un processo, durante la sua esecuzione, puo' creare
un nuovo processo tramite la system call fork().
Il processo corrente
e' detto processo padre o parent process, mentre il nuovo processo
e' detto processo figlio o child process. Entrambi i processi
condividono lo stesso codice programma ma vengono eseguiti in concorrenza fra
loro assieme al resto dei processi elaborati sul sistema in oggetto. Normalmente
il padre e il figlio eseguono delle istruzioni differenti.
Il child process
eredita i file aperti dal parent process. Questi possono costituire un mezzo di
interazione fra i 2 processi.
Diversamente i dati (le variabili del
programma), lo stack e l'ambiente (environment) del parent process vengono
duplicati per il nuovo processo e posti in un'area di memoria a lui riservata e
non visibile dagli altri processi, parent compreso.
La fork() ritorna al child un valore sempre
nullo.
Nella figura
sottostante, ho indicato in blu le istruzioni eseguite dal programma in caso di
errore ritornato dalla system call fork().
E' normale che i 2 processi eseguano percorsi differenti
all'interno dello stesso programma (istruzioni in blu).
Attesa di un processo
Per i processi generati con una fork()
si possono creare uno dei seguenti casi:
![]() |
Il processo padre termina prima del processo figlio. In questa situazione il child process rimane orfano del padre e viene adottato dal processo init che per definizione e' il padre di tutti i processi. |
![]() |
Il processo figlio termina, ma il padre non rileva il suo
termine. Quando cio' si verifica, il processo figlio e' definito defunto oppure zombie e rimane in tale stato finche' o il padre non ha rilevato la sua terminazione, oppure fino a quando anche il padre termina; al termine del padre il processo figlio viene ereditato dal processo init che ne rileva la sua terminazione. Un processo zombie mantiene allocate le risorse fino quando non sia stato rilevato il suo stato di terminazione o dal processo padre o dal processo init. |
Nella situazione di normalita', il padre deve quindi rilevare la terminazione del figlio tramite la system call wait(). Il figlio puo' cosi' rilasciare ogni risorsa impegnata.
Legenda della numerazione posta a fianco del flusso di processo:
Quando una funzione del gruppo exec viene eseguita con successo, il processo
carica il programma o lo script indicato fra gli argomenti della funzione
chiamata e lo manda in esecuzione in sostituzione del processo attuale. Non e'
previsto nessun tipo di ritorno al vecchio processo se non nel caso che non sia
possibile avviare il nuovo processo.
Le pipe costituiscono un meccanismo di sincronizzazione fra processo
produttore e processo consumatore.
Un processo padre:
In alternativa un processo padre puo' anche:
Un processo consumatore (lettore della pipe):
Un processo produttore (scrittore della pipe):
Le funzioni per la gestione dei processi non sono definite da una libreria
specifica, ma fanno riferimento a piu' librerie standard; pertanto per il loro
utilizzo e' necessario includere
gli headers appropriati come descritto nella sinopsi di ciascuna funzione.
Es.:
Funzioni del gruppo exec
Le funzioni del gruppo exec sono in grado di
sostituire il processo corrente con un altro processo. Il pid e il
ppid rimangono invariati. Praticamente si ha una trasformazione
del processo.
Comunicazione fra processi: la pipe
Le pipe (condotte) sono dei
canali unidirezionali per la comunicazione fra processi.
Ogni processo
puo' trattare le pipe come se fossero dei file standard, con le dovute
eccezioni:
Il processo viene collegato da una pipe
generata anch'essa da popen().
In alternativa puo' essere impiegata la funzione fclose() se in
precedenza era stata chiamata la funzione fdopen().
In alternativa puo' essere impiegata la funzione fclose() se in
precedenza era stata chiamata la funzione fdopen().
#include <unistd.h>
#include <sys/wait.h>
Funzioni della libreria stdlib.h
atexit()
exit()
on_exit()
system()
Funzioni della libreria stdio.h
popen(), pclose()
![]() |
![]() |
![]() |