X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=ipc.tex;h=536d0960ce3a5c81630a339dc56c8f771f7ed484;hp=c16d5c32ea8486a3d50d94697642579f590e5190;hb=af52f000f3956b58885a317c51faaa57aa543472;hpb=4793eed79c595620b1c31a70fa4411dbd98445e4 diff --git a/ipc.tex b/ipc.tex index c16d5c3..536d096 100644 --- a/ipc.tex +++ b/ipc.tex @@ -6,7 +6,7 @@ Uno degli aspetti fondamentali della programmazione in un sistema unix-like la comunicazione fra processi. In questo capitolo affronteremo solo i meccanismi più elementari che permettono di mettere in comunicazione processi diversi, come quelli tradizionali che coinvolgono \textit{pipe} e -\textit{fifo} e i meccanismi di intercomunicazione di System V. +\textit{fifo} e i meccanismi di intercomunicazione di System V e quelli POSIX. Tralasceremo invece tutte le problematiche relative alla comunicazione attraverso la rete (e le relative interfacce) che saranno affrontate in @@ -20,12 +20,11 @@ implementati con un ulteriore livello sopra i meccanismi elementari. \section{La comunicazione fra processi tradizionale} \label{sec:ipc_unix} -Il primo meccanismo di comunicazione fra processi usato dai sistemi unix-like, -e quello che viene correntemente usato di più, è quello delle \textit{pipe}, -che sono una delle caratteristiche peculiari del sistema, in particolar modo -dell'interfaccia a linea di comando. In questa sezione descriveremo le sue -basi, le funzioni che ne gestiscono l'uso e le varie forme in cui si è -evoluto. +Il primo meccanismo di comunicazione fra processi introdotto nei sistemi Unix, +è quello delle cosiddette \textit{pipe}; esse costituiscono una delle +caratteristiche peculiari del sistema, in particolar modo dell'interfaccia a +linea di comando. In questa sezione descriveremo le sue basi, le funzioni che +ne gestiscono l'uso e le varie forme in cui si è evoluto. \subsection{Le \textit{pipe} standard} @@ -33,39 +32,36 @@ evoluto. Le \textit{pipe} nascono sostanzialmente con Unix, e sono il primo, e tuttora uno dei più usati, meccanismi di comunicazione fra processi. Si tratta in -sostanza di uno speciale tipo di file descriptor, più precisamente una coppia -di file descriptor,\footnote{si tenga presente che le pipe sono oggetti creati - dal kernel e non risiedono su disco.} su cui da una parte si scrive e da -un'altra si legge. Si viene così a costituire un canale di comunicazione -tramite i due file descriptor, nella forma di un \textsl{tubo} (da cui il -nome) in cui in genere un processo immette dati che poi arriveranno ad un -altro. - -La funzione che permette di creare una pipe è appunto \func{pipe}; il suo -prototipo è: +sostanza di una una coppia di file descriptor\footnote{si tenga presente che + le pipe sono oggetti creati dal kernel e non risiedono su disco.} connessi +fra di loro in modo che se quanto scrive su di uno si può rileggere +dall'altro. Si viene così a costituire un canale di comunicazione tramite i +due file descriptor, nella forma di un \textsl{tubo} (da cui il nome) +attraverso cui fluiscono i dati. + +La funzione che permette di creare questa speciale coppia di file descriptor +associati ad una \textit{pipe} è appunto \func{pipe}, ed il suo prototipo è: \begin{prototype}{unistd.h} {int pipe(int filedes[2])} -Crea una coppia di file descriptor associati ad una pipe. +Crea una coppia di file descriptor associati ad una \textit{pipe}. \bodydesc{La funzione restituisce zero in caso di successo e -1 per un errore, nel qual caso \var{errno} potrà assumere i valori \macro{EMFILE}, \macro{ENFILE} e \macro{EFAULT}.} \end{prototype} -La funzione restituisce una coppia di file descriptor nell'array -\param{filedes}; il primo aperto in lettura ed il secondo in scrittura. Il -concetto di funzionamento di una pipe è relativamente semplice, quello che si +La funzione restituisce la coppia di file descriptor nell'array +\param{filedes}; il primo è aperto in lettura ed il secondo in scrittura. Come +accennato concetto di funzionamento di una pipe è semplice: quello che si scrive nel file descriptor aperto in scrittura viene ripresentato tale e quale -nel file descriptor aperto in lettura, da cui può essere riletto. - -I file descriptor infatti non sono connessi a nessun file reale, ma ad un -buffer nel kernel, la cui dimensione è specificata dalla costante -\macro{PIPE\_BUF}, (vedi \secref{sec:sys_file_limits}); lo schema di -funzionamento di una pipe è illustrato in \figref{fig:ipc_pipe_singular}, in -cui sono illustrati i due capi della pipe, associati a ciascun file -descriptor, con le frecce che indicano la direzione del flusso dei dati -attraverso la pipe. +nel file descriptor aperto in lettura. I file descriptor infatti non sono +connessi a nessun file reale, ma ad un buffer nel kernel, la cui dimensione è +specificata dalla costante \macro{PIPE\_BUF}, (vedi +\secref{sec:sys_file_limits}). Lo schema di funzionamento di una pipe è +illustrato in \figref{fig:ipc_pipe_singular}, in cui sono illustrati i due +capi della pipe, associati a ciascun file descriptor, con le frecce che +indicano la direzione del flusso dei dati. \begin{figure}[htb] \centering @@ -74,12 +70,12 @@ attraverso la pipe. \label{fig:ipc_pipe_singular} \end{figure} -Chiaramente creare una pipe all'interno di un processo non serve a niente; se -però ricordiamo quanto esposto in \secref{sec:file_sharing} riguardo al -comportamento dei file descriptor nei processi figli, è immediato capire come -una pipe possa diventare un meccanismo di intercomunicazione. Un processo -figlio infatti condivide gli stessi file descriptor del padre, compresi quelli -associati ad una pipe (secondo la situazione illustrata in +Chiaramente creare una pipe all'interno di un singolo processo non serve a +niente; se però ricordiamo quanto esposto in \secref{sec:file_sharing} +riguardo al comportamento dei file descriptor nei processi figli, è immediato +capire come una pipe possa diventare un meccanismo di intercomunicazione. Un +processo figlio infatti condivide gli stessi file descriptor del padre, +compresi quelli associati ad una pipe (secondo la situazione illustrata in \figref{fig:ipc_pipe_fork}). In questo modo se uno dei processi scrive su un capo della pipe, l'altro può leggere. @@ -92,16 +88,16 @@ capo della pipe, l'altro pu \end{figure} Tutto ciò ci mostra come sia immediato realizzare un meccanismo di -comunicazione fra processi attraverso una pipe, utilizzando le ordinarie -proprietà dei file, ma ci mostra anche qual'è il principale\footnote{Stevens +comunicazione fra processi attraverso una pipe, utilizzando le proprietà +ordinarie dei file, ma ci mostra anche qual'è il principale\footnote{Stevens in \cite{APUE} riporta come limite anche il fatto che la comunicazione è - unidirezionale, in realtà questo è un limite facilmente superabile usando + unidirezionale, ma in realtà questo è un limite facilmente superabile usando una coppia di pipe.} limite nell'uso delle pipe. È necessario infatti che i processi possano condividere i file descriptor della pipe, e per questo essi -devono comunque derivare da uno stesso processo padre che ha aperto la pipe, -o, più comunemente, essere nella relazione padre/figlio. +devono comunque derivare da uno stesso processo padre in cui è avvenuta la +creazione della pipe, o, più comunemente, essere nella relazione padre/figlio. -A differenza di quanto avviene con i file normali la lettura da una pipe può +A differenza di quanto avviene con i file normali, la lettura da una pipe può essere bloccante (qualora non siano presenti dati), inoltre se si legge da una pipe il cui capo in scrittura è stato chiuso, si avrà la ricezione di un EOF (vale a dire che la funzione \func{read} ritornerà restituendo 0). Se invece @@ -122,14 +118,14 @@ da altri processi. \subsection{Un esempio dell'uso delle pipe} \label{sec:ipc_pipe_use} -Per capire meglio il funzionamento di una pipe faremo un esempio di quello che +Per capire meglio il funzionamento delle pipe faremo un esempio di quello che è il loro uso più comune, analogo a quello effettuato della shell, e che consiste nell'inviare l'output di un processo (lo standard output) sull'input -di un'altro. Realizzaremo il programma nella forma di un -\textit{CGI}\footnote{Un CGI (\textit{Common Gateway Interface}) è un programma - che permette la creazione dinamica di un oggetto da inserire all'interno di - una pagina HTML.} per apache, che genera una immagine JPEG di un codice a -barre, specificato come parametro di input. +di un'altro. Realizzeremo il programma di esempio nella forma di un +\textit{CGI}\footnote{Un CGI (\textit{Common Gateway Interface}) è un + programma che permette la creazione dinamica di un oggetto da inserire + all'interno di una pagina HTML.} per apache, che genera una immagine JPEG +di un codice a barre, specificato come parametro di input. Un programma che deve essere eseguito come \textit{CGI} deve rispondere a delle caratteristiche specifiche, esso infatti non viene lanciato da una @@ -143,13 +139,13 @@ che ne descrive il mime-type) sullo standard output, in modo che il web-server possa reinviarlo al browser che ha effettuato la richiesta, che in questo modo è in grado di visualizzarlo opportunamente. -Per fare questo useremo in sequenza i programmi \cmd{barcode} e \cmd{gs}, il -primo infatti è in grado di generare immagini postscript di codici a barre -corrispondenti ad una qualunque stringa, mentre il secondo serve per poter -effettuare la conversione della stessa immagine in formato JPEG. Usando una -pipe potremo inviare l'output del primo sull'input del secondo, secondo lo -schema mostrato in \figref{fig:ipc_pipe_use}, in cui la direzione del flusso -dei dati è data dalle frecce continue. +Per realizzare quanto voluto useremo in sequenza i programmi \cmd{barcode} e +\cmd{gs}, il primo infatti è in grado di generare immagini postscript di +codici a barre corrispondenti ad una qualunque stringa, mentre il secondo +serve per poter effettuare la conversione della stessa immagine in formato +JPEG. Usando una pipe potremo inviare l'output del primo sull'input del +secondo, secondo lo schema mostrato in \figref{fig:ipc_pipe_use}, in cui la +direzione del flusso dei dati è data dalle frecce continue. \begin{figure}[htb] \centering @@ -168,7 +164,8 @@ file.\footnote{il problema potrebbe essere superato determinando in anticipo un nome appropriato per il file temporaneo, che verrebbe utilizzato dai vari sotto-processi, e cancellato alla fine della loro esecuzione; ma a questo le cose non sarebbero più tanto semplici.} L'uso di una pipe invece permette -di risolvere il problema in maniera semplice ed elegante. +di risolvere il problema in maniera semplice ed elegante, oltre ad essere +molto più efficiente, dato che non si deve scrivere su disco. Il programma ci servirà anche come esempio dell'uso delle funzioni di duplicazione dei file descriptor che abbiamo trattato in @@ -187,7 +184,7 @@ nel file \file{BarCodePage.c} che si trova nella directory dei sorgenti. int main(int argc, char *argv[], char *envp[]) { ... - /* create two pipes to handle process communication */ + /* create two pipes, pipein and pipeout, to handle communication */ if ( (retval = pipe(pipein)) ) { WriteMess("input pipe creation error"); exit(0); @@ -207,14 +204,14 @@ int main(int argc, char *argv[], char *envp[]) dup2(pipein[0], STDIN_FILENO); /* remap stdin to pipe read end */ close(pipeout[0]); dup2(pipeout[1], STDOUT_FILENO); /* remap stdout in pipe output */ - execlp("barcode", "barcode", size, NULL); //"-o", "-", NULL); + execlp("barcode", "barcode", size, NULL); } close(pipein[0]); /* close input side of input pipe */ write(pipein[1], argv[1], strlen(argv[1])); /* write parameter to pipe */ close(pipein[1]); /* closing write end */ waitpid(pid, NULL, 0); /* wait child completion */ /* Second fork: use child to run ghostscript */ - if ( (pid = fork()) == -1) { /* on error exit */ + if ( (pid = fork()) == -1) { WriteMess("child creation error"); exit(0); } @@ -243,7 +240,7 @@ La prima operazione del programma (\texttt{\small 4--12}) le due pipe che serviranno per la comunicazione fra i due comandi utilizzati per produrre il codice a barre; si ha cura di controllare la riuscita della chiamata, inviando in caso di errore un messaggio invece dell'immagine -richiesta.\footnote{la funzione \func{WriteMess}, non è riportata in +richiesta.\footnote{la funzione \func{WriteMess} non è riportata in \secref{fig:ipc_barcodepage_code}; essa si incarica semplicemente di formattare l'uscita alla maniera dei CGI, aggiungendo l'opportuno \textit{mime type}, e formattando il messaggio in HTML, in modo che @@ -379,7 +376,7 @@ originarie del codice a barre e produce un JPEG delle dimensioni adeguate. Questo però ci porta a scontrarci con una caratteristica peculiare delle pipe, che a prima vista non è evidente. Per poter effettuare la conversione di un PDF infatti è necessario, per la struttura del formato, dover eseguire delle -\func{lseek} sul file da convertire; se si esegue \cmd{gs} su un file normale +\func{lseek} sul file da convertire; se si esegue \cmd{gs} su un file regolare non ci sono problemi, ma una pipe però è rigidamente sequenziale, ed il tentativo di eseguire detta operazione su una pipe comporta l'immediato fallimento con un errore di \macro{ESPIPE}. Questo ci dice che in generale la @@ -523,8 +520,56 @@ stare molto attenti alla possibili deadlock.\footnote{se si cerca di leggere processo si blocca e non potrà quindi mai eseguire le funzioni di scrittura.} -L'impiego più comune per le fifo è quello che le vede impegnate con un -processo in +Per la loro caratteristica di essere accessibili attraverso il filesystem, è +piuttosto frequente l'utilizzo di una fifo come canale di comunicazione nelle +situazioni un processo deve ricevere informazioni dagli altri. In questo caso +è fondamentale che le operazioni di scrittura siano atomiche; per questo si +deve sempre tenere presente che questo è vero soltanto fintanto che non si +supera il limite delle dimensioni di \macro{PIPE\_BUF} (si ricordi quanto +detto in \secref{sec:ipc_pipes}). + +A parte il precedente, che resta probabilmente il più comune, Stevens riporta +in \cite{APUE} altre due casistiche principali per l'uso delle fifo: +\begin{itemize} +\item Da parte dei comandi di shell, per evitare la creazione di file + temporanei quando si devono inviare i dati di uscita di un processo + sull'input di parecchi altri (attraverso l'uso del comando \cmd{tee}). + +\item Come canale di comunicazione fra un client ed un server (il modello + \textit{client-server} è illustrato in \secref{sec:net_cliserv}). +\end{itemize} + +Nel primo caso quello che si fa è creare tante pipe quanti sono i processi a +cui i vogliono inviare i dati, da usare come standard input per gli stessi; una +volta che li si saranno posti in esecuzione ridirigendo lo standard input si +potrà eseguire il processo iniziale replicandone, con il comando \cmd{tee}, +l'output sulle pipe. + +Il secondo caso è relativamente semplice qualora si debba comunicare con un +processo alla volta (nel qual caso basta usare due pipe, una per leggere ed +una per scrivere), le cose diventano invece molto più complesse quando si +vuole effettuare una comunicazione fra il server ed un numero imprecisato di +client; se il primo infatti può ricevere le richieste attraverso una fifo +``nota'', per le risposte non si può fare altrettanto, dato che, per la +struttura sequenziale delle fifo, i client dovrebbero sapere, prima di +leggerli, quando i dati inviati sono destinati a loro. + +Per risolvere questo problema, si può usare un'architettura come quella +illustrata da Stevens in \cite{APUE}, in cui le risposte vengono inviate su +fifo temporanee identificate dal \acr{pid} dei client, ma in ogni caso il +sistema è macchinoso e continua ad avere vari inconvenienti\footnote{lo stesso + Stevens nota come sia impossibile per il server sapere se un client è andato + in crash, con la possibilità di far restare le fifo temporanee sul + filesystem, come sia necessario intercettare \macro{SIGPIPE} dato che un + client può terminare dopo aver fatto una richiesta, ma prima che la risposta + sia inviata, e come occorra gestire il caso in cui non ci sono client attivi + (e la lettura dalla fifo nota restituisca al serve un end-of-file.}; in +generale infatti l'interfaccia delle fifo non è adatta a risolvere questo tipo +di problemi, che possono essere affrontati in maniera più semplice ed efficace +o usando i \textit{socket}\index{socket} (che tratteremo in dettaglio a +partire da \capref{cha:socket_intro}) o ricorrendo a diversi meccanismi di +comunicazione, come quelli che esamineremo in \secref{sec:ipc_sysv}. + \section{La comunicazione fra processi di System V} @@ -532,16 +577,49 @@ processo in Benché le pipe (e le fifo) siano ancora ampiamente usate, esse presentano numerosi limiti, il principale dei quali è che il meccanismo di comunicazione -è rigidamente sequenziale; una situazione in cui un processo scrive qualcosa -che molti altri devono poter leggere non può essere implementata con una pipe. +è rigidamente sequenziale; per questo una situazione in cui un processo scrive +qualcosa che molti altri devono poter leggere non può essere implementata in +maniera semplice con una pipe. + +Per superare questi limiti nello sviluppo di System V vennero introdotti una +serie di nuovi oggetti di comunicazione e relative interfacce di +programmazione in grado di garantire una maggiore flessibilità; in questa +sezione esamineremo quello che viene ormai chiamato il \textsl{Sistema di + comunicazione inter-processo} di System V, (o \textit{System V IPC + (Inter-Process Comunication)}. + + + +\subsection{Considerazioni generali} +\label{sec:ipc_sysv_generic} + +La principale caratteristica del sistema di IPC di System V è quella di essere +basato su oggetti permanenti che risiedono nel kernel. Questi, a differenza di +quanto avviene per i file descriptor, non mantengono un contatore dei +riferimenti, pertanto non vengono cancellati dal sistema una volta che non +sono più in uso. Questo comporta che, al contrario di quanto avviene per pipe +e fifo, la memoria allocata per questi oggetti non viene rilasciata +automaticamente, ed essi devono essere cancellati esplicitamente, altrimenti +resteranno attivi fintanto che non si riavvia il sistema. + +Gli oggetti usati nel System V IPC vengono creati direttamente dal kernel, e +sono accessibili solo specificando il relativo \textsl{identificatore}, che è +il numero progressivo che il kernel gli assengna quanto vengono creati (il +prodedimento è simile a quello con cui si assegna il \acr{pid} dei processi). + +L'identificatore è in genere restituito dalle funzioni che creano l'oggetto, +nasce quindi il problema di come processi diversi possono accedere allo stesso +oggetto. Per far questo a ciascuno di essi viene anche associata una +\textsl{chiave}, che può essere indicata in fasi di creazione. Usando la +stessa chiave due processi diversi potranno ricavare l'identificatore +associato ad un oggetto e accedervi entrambi. + +Questa caratteristica mostra il primo dei problemi associati al sistema di IPC +di System V. Un secondo problema riguarda le modalità per l'accesso a questi +oggetti. + + -Per superarne i vari limiti, nello sviluppo di System V vennero introdotti una -serie di nuovi oggetti di comunicazione e relative interfacce id -programmazione che garantissero una maggiore flessibilità; in questa sezione -esamineremo quello che viene ormai chiamato il \textsl{Sistema di - comunicazione inter-processo} di System V , più comunemente noto come -\textit{System V IPC (Inter-Process Comunication)}. - \subsection{Code di messaggi} \label{sec:ipc_messque} @@ -561,6 +639,19 @@ Il secondo oggetto introdotto dal \textit{System V IPC} Il terzo oggetto introdotto dal \textit{System V IPC} è quello della memoria condivisa. + + + +\section{La comunicazione fra processi di POSIX} +\label{sec:ipc_posix} + +Lo standard POSIX.1b ha introdotto dei nuovi meccanismi di comunicazione, +rifacendosi a quelli di System V, introducendo una nuova interfaccia che +evitasse i principali problemi evidenziati in ... + + + + %%% Local Variables: %%% mode: latex %%% TeX-master: "gapil"