cerca
Sistemi Operativi - Comunicazione tra processi
modifica cronologia stampa login logout

Wiki

UniCrema


Materie per semestre

Materie per anno

Materie per laurea


Help

Sistemi Operativi - Comunicazione tra processi

Torna alla pagina di Sistemi Operativi


 :: Appunti 2.0 ::

Comunicazione tra processi

Processi cooperanti

Un processo che non condivide dati con altri processi è detto indipendente, e la sua evoluzione non può influenzare o essere influenzata da alcuno. Da notare che ciò non implica che non abbiano risorse condivise (sarebbe impensabile, dal momento che almeno il processore deve essere accessibile a tutti), l'importante è che nessuno acceda alle informazioni dell'altro. I processi cooperanti sono invece l'esatto contrario e, pur svolgendo ognuno un compito ben definito, le loro computazioni concorrono all'adempimento di uno scopo applicativo congiunto. Stessa distinzione può essere operata sui thread.

Alcuni dei vantaggi della cooperazione sono:

  • condivisione delle informazioni, poiché molti utenti potrebbero essere interessati a stessi dati e/o risorse
  • parallelizzazione, che sfrutta in modo nativo l'eventuale disponibilità di più processori, velocizzando la computazione
  • modularità, che consente di progettare l'applicazione un aspetto per volta, per poi mettere insieme in modo opportuno i moduli che implementano tali aspetti
  • scalabilità, che permette di ampliare le capacità operative dell'applicazione aggiungendo semplicemente nuove istanze dello stesso processo (ad esempio utile per un server web)
  • specializzazione, grazie alla quale è possibile assegnare la realizzazione dei processi che si occupano dei vari aspetti (ad esempio controllo, acquisizione, interfaccia, ecc) ad esperti specializzati in quell'ambito di programmazione
  • qualità del progetto e della realizzazione, perché sviluppando ogni aspetto in modo separato posso raggiungere qualità elevata.

Perché tutto ciò sia possibile occorre che tali processi possano scambiarsi informazioni e coordinarsi, o meglio, che possano comunicare e sincronizzarsi. I processi indipendenti si limitano invece alla sola sincronizzazione, per regolamentare l'accesso alle risorse condivise.

Comunicazione

Il processo di comunicazione comporta la descrizione di politiche e meccanismi che permettano ai processi di scambiarsi informazioni per operare in modo cooperativo. Anzitutto vanno definite le entità coinvolte, quindi il processo mittente (P) produttore dell'informazione, il processo ricevente (Q) utilizzatore della stessa, e il canale di comunicazione (mono o bidirezionale) attraverso cui farla fluire. Per decidere poi quale tipo di comunicazione è meglio adottare in una specifica situazione, bisognerà tener conto di alcune caratteristiche. Di seguito riportiamo le più importanti, alcune delle quali dovrebbero sempre essere garantite:

  • quantità di informazioni da trasmettere. Se avessi ad esempio grosse quantità di informazioni, potrebbe essere poco conveniente copiarle in memoria centrale (sprecherei molto tempo)
  • velocità di esecuzione
  • scalabilità, descritta prima. Bisognerebbe infatti garantire in modo semplice la possibilità di aumentare i processi che concorrono alla comunicazione, cercando di non far lievitare la complessità totale
  • semplicità di uso nelle applicazioni
  • omogeneità delle comunicazioni, dato che non avrebbe senso adottare n modi diversi di effettuare una comunicazione, col conseguente incremento delle complessità di gestione e quindi di commettere errori
  • integrazione nel linguaggio di programmazione, che garantisce la portabilità. E' sempre consigliabile utilizzare le tecniche di comunicazione definite nel linguaggio di programmazione utilizzato, o sarei costretto ad usare librerie che potrebbero cambiare di sistema in sistema
  • affidabilità
  • sicurezza
  • protezione

Prima di elencarne le principali implementazioni, distinguiamo le due modalità di realizzazione:

  • Comunicazione diretta, in cui ogni processo che voglia comunicare deve conoscere esplicitamente il nome del destinatario o del mittente della comunicazione. Notare come tra ogni coppia di processi possa sussistere un'unica connessione, e come si presupponga che P e Q siano entrambi vivi e attivi per tutta la durata della comunicazione (il che rappresenta spesso un grosso limite)
  • Comunicazione indiretta, in cui mittente e ricevente non si conoscono e la comunicazione avviene su punti noti a entrambi (i canali) sfruttando una struttura dati passiva per contenere l'informazione. Se il problema della modalità diretta era che se una delle entità in gioco veniva a mancare durante la comunicazione nessun altro poteva accedere all'informazione, nella modalità indiretta (essendo il canale in comune) ci sarebbe sempre qualche processo in grado di raccogliere l'informazione.

