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()
Indice linguaggio C |
Indice librerie C |
Umberto Zappi Home Page |