next up previous contents index return to home!
Next: Esercizio Up: Processi Previous: Identificatori di processo   Indice   Indice analitico

Creazione di nuovi processi

L'unico modo per creare un nuovo processo in UNIX è tramite la primitiva fork(). Questa primitiva crea un processo figlio (child) che ha lo stesso identico codice del processo che invoca la primitiva, che viene perciò chiamato anche processo padre (father). Subito dopo l'esecuzione della primitiva fork(), dunque, esistono due processi, ognuno con il suo spazio di memoria privato. Sia padre che figlio (in caso di corretta terminazione della syscall) continueranno la loro esecuzione da dopo la fork(): tale syscall verrà quindi chiamata una volta e ritornerà due volte (una nel processo padre ed una nel processo figlio).

Il processo figlio è una copia quasi identica del processo padre. In particolare, la fork() esegue le seguenti operazioni:

  1. crea un nuovo spazio di memoria privato destinato a contenere il processo figlio;

  2. crea un nuovo descrittore di processo all'interno del nucleo del sistem operativo;

  3. assegna un nuovo PID al processo figlio (infatti, come detto, il PID deve identificare univocamente un nuovo processo);

  4. esegue una copia quasi fedele della memoria del padre nella memoria del figlio, sia per quanto riguarda la parte dei dati, che per quanto riguarda il codice;

  5. a seconda della politica di schedulazione del sistema operativo, uno dei due processi, il padre o il figlio, andrà in esecuzione, mentre l'altro resterà in attesa di essere eseguito dal processore; nei sistemi con multiprocessori, entrambi i processi possono andare in esecuzione contemporaneamente su due processori diversi;

  6. ritorna due volte: nel processo padre, la primitiva fork() ritorna il valore del PID assegnato al processo figlio; nel processo figlio, ritorna $0$.

Il punto 4 è molto importante e merita di essere approfondito meglio con un esempio. Se il processo padre ha dichiarato una variabile intera a il cui valore subito prima di eseguire la fork() e' di 5, subito dopo la fork() sia il padre che il figlio possiedono una variabile a = 5. Però, da questo momento in poi, le due variabili sono completamente distinte, una sta nello spazio privato del processo padre e l'altra sta nello spazio privato del processo figlio, e avranno vita diversa. Quindi, se successivamente il processo padre esegue l'istruzione a++, solo la sua variabile a verrà incrementata, mentre la variabile a che sta nel processo figlio conserverà il valore originale.

I due processi possono essere distinti consultando il valore di ritorno della fork(): il padre otterrà il PID del processo figlio, mentre il figlio otterrà 0. Un utilizzo della fork() può quindi essere il seguente:

Esempio

Prima della fork() esiste un solo processo, che dichiara 3 variabili, res, a e b. Subito dopo la fork() esistono 2 processi, ognuno con il proprio spazio di memoria privato. Il processo padre possiede ancora 3 variabili, il cui valore è res = pid del figlio, a = 1 e b = 2. Il processo figlio possiede anch'egli le stesse 3 variabili, con valori res = 0, a = 1 e b = 2.

La parte di codice indicata con 1: viene eseguita de entrambi i processi: quindi, sia il padre che il figlio incrementano le loro rispettive variabili a e decrementano le loro variabili b, che quindi continuano ad avere gli stessi valori.

La parte di codice indicata con 2: viene eseguita soltanto ed esclusivamente dal processo figlio: infatti, soltanto nel processo figlio la variabile res ha il valore 0. Quindi, il processo figlio incrementa la sua variabile a che assume quindi il valore 3, mentre la variabile a del padre rimane al valore 2.

La parte di codice indicata con 3: viene eseguita soltanto ed esclusivamente dal processo padre, e quindi la sua variabile b assumerà il valore 5, mentre la variabile b del figlio resterà al valore 1.

Infine, la parte di codice indicata con 4: potrebbe, in generale, essere eseguita da entrambi i processi, a meno che uno dei due processi non decida di terminare prima nel suo ramo dell'if. Ad esempio, potrebbe darsi che il processo figlio invochi la primitiva exit nella parte 1:, e quindi non raggiungerà mai la parte 4:.

Come si vede, una soluzione di questo tipo non è però molto pratica, in quanto obbliga a scrivere il codice del processo figlio e quello del processo padre nei due blocchi then e else di un'istruzione if. Una soluzione per rendere il codice più leggibile può essere la seguente:

Esempio

In questo caso, il corpo del figlio è stato spostato in una funzione separata, il maniera da rendere meno confuso il codice. Naturalmente, ci sono decine di modi di strutturare un programma, la fantasia è il solo limite!



Subsections
next up previous contents index
Next: Esercizio Up: Processi Previous: Identificatori di processo   Indice   Indice analitico
Giuseppe Lipari 2002-10-27