Implementazioni

Con memoria condivisa

E' una modalità di comunicazione diretta e può essere realizzata attraverso l'uso di variabili globali o buffer.

Nella condivisione di variabili globali ho due processi con una porzione del loro spazio di indirizzamento (le variabili globali, appunto) che si sovrappone. Pur essendo un sistema molto rapido, avere porzioni di memoria in comune implica dei problemi, come la possibilità di compiere operazioni inconsistenti. Se ad esempio un processo richiede la lettura di un'informazione in corso di modifica dall'altro processo, leggerebbe qualcosa di obsoleto e dunque errato. La situazione è però facilmente risolvibile attuando meccanismi di sincronizzazione per l'accesso in mutua esclusione all'area dati comune per le operazioni non compatibili (come ad esempio lettura e scrittura contemporanea). Inoltre sono in qualche modo tutelato dal fatto che essendo i due processi cooperanti non hanno alcun interesse a danneggiare l'altro, dal momento che indirettamente danneggerebbero sé stessi. Ho due meccanismi per realizzarla.
Il primo è fare in modo che l' area comune sia copiata dal sistema operativo, che crea l'illusione della condivisione spostando l'informazione in due tempi: 1. la copia dall'area condivisa del processo mittente al proprio spazio di indirizzamento (in una porzione di memoria grande abbastanza) 2. da qui la copia nell'area condivisa del ricevente. Ricordiamo che il sistema operativo può penetrare ovunque, quindi non ha alcun problema ad accedere agli spazi di indirizzamento riservati dei processi. Il limite di questo meccanismo è proprio che l'operazione di copiatura si fa in due passaggi, il che su grandi porzioni di memoria rallenterebbe sensibilmente il sistema; soprattutto se si considera il fatto che anche se un processo modifica solo una minima parte dell'area condivisa, il sistema operativo non ne conosce l'entità e quindi trasferisce tutto anche se non sarebbe necessario.
Un secondo meccanismo è la realizzazione con area comune fisicamente condivisa, in cui il sistema operativo garantisce che l'area comune appartenga contemporaneamente agli spazi di indirizzamento di entrambi i processi. Lo spazio rimane comunque logicamente separato e garantito protetto dal sistema operativo, anche se alcune porzioni sono residenti fisicamente negli stessi indirizzi. E' piuttosto semplice da realizzare e supera tutti i limiti della soluzione precedente, bisogna solo fare in modo che non avvengano operazioni inconsistenti (basta utilizzare politiche di sincronizzazione).

Nella condivisione di buffer ciò che cambia rispetto a prima è l'utilizzo di un buffer. La comunicazione rimane diretta, ma il processo mittente scriverà stavolta le sue informazioni all'interno del buffer dal quale poi il processo ricevente andrà a recuperarle. Anche in questo caso con la sincronizzazione è possibile imporre gli accessi in mutua esclusione così da garantire la consistenza.
I meccanismi con cui realizzarli sono del tutto simili a quelli precedenti, ovvero buffer con copiatura gestita dal sistema operativo e buffer in memoria fisicamente condivisa.
Notare infine come i buffer siano significativamente più piccoli delle porzioni di memoria centrale condivisa, quindi le operazioni di copiatura sono molto più veloci.

Con scambio di messaggi

La comunicazione con scambio di messaggi è diretta e prevede che l'informazione viaggi incapsulata all'interno di messaggi. Oltre ad essa, i messaggi contengono l'identità del processo mittente e ricevente, ed eventuali altre informazioni relative alla gestione dello scambio.
I messaggi vengono memorizzati in buffer che il sistema operativo può assegnare esplicitamente ad ogni coppia di processi, o predisporne un certo numero di uso generale che mette a disposizione di chiunque ne abbia bisogno. Tale quantità può essere:

  • illimitata: il processo mittente appena possiede un messaggio lo deposita immediatamente nel buffer. Non è mai bloccante;
  • limitata: il processo mittente potrebbe aspettare prima di depositare il messaggio nel caso in cui non ci siano buffer liberi. Risulta quindi bloccante se manca spazio disponibile;
  • nulla: il processo mittente non può depositare alcun messaggio dato che non ci sono buffer disponibili in cui deporlo. E' sempre bloccante. Da notare che in questo scenario il processo ricevente non troverà il messaggio, ma il mittente in attesa di deporlo; ciò permetterà l'avvio di una comunicazione diretta

