Abbiamo già visto cos’è un processo: un’entità dinamica in memoria che rappresenta un programma in esecuzione. Essi, però, non sono solo una roba che usa il sistema operativo per far girare i programmi, ma sono un qualcosa controllabile dal programmatore. Infatti è possibile creare arbitrariamente dei processi, cambiarne lo stato, e addirittura mettere in comunicazione l’uno con l’altro. È così possibile creare meccanismi di sincronizzazione, di programmazione concorrente e di InterProcess Communication (IPC). In questo articolo vedremo in che modo è possibile manipolare i processi dei sistemi operativi Unix-like.
Le system call
Come abbiamo già detto più volte, i processi sono una roba interna dei sistemi operativi. Questo vuol dire che ciò che succede in Windows può essere nettamente diverso rispetto a quello che invece accade in sistemi Unix-like. In tutti i casi, però, i sistemi operativi tengono sotto stretto controllo tutto quello che accade, per evitare, ad esempio, di andare contro il sistema dei permessi. Per questo motivo esiste uno strato intermediario, rappresentato dalle system call (o syscall). Si tratta di un’API, ossia un set di funzioni, attraverso cui dobbiamo interagire per fare qualsiasi tipo di richiesta.
Creazione di un processo Unix
La syscall fork fa parte degli standard POSIX e Single UNIX Specification e ci permette di creare un nuovo processo che è copia di quello corrente. In pratica, dopo l’invocazione di questa syscall, ci saranno due processi, detti padre e figlio, che eseguiranno lo stesso codice a partire dall’istruzione successiva alla fork. L’operazione è di tipo copy-on-write per questioni di efficienza, ma per semplicità diciamo che ognuno possiede un proprio spazio di indirizzamento e un proprio PID (per essere più precisi, il sistema associa ad ogni processo un PCB differente). Il PID, che sta per Process IDentifier, è un identificativo univoco e quindi può essere utilizzato in un programma per distinguere i processi.
fork() ed exec()
Vedremo ora come creare un processo in un programma C. La best practice è non usare le syscall direttamente, perciò glibc, la libreria standard del C in GNU, mette a disposizione l’omonima funzione fork(), che annega la syscall nella sua implementazione. Questa funzione ha un valore di ritorno, un numero intero. Nel processo figlio vale 0, mentre nel processo padre è maggiore di 0 e corrisponde al PID del figlio. Invece, se la fork non va a buon fine (ad esempio perché la memoria allocabile non è sufficiente), ritorna -1.
int f = fork(); if (f > 0) { // Eseguito dal padre (f > 0) // f = pid del figlio // ... } else if (f == 0) { // Eseguito dal figlio (f = 0) // ... } else { // Errore: f = -1 // ... }
A seguito della prima istruzione avremo due processi. Entrambi eseguiranno l’if, ma il valore di ritorno della fork è diverso ed è utilizzato per distinguere tra padre e figlio. Quindi il padre entrerà nel blocco f > 0, mentre il figlio in quello f == 0. Questo costrutto è tipico e possono succedere varie cose. Una possibilità è invocare un’altra importante funzione della glibc, che si chiama exec(), che annega al suo interno l’omonima syscall. Essa sostituisce l’immagine del processo con quella di un’altro eseguibile. Detto in termini un po’ superficiali, ci permette di eseguire un altro programma presente nel filesystem.
Alberi di processi e esecuzione concorrente
Se la fork() genera un figlio, eseguendo più fork possiamo generare interi alberi “genealogici” di processi.

Chiaramente i processi vengono eseguiti in maniera concorrente, perciò non si tratta più un unico flusso sincrono, per cui “sdoppiare” i processi potrebbe essere un modo per eseguire dei task indipendenti tra loro in parallelo. Inoltre i processi possono comunicare tra loro attraverso un sistema di segnali, che può essere utilizzato per creare meccanismi di sincronizzazione, come i semafori. È così possibile utilizzare eseguire catene di elaborazione ordinate. Tutto molto bello, ma badate che oggi usare i processi con questo fine potrebbe risultare arcaico: i moderni sistemi elaborazione utilizzano i thread, che sono molto più snelli, efficienti e flessibili.