La creazione di un processo richiede alcune operazioni da parte del Sistema Operativo:
Un processo è sempre creato da un altro processo tramite un’opportuna chiamata a sistema. Fa eccezione init
(pid = 1) che viene creato al momento del boot.
Il processo creante e detto parent (genitore), mentre il processo creato child (figlio). Si genera una struttura di “parentela” ad albero.
Cosa accade dopo la creazione?
Ci sono due possibilità:
$ sleep 5 ( ... 5 secondi di attesa ... ) $
La shell è genitore del nuovo processo sleep 5
NOTA: il terminale e associato al nuovo programma: ctrl-c
, ad esempio, termina il programma.
Esempio: Esecuzione in background di un programma da shell:
$ sleep 5 & [1] 20 $ ps PID TTY TIME CMD 11 pts/0 00:00:00 bash 20 pts/0 00:00:00 sleep 21 pts/0 00:00:00 ps $
In questo caso i due processi procedono in modo concorrente. Tramite ps
possiamo osservare i processi in esecuzione.
Per visualizzare il PID del genitore basta passare gli opportuni parametri al programma ps (PPID significa Parent process ID):
$ sleep 5 & [1] 23 $ ps -o pid,ppid,comm PID PPID COMMAND 11 10 bash 23 11 sleep 24 11 ps $
Si osservi che sleep
e ps
sono entrambi figli di bash
.
Due possibilità:
Questo è il comportamento standard ma ovviamente è possibile anche l’altra modalità in entrambi i sistemi.
La chiamata a sistema fork
permette di creare un processo duplicato del processo genitore.
La fork
crea un nuovo processo che
Come fanno i processi a differenziarsi? Si utilizza il valore di ritorno:
pid = fork();
if ( pid < 0 )
perror("fork error"); // stampa la descrizione dell'errore
else if ( pid == 0 ) {
// codice figlio
} else {
// codice genitore, (pid > 0)
}
// codice del genitore e del figlio: da usare con cautela!
int main() { pid_t pid; printf("Prima della fork. pid = %d, pid del genitore = %d\n",getpid(), getppid()); pid = fork(); if ( pid < 0 ) perror("fork error"); // stampa la descrizione dell'errore else if (pid == 0) { // figlio printf("[Figlio] pid = %d, pid del genitore = %d\n",getpid(), getppid()); } else { // genitore printf("[Genitore] pid = %d, pid del mio genitore = %d\n",getpid(), getppid()); printf("[Genitore] Mio figlio ha pid = %d\n",pid); sleep(1); // attende 1 secondo } // entrambi i processi printf("PID %d termina.\n", getpid()); }
int main() { pid_t pid; int i; pid = fork(); if ( pid < 0 ) perror("fork error"); // stampa la descrizione dell'errore else if (pid == 0) { while(1) { for (i=0;i<10000;i++) {} // riduce il numero di printf printf("Figlio: pid = %d, pid del genitore = %d\n",getpid(), getppid()); fflush(stdout); } } else { while(1) { for (i=0;i<10000;i++) {} // riduce il numero di printf printf("genitore: pid = %d, pid genitore = %d\n",getpid(), getppid()); fflush(stdout); } } }
Quando fallisce una fork
? Quando non è possibile creare un processo e tipicamente questo accade quando non c’è memoria per il processo o per il kernel. Ecco un piccolo test.
NOTA BENE. ulimit
e, in Linux, cgroups limitano il numero massimo di risorse allocabili evitando che si blocchi tutto il sistema (provare ulimit -u
)!
// fork bomb, usare a proprio rischio e pericolo! int main() { while(1) if (fork() < 0) perror ("errore fork"); }
Se metto una sleep
subito prima della printf nel figlio lo rendo orfano perché termina il genitore prima di lui: viene adottato da init (genitore di tutti i processi di sistema) o da upstart nei moderni sistemi Linux:
// figlio
sleep(5);
printf("[Figlio] pid = %d, pid genitore = %d\n",getpid(), getppid());
NOTA Un processo orfano non viene più terminato da ctrl-c
(utilizzare ps -o pid,ppid,comm
per vedere che il processo è ancora attivo, eventualmente con l’opzione -e
).
Gli zombie sono processi terminati ma in attesa che il genitore rilevi il loro stato di terminazione. Per osservare la generazione di un processo zombie ci basta porre la sleep prima della printf del processo genitore:
// genitore
sleep(5);
printf("[genitore] pid = %d, pid genitore = %d\n",getpid(),getppid());
Testare la presenza di zombie con ps -e
da un altro terminale oppure lanciando il programma in background tramite &
:
$ fork & ... ps -e | grep fork 5118 pts/3 00:00:00 fork 5119 pts/3 00:00:00 fork <defunct>