Le funzioni messe a disposizione per la gestione dei messaggi sono quelle per l' invio e la ricezione, eventualmente condizionali:

  • Invio: send(proc_ricevente, messaggio).
    Deposita il messaggio in un buffer libero. La funzione è bloccante, ovvero blocca il processo mittente nel caso in cui non ci fosse spazio disponibile. Una volta liberato un buffer, la funzione completa la comunicazione con il destinatario e sblocca il mittente.
  • Invio condizionale: cond_send(proc_ricevente, messaggio): error status.
    A differenza della funzione di invio precedente, non è bloccante. Se al momento di depositare il messaggio non è presente alcun buffer libero, ritornerà un messaggio di errore e il messaggio non sarà più depositato. Sarà responsabilità del mittente decidere se rimandarlo o meno.
  • Ricezione: receive(proc_mittente, messaggio).
    Riceve il messaggio presente nel buffer. E' bloccante, ovvero blocca il destinatario fino a quando non c'è un messaggio nel buffer da leggere.
  • Ricezione condizionale: con_receive(proc_mittente, messaggio): error status.
    Il processo ricevente preleverà il messaggio dal buffer; se non ci sono messaggi ritornerà una condizione di errore, senza bloccare il destinatario.

La comunicazione tramite buffer in generale è asincrona, ovvero il mittente può spedire il messaggio in qualsiasi momento della computazione senza preoccuparsi se c'è qualche ricevente in grado di raccoglierlo. In una comunicazione sincrona lo scambio delle informazioni avviene invece solo quando entrambi gli interlocutori sono pronti. Si può ottenere utilizzando un buffer di dimensione nulla, così che tutte le operazioni di scrittura diventino bloccanti, obbligando di fatto i processi a scrivere e leggere nello stesso momento. E' molto interessante poi osservare che tale politica può essere facilmente adottata per modalità di comunicazione diretta o indiretta. Nel primo caso si parla di comunicazione simmetrica e prevede ovviamente che mittente e destinatario siano sempre univocamente identificati. Nel secondo caso otteniamo invece una comunicazione asimmetrica, in cui l'accesso ad un buffer in scrittura non è limitato ad un processo solo, ma ad un gruppo di processi che possono a loro volta decidere di far leggere il loro messaggio a tutti quelli che ascoltano o solo ad uno di essi.

Dal momento che la sincronizzazione per l'accesso ai messaggi viene gestita implicitamente dal sistema operativo, i buffer vengono implementati direttamente all'interno del suo spazio di indirizzamento. In questo modo viene risolto anche il problema dell'identificazione dei processi che inviano informazione, dato che è sufficiente vedere qual è il processo attivo (se ha effettuato una richiesta è evidentemente nello stato di running). I messaggi vengono normalmente smaltiti in modalità FIFO, ma potrebbero essere previste politiche di altro tipo (ad esempio con priorità, deadline, ecc).

Comunicazione con mailbox

La comunicazione con mailbox è un sistema indiretto, in cui non c'è conoscenza esplicita tra i processi che comunicano. Se nella comunicazione asimmetrica vista in precedenza non era necessaria la conoscenza esplicita dei processi, ma era sufficiente l'identificativo dei gruppi, questo sistema è invece completamente anonimo. Il messaggio viene depositato in una mailbox (detta anche porta), una struttura dati presente nel sistema operativo caratterizzata da un nome cui si farà riferimento per accedere ai messaggi in essa contenuta. Questi contengono, oltre all'informazione da trasmettere, anche l'identità del processo mittente, il nome della mailbox destinataria ed eventuali altre informazioni di supporto alla gestione dei messaggi nella mailbox (ad esempio se vanno tolti messaggi con deadline scaduta o come ordinare i messaggi). Va ricordato che non stiamo assegnando i messaggi ad una coppia di processi, ma li stiamo inserendo in una struttura accessibile (teoricamente) da tutti; eventuali restrizioni potranno essere applicate con delle politiche di accesso stabilite dal sistema operativo.
In generale la mailbox non è proprietà del processo, ma del sistema operativo (ricordiamo che è nel suo spazio di indirizzamento). Esistono però delle procedure per attribuirle ad un processo proprietario, al quale il sistema può dare il diritto esclusivo di ricezione. In questo caso, quando il processo proprietario termina, la mailbox viene deallocata con esso.

Similmente al buffer, la mailbox può avere una capienza limitata illimitata o nulla, indipendentemente dal numero di processi che la utilizzano e con le stesse conseguenze viste prima. Anche le funzioni sono simili:

  • Invio: send(M, messaggio)
    Deposita il messaggio nella mailbox indicata da parametro.
  • Invio condizionale: cond_send(M, messaggio): error status
  • Ricezione: receive(M, messaggio)
  • Ricezione condizionale: con_receive(M, messaggio): error status

e in più abbiamo

  • create(M)
    Crea la mailbox con M nome simbolico associato
  • delete(M)
    Cancella la mailbox indicata

