2. Un sistema di comunicazione tra processi in un sistema time-sharing
Questa sezione descrive un insieme di operazioni che consentono la comunicazione tra processi all'interno di un sistema time-sharing. Seguendo la notazione di [10], chiamo questa facilità di comunicazione tra processi un IPC. Per aiutare l'esposizione di questo IPC, viene descritto un modello di sistema time-sharing; tale modello viene poi usato per illustrare l'uso delle operazioni di comunicazione tra processi.
Il sistema time-sharing modello ha due parti: il monitor e i processi. Il monitor svolge funzioni quali il passaggio del controllo da un processo a un altro quando un processo ha usato "abbastanza" tempo, la gestione degli interrupt hardware, la gestione della memoria centrale e del supporto di scambio, il controllo del passaggio del controllo da un processo a un altro (cioè i meccanismi di protezione), la creazione di processi, la cura dei processi dormienti e la messa a disposizione dei processi di un insieme di operazioni di estensione della macchina (spesso dette Supervisor o Monitor Calls). I processi svolgono le normali funzioni dell'utente (processi utente) nonché le funzioni abitualmente considerate funzioni di supervisione in un sistema time-sharing (processi di sistema) ma non svolte dal monitor nel modello attuale. Un tipico processo di sistema è il gestore del disco o il file system. I processi di sistema sono il gestore del disco o il file system. I processi di sistema sono probabilmente autorizzati a eseguire in modalità supervisore, ed eseguono effettivamente istruzioni di I/O e altre operazioni privilegiate che i processi utente non sono autorizzati a eseguire. Sotto ogni altro aspetto, processi utente e processi di sistema sono identici. Per ragioni di efficienza, può essere utile pensare ai processi di sistema come bloccati in memoria centrale.
Sebbene ci interesseranno più avanti in questo studio, le considerazioni sulla protezione non sono qui il mio argomento: supporrò invece che tutti i processi siano processi "buoni" che non commettono mai errori. Se il lettore ha bisogno di una struttura di protezione da tenere a mente mentre legge questa nota, il sistema a capacità sviluppato in [1][3][7][8] dovrebbe essergli soddisfacente.
Tra le operazioni che un processo può chiedere al monitor di eseguire, sei sono di particolare interesse per fornire una capacità di comunicazione tra processi.
RECEIVE. Questa operazione consente a un processo specificato di inviare un messaggio al processo che esegue il RECEIVE. L'operazione ha quattro parametri: la porta (definita più sotto) in attesa del messaggio -- la porta RECEIVE; la porta dalla quale sarà accettato un messaggio -- la porta SEND; una specifica del buffer disponibile per ricevere il messaggio; e una locazione a cui trasferirsi quando la trasmissione è completa -- la locazione di ripresa.
SEND. Questa operazione invia un messaggio dal processo che esegue il SEND a un processo specificato. Ha quattro parametri: una porta a cui inviare il messaggio -- la porta RECEIVE; la porta dalla quale il messaggio viene inviato -- la porta SEND; una specifica del buffer che contiene il messaggio da inviare; e la locazione di ripresa.
RECEIVE ANY. Questa operazione consente a qualsiasi processo di inviare un messaggio al processo che esegue il RECEIVE ANY. L'operazione ha quattro parametri: la porta in attesa del messaggio -- la porta RECEIVE; una specifica del buffer disponibile per ricevere il messaggio; una locazione di ripresa; e una locazione in cui annotare la porta che ha inviato il messaggio.
SEND FROM ANY. Questa operazione consente a un processo di inviare un messaggio a un processo in grado di ricevere un messaggio da qualsiasi processo. Ha gli stessi quattro parametri di SEND. (La necessità di questa operazione sarà spiegata molto più avanti.)
SLEEP. Questa operazione consente al processo attualmente in esecuzione di mettersi a dormire in attesa del completamento di un evento. L'operazione ha un parametro opzionale, l'evento da attendere. Un esempio di evento è l'arrivo di un interrupt hardware. Il monitor non mette mai unilateralmente a dormire un processo in conseguenza dell'esecuzione di una delle quattro operazioni precedenti; tuttavia, se un processo è dormiente quando una delle quattro operazioni precedenti viene soddisfatta, il processo viene svegliato.
UNIQUE. Questa operazione ottiene un numero unico dal monitor.
Una porta è un particolare percorso di dati verso un processo (una porta RECEIVE) o da un processo (una porta SEND), e tutte le porte hanno associato un numero di porta unico che serve a identificare la porta. Le porte sono usate per trasmettere messaggi da un processo a un altro nel modo seguente. Si considerino due processi, A e B, che desiderano comunicare. Il processo A esegue un RECEIVE sulla porta N dalla porta M. Il processo B esegue un SEND sulla porta N dalla porta M. Il monitor fa corrispondere i numeri di porta e trasferisce il messaggio dal processo B al processo A. Non appena il buffer è stato interamente trasmesso fuori dal processo B, il processo B viene ripreso nella locazione specificata nell'operazione SEND. Non appena il messaggio è interamente ricevuto dal processo A, il processo A viene ripreso nella locazione specificata nell'operazione RECEIVE. Come i processi si procurino i numeri di porta corretti con cui comunicare con altri processi non è affare del monitor -- questo problema è lasciato ai processi.
Quando si esegue un SEND, non succede nulla finché non viene eseguito un RECEIVE corrispondente. Da qualche parte nel monitor deve esserci una tabella dei numeri di porta associati ai processi e alle locazioni di ripresa. Le voci della tabella vengono cancellate dopo ogni corrispondenza SEND/RECEIVE stabilita. Se un RECEIVE adeguato non viene eseguito per qualche tempo, il SEND scade dopo un po' e il processo che lo ha eseguito viene avvisato. Se viene eseguito un RECEIVE ma il SEND corrispondente non si verifica per molto tempo, il RECEIVE scade e il processo che lo ha eseguito viene avvisato.
Il meccanismo di scadenza delle voci "inutilizzate" della tabella ha scarsa importanza fondamentale e fornisce soltanto un metodo comodo per raccogliere i rifiuti della tabella. Non c'è problema se una voce scade prematuramente, perché il processo può sempre rieseguire l'operazione. Tuttavia, l'intervallo di scadenza dovrebbe essere abbastanza lungo da far sì che la continua riesecuzione di un'operazione comporti un sovraccarico minimo.
Un RECEIVE ANY non scade mai, ma può essere ritirato con una chiamata al supervisore. Un messaggio risultante da un SEND FROM ANY viene sempre inviato immediatamente e sarà scartato se non esiste un destinatario adeguato. Non viene restituito alcun messaggio di errore e l'accusa di ricezione, se ve n'è una, è a carico dei processi. Se la tabella in cui SEND e RECEIVE vengono fatti corrispondere dovesse traboccare, un processo che origina un ulteriore SEND o RECEIVE viene avvisato esattamente come se il SEND o il RECEIVE fosse scaduto.
La locazione di ripresa è un ingresso di interrupt associato a un pseudo-interrupt locale al processo che esegue l'operazione che specifica la locazione di ripresa. Se il processo è in esecuzione quando si verifica l'evento che causa lo pseudo-interrupt (per esempio, l'arrivo di un messaggio che soddisfa un RECEIVE in sospeso), l'effetto è esattamente quello che si avrebbe se l'hardware interrompesse il processo e trasferisse il controllo alla locazione di ripresa. Vengono salvate informazioni sufficienti perché il processo possa proseguire l'esecuzione dal punto in cui è stato interrotto, una volta servito l'interrupt. Se il processo è dormiente, viene messo in stato di pronto e lo pseudo-interrupt viene conservato finché il processo non torna in esecuzione, e allora l'interrupt viene consentito. Qualsiasi porta di messaggi RECEIVE o RECEIVE ANY può quindi servire a fornire interrupt di processo, canali di eventi, sincronizzazione tra processi, trasferimenti di messaggi, ecc. È il programma utente a stabilire ciò che vuole.
Resta al lettore, come esercizio, convincersi che il monitor di cui è gravato può essere indotto a fornire le sei operazioni sopra descritte -- la maggior parte dei monitor può farlo, dato che si tratta solo di chiamate al supervisore aggiuntive.
Un esempio. Si supponga che il nostro sistema time-sharing modello sia inizializzato con diversi processi sempre in esecuzione. Inoltre, questi processi permanenti hanno alcune porte<2> universalmente note e assegnate in modo permanente. Si supponga che due dei processi in esecuzione permanente siano il processo di registro (logger-process) e il processo di scansione del telescrivente (teletype-scanner-process). Quando il processo di scansione del telescrivente inizia a essere eseguito, si mette a dormire in attesa di un interrupt dallo scanner hardware del telescrivente. Il processo di registro si mette inizialmente a dormire in attesa di un messaggio dal processo di scansione del telescrivente attraverso porte SEND e RECEIVE permanenti ben note. Il processo di scansione del telescrivente tiene una tabella indicizzata per numero di telescrivente, contenente in ogni voce una coppia di numeri di porta da usare per inviare caratteri da quel telescrivente a un processo e una coppia di numeri di porta da usare per ricevere caratteri per quel telescrivente da un processo. Se arriva un carattere (svegliando il processo di scansione del telescrivente) e il processo non ha alcuna voce per quel telescrivente, ottiene una coppia di numeri unici dal monitor (tramite UNIQUE) e invia al processo di registro un messaggio contenente questa coppia di numeri, usando le porte per le quali sa che il processo di registro ha un RECEIVE in sospeso. Il processo di scansione inserisce inoltre la coppia di numeri nella tabella dei telescriventi e invia il carattere e tutti i caratteri futuri di questo telescrivente alla porta con il primo numero dalla porta con il secondo numero. Il processo di scansione deve anche passare una seconda coppia di numeri unici al processo di registro perché li usi per l'output verso il telescrivente, ed eseguire un RECEIVE usando questi numeri di porta. Quando il processo di registro riceve il messaggio dal processo di scansione, avvia una copia di quello che gli utenti del SDS 940 TSS [6] chiamano l'executive<3>, e passa i numeri di porta a questa copia dell'executive, così che anche questo processo executive possa effettuare i suoi ingressi e le sue uscite verso il telescrivente usando queste porte. Se il processo di registro vuole ottenere un numero di lavoro e una password dall'utente, può usare temporaneamente i numeri di porta per comunicare con l'utente prima di passarli all'executive. Il processo di scansione potrebbe sempre usare gli stessi numeri di porta per un dato telescrivente, purché i numeri siano passati a una sola copia dell'executive alla volta.
È importante distinguere tra l'atto di passare una porta da un processo a un altro e l'atto di passare un numero di porta da un processo a un altro. Nell'esempio precedente, in cui i caratteri di un dato telescrivente sono inviati dal processo di scansione del telescrivente o al processo di registro o a un processo executive, la porta SEND resta sempre nel processo di scansione del telescrivente, mentre la porta RECEIVE si sposta dal processo di registro al processo executive. D'altro canto, il numero di porta SEND viene passato tra il processo di registro e il processo executive per consentire al processo ricevente di eseguire un RECEIVE dalla porta SEND corretta. È cruciale che, una volta che un processo trasferisce una porta a un altro processo, il primo processo non usi più quella porta. Potremmo aggiungere un meccanismo che lo imponga. Il sistema a oggetti protetti di [9] è uno di tali meccanismi. Usando questo meccanismo, un processo che esegue un SEND avrebbe bisogno di una capacità per la porta SEND, e in un dato momento esisterebbe nel sistema una sola capacità per questa porta SEND. A un processo che esegue un RECEIVE sarebbe richiesto di possedere una capacità per la porta RECEIVE, e in un dato momento esisterebbe una sola capacità per questa porta RECEIVE. Senza un tale meccanismo di protezione, una porta passa implicitamente da un processo a un altro semplicemente perché i processi usano la porta in momenti disgiunti, anche se il numero della porta non viene mai passato esplicitamente.
Naturalmente, se il sistema a oggetti protetti ci è disponibile, non c'è davvero bisogno di specificare due numeri di porta prima che una trasmissione possa aver luogo. Il fatto che un processo conosca un numero di porta RECEIVE esistente potrebbe essere considerato prova prima facie del diritto di quel processo di inviare a quella porta. La differenza tra porte RECEIVE e RECEIVE ANY dipende allora unicamente dal numero di copie di un particolare numero di porta che sono state distribuite. Un sistema basato su questo approccio sarebbe chiaramente preferibile a quello qui descritto se fosse possibile supporre che tutti i sistemi time-sharing autonomi di una rete adottassero questo meccanismo di protezione. Se tale supposizione non può essere fatta, sembra più pratico esigere entrambi i numeri di porta.
Si noti che nel sistema di comunicazione tra processi (IPC) qui descritto, quando due processi desiderano comunicare, sono essi stessi a stabilire la connessione, e sono liberi di farlo in modo reciprocamente comodo. Per esempio, possono scambiarsi i numeri di porta, oppure un processo può scegliere tutti i numeri di porta e indicare all'altro quali usare. Tuttavia, in una particolare realizzazione di un sistema time-sharing, i costruttori del sistema potrebbero scegliere di limitare l'esecuzione di SEND e RECEIVE da parte dei processi e di vietare il passaggio arbitrario di porte e numeri di porta, richiedendo invece che venga chiamato il monitor (o qualche altro programma speciale) per svolgere queste funzioni.
Il controllo di flusso è fornito in questo IPC con il semplice metodo di non iniziare mai la trasmissione di dati risultante da un SEND di un processo prima che il destinatario esegua un RECEIVE. Naturalmente, messaggi tra processi possono anche essere scambiati per suggerire a un processo di smettere di inviare o che venga allocato spazio.
In generale, le porte ben note e assegnate in modo permanente sono usate tramite RECEIVE ANY e SEND FROM ANY. Le porte permanenti serviranno il più delle volte ad avviare processi e, di conseguenza, attraverso di esse passeranno pochi dati. Se un processo è in esecuzione (magari dormiente) e ha un RECEIVE ANY in sospeso, allora qualsiasi processo che conosca il numero della porta di ricezione può parlare con quel processo senza passare per i processi di registro. Ciò è ovviamente essenziale all'interno di un sistema time-sharing locale e sembra molto utile in una rete più generale se si vuole raggiungere l'ideale della condivisione di risorse. Per esempio, in una rete con condivisione di risorse, i programmi delle librerie di sottoprogrammi di tutti i siti potrebbero avere sempre RECEIVE ANY in sospeso su porte assegnate in modo permanente e con numeri di porta ben noti. Così, per usare una particolare risorsa di rete come un hardware per la manipolazione di matrici, un processo in esecuzione ovunque nella rete può inviare al sottoprogramma di inversione di matrice un messaggio contenente la matrice da invertire e i numeri di porta da usare per restituire i risultati.
Un ulteriore esempio dimostra l'uso del compilatore FORTRAN. Abbiamo già spiegato come un utente si sieda al suo telescrivente e venga collegato a un executive. Da lì proseguiamo. L'utente sta digitando in ingresso e ricevendo in uscita dall'executive, che sta eseguendo SEND e RECEIVE. Alla fine l'utente digita RUN FORTRAN, e l'executive chiede al monitor di avviare una copia del compilatore FORTRAN e passa a FORTRAN, come parametri di avvio, i numeri di porta che l'executive stava usando per parlare con il telescrivente. (Almeno concettualmente, a FORTRAN viene passata una porta su cui RECEIVE i caratteri dal telescrivente e una porta da cui SEND i caratteri verso il telescrivente.) FORTRAN si aspetta naturalmente questi parametri ed esegue SEND e RECEIVE attraverso le porte indicate per scoprire dall'utente quali file di ingresso e di uscita vuole usare. FORTRAN digita INPUT FILE? all'utente, che risponde F001. FORTRAN invia allora un messaggio al processo del file system, che è dormiente in attesa di qualcosa da fare. Il messaggio viene inviato tramite porte ben note e chiede al file system di aprire F001 in ingresso. Il messaggio contiene anche una coppia di numeri di porta che il processo del file system può usare per inviare la sua risposta. Il file system cerca F001, lo apre in ingresso, inserisce alcune voci nelle sue tabelle dei file aperti e rinvia a FORTRAN un messaggio contenente i numeri di porta che FORTRAN può usare per leggere il file. La stessa procedura viene seguita per il file di uscita. Quando la compilazione è completa, FORTRAN restituisce i numeri di porta del telescrivente (e le porte) all'executive che era dormiente in attesa di un messaggio da FORTRAN, e poi FORTRAN arresta se stesso. Il processo del file system torna a dormire quando non ha altro da fare<4>.
Ancora una volta, il processo del file system può conservare una piccola raccolta di numeri di porta che usa ripetutamente, se riesce a far restituire i numeri di porta dagli utenti del file system quando hanno finito di usarli. Naturalmente, quando questa raccolta di numeri di porta si è infine esaurita, il file system può ottenere dal monitor alcuni nuovi numeri unici.