Introduzione ai sistemi operativi con UNIX
Prec Succ

6. La comunicazione tra i processi

  1. Problema della variabile condivisa
  2. Il problema del produttore-consumatore
  3. Riepilogo sull'uso dei semafori
  4. exit, _exit
  5. pipe, popen
  6. mkfifo
  7. kill, signal

In questa sezione, per evitare di dover introdurre troppe cose in una volta, studieremo dapprima solo teoricamente i problemi di interferenza e comunicazione tra i processi. I programmi C di esempio forniti dapprima saranno incompleti e serviranno solo a scopo illustrativo e quindi non saranno inizialmente adatti per una reale sperimentazione. Per quelli come me, che hanno uno spirito pratico e vogliono sempre vedere soluzioni reali ed eseguibili, vi assicuro che modificheremo e completeremo ciascuno di questi programmi in esempi completi in un secondo momento, non appena avremo studiato i metodi di comunicazione tra i processi messi a disposizione da UNIX.

Premettiamo alcune definizioni riguardanti la terminologia: processi concorrenti, indipendenti, interagenti.

Due processi si dicono concorrenti se coesistono all'interno del sistema di elaborazione e la loro esecuzione si sovrappone nel tempo con una tecnica di overlapping o interleaving. Nel caso dell'overlapping c'è un intervallo di tempo in cui i due processi sono in esecuzione contemporaneamente e ci sono più CPU. Nell'interleaving c'è una sola CPU e l'esecuzione si alterna: un solo processo alla volta è in esecuzione.

Possiamo rappresentare gli intervalli di tempo in cui è in esecuzione un determinato processo tramite segmenti su un asse dei tempi, usando ad esempio un colore o una etichetta diversa per ogni processo.

La definizione rigorosa di processi concorrenti, che va bene sia nel caso ci sia una sola CPU sia nel caso ci siano molte CPU, è la seguente: due processi si dicono concorrenti se e solo se la prima operazione di uno inizia prima dell'ultima operazione dell'altro e viceversa (cioè è importante che questa proprietà valga anche cambiando l'ordine dei due processi).

diagrammi temporali dei processi
alcuni esempi di diagrammi temporali per due soli processi

I processi concorrenti possono a loro volta essere divisi in due categorie: processi indipendenti e processi interagenti.

I processi concorrenti indipendenti non si influenzano durante la loro esecuzione. Ad es. sono processi di utenti diversi che non hanno alcuno stato (variabili globali) condiviso, ma condividono solo le risorse del sistema di calcolo. È vero che il fatto di condividere le risorse del sistema di calcolo produce un problema di interazione, ma questo è risolto dal sistema operativo, e non dai processi il quale ognuno ha l'impressione di avere il sistema di calcolo tutto per sé.

I processi concorrenti indipendenti hanno sempre una esecuzione deterministica e riproducibile (il risultato dipende solo dai dati in ingresso) e quindi non pongono nessun problema di interazione al programmatore, semmai lo pongono al sistema operativo.

In in sistema multiutente come UNIX possiamo affermare che in media il caso meno problematico, quello dei processi indipendenti tra loro, è di gran lunga quello più frequente (per fortuna!): generalmente programmi diversi danno luogo a processi indipendenti, anche se eseguono in modo concorrente. Se ad es. un utente sta giocando a pacman(1), un altro sta scrivendo un testo con vi(1), un altro sta compilando un programma C con gcc(1), questi sono tutti processi indipendenti, anche se utilizzano, tramite chiamate al sistema operativo, le stesse risorse di calcolo, compresa la CPU e la memoria, perchè non devono prendere alcuna iniziativa, non devono usare alcun sistema di sincronizzazione per regolare l'accesso a queste risorse. Ognuno opera su proprie variabili e non condivide esplicitamente alcuna risorsa con l'altro. Del resto si tratta di programmi molto diversi, con scopi e funzioni differenti.

I processi concorrenti interagenti, l'altra categoria che dà luogo al maggior numero di problemi, invece competono esplicitamente per risorse che possono essere usate solo da un processo alla volta, o come si dice, in modo mutuamente esclusivo o hanno uno stato condiviso, ossia delle variabili comuni (shared memory), che pure sono delle "risorse". Un esempio tipico è quello della comunicazione tra processi: un'applicazione viene scomposta in più processi che eseguono concorrentemente e devono comunicare tra loro per portare a termine la computazione.

L'esecuzione dei processi concorrenti interagenti che non prendano particolari misure di sincronizzazione è non deterministica: il risultato dell'esecuzione dipende dalla loro velocità relativa (da chi va a modificare per primo una variabile), dalla loro sequenza di esecuzione e questo fa sì che l'esecuzione dei processi non sia predicibile, perchè non è detto che si verifichi la stessa velocità relativa.

I sistemi time-sharing ad esempio hanno degli schedulatori che, nel decidere quale (o quali se ci sono più CPU) tra i processi pronti mandare in esecuzione, cercano di privilegiare le applicazioni interattive, per ridurre i tempi di risposta per gli utenti e dare a ciascun utente l'impressione di avere un sistema di calcolo molto potente a sua completa disposizione (almeno finchè il sistema non è sovraccarico). Poichè i tempi di interazione degli utenti non sono predicibili a priori, i diagrammi temporali di esecuzione dei processi pure non sono predicibili a priori.

Questo fatto che la esecuzione è non riproducibile, che il risultato non è sempre lo stesso per gli stessi dati di ingresso, è un grave problema dovuto naturalmente all'interazione, infatti non si può verificare per processi indipendenti. È l'interazione che dà luogo ad errori nel risultato complessivo della computazione, che possono essere evitati solo usando tecniche di sincronizzazione dei processi. Occorre fornire strumenti di programmazione che garantiscano che qualunque sia la velocità relativa dei processi il risultato sia comunque corretto e il sistema operativo mette a disposizione del programmatore questi strumenti.

Esistono due forme di interazione tra i processi concorrenti interagenti. Una è la competizione per l'uso di risorse comuni (ad es. risorse fisiche come la memoria o la stampante o risorse logiche, come un buffer in memoria) che non possono essere usate contemporaneamente. I problemi creati dalla competizione non sono voluti dai processi: ogni processo vorrebbe la risorsa per sé e se fosse l'unico ad usarla il problema non si verificherebbe. L'altra forma di interazione è invece voluta dai processi e si tratta della cooperazione nell'eseguire un'attività comune mediante scambio di informazioni.


Prec Indice Succ
execl, execve Problema della variabile condivisa