Come per i buffer, le politiche di sincronizzazione delle mailbox dipendono dalla loro capacità. Se è illimitata ho una comunicazione asincrona, ovvero il mittente depone il suo messaggio indipendentemente dallo stato di computazione del processo ricevente. Se invece è nulla quella che ottengo è un comunicazione sincrona. Ho infatti un processo mittente che non trova spazio per deporre il suo messaggio, ed è dunque obbligato a mettersi in attesa. Quando un processo Q invocherà una receive(), lo troverà ancora in quello stato, e a questo punto i due processi cominceranno a passarsi l'informazione direttamente in modo sincrono. Questo meccanismo prende il nome di rendez-vou, ed implica che P sappia a che punto della computazione si trovi il processo ricevente quando richiede la comunicazione. Se infine la capacità è limitata, la comunicazione verrà bufferizzata, ovvero passerà un po' di tempo prima che il messaggio inviato venga ricevuto.
Per quanto riguarda l'ordinamento delle code dei messaggi all'interno delle mailbox, può essere applicato un qualsiasi algoritmo di schedulazione.

La comunicazione con mailbox è particolarmente adatta per i seguenti scenari:

  • comunicazioni molti a uno, dove l'unico processo che può leggere dalla mailbox è detto processo di servizio (o server) e tutti i richiedenti sono i client. Nel caso un cui il server abortisca, il sistema operativo ne esegue immediatamente una copia diversa solo per l'identificatore. Nessun client si accorge della situazione, tranne quello eventualmente in comunicazione col processo di servizio originale quando era terminato. Il nuovo server non è infatti messo a conoscenza delle richieste pendenti
  • comunicazioni uno a molti, con più processi di servizio che soddisfano le richieste di un unico mittente. Per quanto questo possa produrre messaggi, ci sono buone possibilità che trovi sempre un ricevente disponibile. Questa tecnica è auspicabile quando ho un singolo processo che ha più richieste che vuole siano soddisfatte contemporaneamente
  • comunicazione molti a molti, in cui diversi processi di servizio comunicano con diversi processi client.

Comunicazione con file

Distinguiamo due implementazioni, quella con file condivisi e quella mediante pipe.

La comunicazione mediante file condivisi rappresenta una diretta estensione della comunicazione con le mailbox: se quella faceva uso di strutture dati realizzate in memoria centrale condivisa, i file sono invece memorizzati in memoria di massa. La sua gestione sarà al solito demandata al sistema operativo, che garantirà che le operazioni di accesso siano fatte in modo corretto e solo dai processi autorizzati.

La comunicazione mediante pipe impiega invece l'utilizzo dei pipe come strutture di appoggio, una struttura dati di tipo FIFO residente in memoria centrale e che condivide coi file molte delle loro funzioni.

Utilizzo per entrambi le stesse funzioni viste per le mailbox, delle quali ereditano anche gli stessi problemi (e soluzioni) di sincronizzazione e ordinamento.

Comunicazione con socket

La comunicazione con socket è dal punto di vista concettuale una generalizzazione in rete delle pipe, anche se dal punto di vista realizzativo è ovviamente qualcosa di molto più complesso. In sostanza ho il sistema operativo che virtualizza la comunicazione tra macchine diverse utilizzando una pipe spezzata in due porzioni, ognuna residente sulla memoria centrale delle macchine coinvolte.
L'architettura che sta dietro a tale modalità di comunicazione è di tipo client-server, ovvero ho un client che inoltra una richiesta ad una porta specifica su cui un server è in ascolto per la loro evasione. L'identificazione del server avviene tramite la coppia di identificatori indirizzo:porta.

I messaggi possono avere dimensione fissa o variabile a seconda delle applicazioni che le utilizzano, e sono ordinati in modalità FIFO (così come i processi in attesa). I socket si possono creare o distruggere, e si può leggerci o scriverci sopra tramite due canali mono-direzionali. Ciò significa che nel momento in cui una connessione via socket viene accettata, è possibile sia per il server che per il client comunicare su due canali separati. Per evitare che i client trovino la porta di ascolto del server chiusa mentre sta servendo un altro processo, il client chiamante fa in modo di segnalare al server una nuova porta su cui spostare la comunicazione per la gestione della risposta.
Infine, la connessione può essere con gestione del sistema operativo o senza, in cui ogni messaggio viene trasmesso in modo individuale. Questa seconda modalità è ovviamente la più semplice da realizzare, dal momento che non viene fornita alcuna informazione su come il sistema dovrà trattare i messaggi. C'è poi un'ultima tecnica di connessione, che è il multicast.


Torna alla pagina di Sistemi Operativi