X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=ipc.tex;h=536d0960ce3a5c81630a339dc56c8f771f7ed484;hp=abb9aef11b7a026258ba4df5d189501f74168010;hb=af52f000f3956b58885a317c51faaa57aa543472;hpb=0d526e9c3fd5ccb8a3ea860c578f20f48d89f2a4 diff --git a/ipc.tex b/ipc.tex index abb9aef..536d096 100644 --- a/ipc.tex +++ b/ipc.tex @@ -6,10 +6,10 @@ 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 gran +attraverso la rete (e le relative interfacce) che saranno affrontate in dettaglio in un secondo tempo. Non affronteremo invece meccanismi più complessi ed evoluti come le RPC (\textit{Remote Procedure Calls}) e CORBA (\textit{Common Object Request Brocker Architecture}) che in genere sono @@ -20,50 +20,48 @@ 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} \label{sec:ipc_pipes} -Le \textit{pipe} nascono sostanzialmente con Unix, e sono il primo, ed uno dei -più usati, meccanismi di comunicazione fra processi. Si tratta in sostanza uno -speciale tipo di file\footnote{più precisamente un file descriptor; le pipe - sono create dal kernel e non risiedono su disco.} in cui un processo scrive -ed un altro legge. Si viene così a costituire un canale di comunicazione fra i -due processi, nella forma di un \textsl{tubo} (da cui il nome) in cui uno dei -processi immette dati che poi arriveranno all'altro. - - -La funzione che permette di creare una pipe è appunto \func{pipe}; il suo -prototipo è: +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 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, vedi \secref{sec:sys_file_limits}, è -specificata dalla costante \macro{PIPE\_BUF} illustrata 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 attaverso 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 @@ -72,79 +70,588 @@ del flusso dei dati attaverso 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. \begin{figure}[htb] \centering \includegraphics[height=5cm]{img/pipefork} - \caption{Schema dell'uso di una pipe come mezzo di comunicazione fra - processo attraverso una \func{fork}.} + \caption{Schema dei collegamenti ad una pipe, condivisi fra processo padre e + figlio dopo l'esecuzione \func{fork}.} \label{fig:ipc_pipe_fork} \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 - riporta in APUE come limite anche il fatto che la comunicazione è - unidirezionale, in realtà questo è un limite facilemente risolvibile usando +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, 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ò +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 +si esegue una scrittura su una pipe il cui capo in lettura non è aperto il +processo riceverà il segnale \macro{EPIPE}, e la funzione di scrittura +restituirà un errore di \macro{EPIPE} (al ritorno del manipolatore, o qualora +il segnale sia ignorato o bloccato). + +La dimensione del buffer della pipe (\macro{PIPE\_BUF}) ci dà inoltre un'altra +importante informazione riguardo il comportamento delle operazioni di lettura +e scrittura su di una pipe; esse infatti sono atomiche fintanto che la +quantità di dati da scrivere non supera questa dimensione. Qualora ad esempio +si effettui una scrittura di una quantità di dati superiore l'operazione verrà +effettuata in più riprese, consentendo l'intromissione di scritture effettuate +da altri processi. + + +\subsection{Un esempio dell'uso delle pipe} +\label{sec:ipc_pipe_use} + +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. 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 +shell, ma dallo stesso web server, alla richiesta di una specifica URL, che di +solito ha la forma: +\begin{verbatim} + http://www.sito.it/cgi-bin/programma?parametro +\end{verbatim} +ed il risultato dell'elaborazione deve essere presentato (con una intestazione +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 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 + \includegraphics[height=5cm]{img/pipeuse} + \caption{Schema dell'uso di una pipe come mezzo di comunicazione fra + due processi attraverso attraverso l'esecuzione una \func{fork} e la + chiusura dei capi non utilizzati.} + \label{fig:ipc_pipe_use} +\end{figure} + +Si potrebbe obiettare che sarebbe molto più semplice salvare il risultato +intermedio su un file temporaneo. Questo però non tiene conto del fatto che un +\textit{CGI} deve poter gestire più richieste in concorrenza, e si avrebbe una +evidente race condition in caso di accesso simultaneo a detto +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, 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 +\secref{sec:file_dup}, in particolare di \func{dup2}. È attraverso queste +funzioni infatti che è possibile dirottare gli stream standard dei processi +(che abbiamo visto in \secref{sec:file_std_descr} e +\secref{sec:file_std_stream}) sulla pipe. In \figref{fig:ipc_barcodepage_code} +abbiamo riportato il corpo del programma, il cui codice completo è disponibile +nel file \file{BarCodePage.c} che si trova nella directory dei sorgenti. + + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \begin{lstlisting}{} +int main(int argc, char *argv[], char *envp[]) +{ + ... + /* create two pipes, pipein and pipeout, to handle communication */ + if ( (retval = pipe(pipein)) ) { + WriteMess("input pipe creation error"); + exit(0); + } + if ( (retval = pipe(pipeout)) ) { + WriteMess("output pipe creation error"); + exit(0); + } + /* First fork: use child to run barcode program */ + if ( (pid = fork()) == -1) { /* on error exit */ + WriteMess("child creation error"); + exit(0); + } + /* if child */ + if (pid == 0) { + close(pipein[1]); /* close pipe write end */ + 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); + } + 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) { + WriteMess("child creation error"); + exit(0); + } + /* second child, convert PS to JPEG */ + if (pid == 0) { + close(pipeout[1]); /* close write end */ + dup2(pipeout[0], STDIN_FILENO); /* remap read end to stdin */ + /* send mime type */ + write(STDOUT_FILENO, content, strlen(content)); + execlp("gs", "gs", "-q", "-sDEVICE=jpeg", "-sOutputFile=-", "-", NULL); + } + /* still parent */ + close(pipeout[1]); + waitpid(pid, NULL, 0); + exit(0); +} + \end{lstlisting} + \end{minipage} + \normalsize + \caption{Sezione principale del codice del \textit{CGI} + \file{BarCodePage.c}.} + \label{fig:ipc_barcodepage_code} +\end{figure} -Per capire meglio il funzionamento di una pipe faremo un esempio di quello che -è l'uso più comune, di una pipe, quello fatto dalla shell, che permette di -inviare automaticamente l'output (usando lo standard output) di un programma -sull'input di un'altro. +La prima operazione del programma (\texttt{\small 4--12}) è quella di creare +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 + \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 + quest'ultimo possa essere visualizzato correttamente da un browser.} + +Una volta create le pipe, il programma può creare (\texttt{\small 13-17}) il +primo processo figlio, che si incaricherà (\texttt{\small 19--25}) di eseguire +\cmd{barcode}. Quest'ultimo legge dallo standard input una stringa di +caratteri, la converte nell'immagine postscript del codice a barre ad essa +corrispondente, e poi scrive il risultato direttamente sullo standard output. + +Per poter utilizzare queste caratteristiche prima di eseguire \cmd{barcode} si +chiude (\texttt{\small 20}) il capo aperto in scrittura della prima pipe, e se +ne collega (\texttt{\small 21}) il capo in lettura allo standard input, usando +\func{dup2}. Si ricordi che invocando \func{dup2} il secondo file, qualora +risulti aperto, viene, come nel caso corrente, chiuso prima di effettuare la +duplicazione. Allo stesso modo, dato che \cmd{barcode} scrive l'immagine +postscript del codice a barre sullo standard output, per poter effettuare una +ulteriore redirezione il capo in lettura della seconda pipe viene chiuso +(\texttt{\small 22}) mentre il capo in scrittura viene collegato allo standard +output (\texttt{\small 23}). + +In questo modo all'esecuzione (\texttt{\small 25}) di \cmd{barcode} (cui si +passa in \var{size} la dimensione della pagina per l'immagine) quest'ultimo +leggerà dalla prima pipe la stringa da codificare che gli sarà inviata dal +padre, e scriverà l'immagine postscript del codice a barre sulla seconda. + +Al contempo una volta lanciato il primo figlio, il processo padre prima chiude +(\texttt{\small 26}) il capo inutilizzato della prima pipe (quello in input) e +poi scrive (\texttt{\small 27}) la stringa da convertire sul capo in output, +così che \cmd{barcode} possa riceverla dallo standard input. A questo punto +l'uso della prima pipe da parte del padre è finito ed essa può essere +definitivamente chiusa (\texttt{\small 28}), si attende poi (\texttt{\small + 29}) che l'esecuzione di \cmd{barcode} sia completata. + +Alla conclusione della sua esecuzione \cmd{barcode} avrà inviato l'immagine +postscript del codice a barre sul capo in scrittura della seconda pipe; a +questo punto si può eseguire la seconda conversione, da PS a JPEG, usando il +programma \cmd{gs}. Per questo si crea (\texttt{\small 30--34}) un secondo +processo figlio, che poi (\texttt{\small 35--42}) eseguirà questo programma +leggendo l'immagine postscript creata da \cmd{barcode} dallo standard input, +per convertirla in JPEG. + +Per fare tutto ciò anzitutto si chiude (\texttt{\small 37}) il capo in +scrittura della seconda pipe, e se ne collega (\texttt{\small 38}) il capo in +lettura allo standard input. Per poter formattare l'output del programma in +maniera utilizzabile da un browser, si provvede anche \texttt{\small 40}) alla +scrittura dell'apposita stringa di identificazione del mime-type in testa allo +standard output. A questo punto si può invocare \texttt{\small 41}) \cmd{gs}, +provvedendo gli appositi switch che consentono di leggere il file da +convertire dallo standard input e di inviare la conversione sullo standard +output. + +Per completare le operazioni il processo padre chiude (\texttt{\small 44}) il +capo in scrittura della seconda pipe, e attende la conclusione del figlio +(\texttt{\small 45}); a questo punto può (\texttt{\small 46}) uscire. Si tenga +conto che l'operazione di chiudere il capo in scrittura della seconda pipe è +necessaria, infatti, se non venisse chiusa, \cmd{gs}, che legge il suo +standard input da detta pipe, resterebbe bloccato in attesa di ulteriori dati +in ingresso (l'unico modo che un programma ha per sapere che l'input è +terminato è rilevare che lo standard input è stato chiuso), e la \func{wait} +non ritornerebbe. + + +\subsection{Le funzioni \func{popen} e \func{pclose}} +\label{sec:ipc_popen} + +Come si è visto la modalità più comune di utilizzo di una pipe è quella di +utilizzarla per fare da tramite fra output ed input di due programmi invocati +in sequenza; per questo motivo lo standard POSIX.2 ha introdotto due funzioni +che permettono di sintetizzare queste operazioni. La prima di esse si chiama +\func{popen} ed il suo prototipo è: +\begin{prototype}{stdio.h} +{FILE *popen(const char *command, const char *type)} + +Esegue il programma \param{command}, di cui, a seconda di \param{type}, +restituisce, lo standard input o lo standard output nella pipe collegata allo +stream restituito come valore di ritorno. + +\bodydesc{La funzione restituisce l'indirizzo dello stream associato alla pipe + in caso di successo e \macro{NULL} per un errore, nel qual caso \var{errno} + potrà assumere i valori relativi alle sottostanti invocazioni di \func{pipe} + e \func{fork} o \macro{EINVAL} se \param{type} non è valido.} +\end{prototype} + +La funzione crea una pipe, esegue una \func{fork}, ed invoca il programma +\param{command} attraverso la shell (in sostanza esegue \file{/bin/sh} con il +flag \code{-c}); l'argomento \param{type} deve essere una delle due stringhe +\verb|"w"| o \verb|"r"|, per indicare se la pipe sarà collegata allo standard +input o allo standard output del comando invocato. + +La funzione restituisce il puntatore allo stream associato alla pipe creata, +che sarà aperto in sola lettura (e quindi associato allo standard output del +programma indicato) in caso si sia indicato \code{"r"}, o in sola scrittura (e +quindi associato allo standard input) in caso di \code{"w"}. + +Lo stream restituito da \func{popen} è identico a tutti gli effetti ai file +stream visti in \secref{cha:files_std_interface}, anche se è collegato ad una +pipe e non ad un inode, e viene sempre aperto in modalità +\textit{fully-buffered} (vedi \secref{sec:file_buffering}); l'unica differenza +con gli usuali stream è che dovrà essere chiuso dalla seconda delle due nuove +funzioni, \func{pclose}, il cui prototipo è: +\begin{prototype}{stdio.h} +{int pclose(FILE *stream)} + +Chiude il file \param{stream}, restituito da una precedente \func{popen} +attendendo la terminazione del processo ad essa associato. + +\bodydesc{La funzione restituisce 0 in caso di successo e -1 in caso di + errore; nel quel caso il valore di \func{errno} deriva dalle sottostanti + chiamate.} +\end{prototype} +\noindent che oltre alla chiusura dello stream si incarica anche di attendere +(tramite \func{wait4}) la conclusione del processo creato dalla precedente +\func{popen}. + +Per illustrare l'uso di queste due funzioni riprendiamo il problema +precedente: il programma mostrato in \figref{fig:ipc_barcodepage_code} per +quanto funzionante, è (volutamente) codificato in maniera piuttosto complessa, +inoltre nella pratica sconta un problema di \cmd{gs} che non è in +grado\footnote{nella versione GNU Ghostscript 6.53 (2002-02-13).} di +riconoscere correttamente l'encapsulated postscript, per cui deve essere usato +il postscript e tutte le volte viene generata una pagina intera, invece che +una immagine delle dimensioni corrispondenti al codice a barre. + +Se si vuole generare una immagine di dimensioni appropriate si deve usare un +approccio diverso. Una possibilità sarebbe quella di ricorrere ad ulteriore +programma, \cmd{epstopsf}, per convertire in PDF un file EPS (che può essere +generato da \cmd{barcode} utilizzando lo switch \cmd{-E}). Utilizzando un PDF +al posto di un EPS \cmd{gs} esegue la conversione rispettando le dimensioni +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 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 +concatenazione di vari programmi funzionerà soltanto quando tutti prevedono +una lettura sequenziale del loro input. + +Per questo motivo si è dovuto utilizzare una strada diversa, che prevede la +conversione attraverso \cmd{gs} del PS in un altro formato intermedio, il +PPM,\footnote{il \textit{Portable PixMap file format} è un formato usato + spesso come formato intermedio per effettuare conversioni, è estremamente + inefficiente, ma molto facile da manipolare dato che usa caratteri ASCII per + memorizzare le immagini.} dal quale poi si può ottenere un'immagine di +dimensioni corrette attraverso vari programmi di manipolazione (\cmd{pnmcrop}, +\cmd{pnmmargin}) che può essere infine trasformata in PNG (con \cmd{pnm2png}). + +In questo caso però occorre eseguire in sequenza ben quattro comandi diversi, +inviando l'output di ciascuno all'input del successivo, per poi ottenere il +risultato finale sullo standard output: un caso classico di utilizzazione +delle pipe, in cui l'uso di \func{popen} e \func{pclose} permette di +semplificare notevolmente la stesura del codice. + +Nel nostro caso, dato che ciascun processo deve scrivere il suo output sullo +standard input del successivo, occorrerà usare \func{popen} aprendo la pipe in +scrittura. Il codice del nuovo programma è riportato in +\figref{fig:ipc_barcode_code}. Come si può notare l'ordine di invocazione dei +programmi è l'inverso di quello in cui ci si aspetta che vengano +effettivamente eseguiti. Questo non comporta nessun problema dato che la +lettura su una pipe è bloccante, per cui ciascun processo, per quanto lanciato +per primo, si bloccherà in attesa di ricevere sullo standard input il +risultato dell'elaborazione del precedente, benchè quest'ultimo venga +invocato dopo. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \begin{lstlisting}{} +int main(int argc, char *argv[], char *envp[]) +{ + FILE *pipe[4]; + FILE *pipein; + char *cmd_string[4]={ + "pnmtopng", + "pnmmargin -white 10", + "pnmcrop", + "gs -sDEVICE=ppmraw -sOutputFile=- -sNOPAUSE -q - -c showpage -c quit" + }; + char content[]="Content-type: image/png\n\n"; + int i; + /* write mime-type to stout */ + write(STDOUT_FILENO, content, strlen(content)); + /* execute chain of command */ + for (i=0; i<4; i++) { + pipe[i] = popen(cmd_string[i], "w"); + dup2(fileno(pipe[i]), STDOUT_FILENO); + } + /* create barcode (in PS) */ + pipein = popen("barcode", "w"); + /* send barcode string to barcode program */ + write(fileno(pipein), argv[1], strlen(argv[1])); + /* close all pipes (in reverse order) */ + for (i=4; i==0; i--) { + pclose((pipe[i])); + } + exit(0); +} + \end{lstlisting} + \end{minipage} + \normalsize + \caption{Codice completo del \textit{CGI} \file{BarCode.c}.} + \label{fig:ipc_barcode_code} +\end{figure} +Nel nostro caso il primo passo (\texttt{\small 14}) è scrivere il mime-type +sullo standard output; a questo punto il processo padre non necessita più di +eseguire ulteriori operazioni sullo standard output e può tranquillamente +provvedere alla redirezione. + +Dato che i vari programmi devono essere lanciati in successione, si è +approntato un ciclo (\texttt{\small 15--19}) che esegue le operazioni in +sequenza: prima crea una pipe (\texttt{\small 17}) per la scrittura eseguendo +il programma con \func{popen}, in modo che essa sia collegata allo standard +input, e poi redirige (\texttt{\small 18}) lo standard output su detta pipe. + +In questo modo il primo processo ad essere invocato (che è l'ultimo della +catena) scriverà ancora sullo standard output del processo padre, ma i +successivi, a causa di questa redirezione, scriveranno sulla pipe associata +allo standard input del processo invocato nel ciclo precedente. + +Alla fine tutto quello che resta da fare è lanciare (\texttt{\small 21}) il +primo processo della catena, che nel caso è \cmd{barcode}, e scrivere +(\texttt{\small 23}) la stringa del codice a barre sulla pipe, che è collegata +al suo standard input, infine si può eseguire (\texttt{\small 24--27}) un +ciclo, che chiuda, nell'ordine inverso rispetto a quello in cui le si sono +create, tutte le pipe create con \func{pclose}. \subsection{Le \textit{pipe} con nome, o \textit{fifo}} \label{sec:ipc_named_pipe} -Per poter superare il problema delle \textit{pipe} originali, che consentono -la comunicazione solo fra processi correlati, passando attraverso strutture -interne del kernel, sono stati introdotti dei nuovi oggetti, le \textit{fifo}, -che invece possono risiedere sul filesystem, e che i processi possono usare -per le comunicazioni senza dovere per forza essere in relazione diretta. - +Come accennato in \secref{sec:ipc_pipes} il problema delle \textit{pipe} è che +esse possono essere utilizzate solo da processi con un progenitore comune o +nella relazione padre/figlio; per superare questo problema lo standard POSIX.1 +ha definito dei nuovi oggetti, le \textit{fifo}, che hanno le stesse +caratteristiche delle pipe, ma che invece di essere strutture interne del +kernel, visibili solo attraverso un file descriptor, sono accessibili +attraverso un inode che risiede sul filesystem, così che i processi le possono +usare senza dovere per forza essere in una relazione di \textsl{parentela}. + +Utilizzando una \textit{fifo} tutti i dati passeranno, come per le pipe, +attraverso un apposito buffer nel kernel, senza transitare dal filesystem; +l'inode allocato sul filesystem serve infatti solo a fornire un punto di +riferimento per i processi, che permetta loro di accedere alla stessa fifo; il +comportamento delle funzioni di lettura e scrittura è identico a quello +illustrato per le pipe in \secref{sec:ipc_pipes}. + +Abbiamo già visto in \secref{sec:file_mknod} le funzioni \func{mknod} e +\func{mkfifo} che permettono di creare una fifo; per utilizzarne una un +processo non avrà che da aprire il relativo file speciale o in lettura o +scrittura; nel primo caso sarà collegato al capo di uscita della fifo, e dovrà +leggere, nel secondo al capo di ingresso, e dovrà scrivere. + +Il kernel crea una singola pipe per ciascuna fifo che sia stata aperta, che può +essere acceduta contemporaneamente da più processi, sia in lettura che in +scrittura. Dato che per funzionare deve essere aperta in entrambe le +direzioni, per una fifo di norma la funzione \func{open} si blocca se viene +eseguita quando l'altro capo non è aperto. + +Le fifo però possono essere anche aperte in modalità \textsl{non-bloccante}, +nel qual caso l'apertura del capo in lettura avrà successo solo quando anche +l'altro capo è aperto, mentre l'apertura del capo in scrittura restituirà +l'errore di \macro{ENXIO} fintanto che non verrà aperto il capo in lettura. + +In Linux\footnote{lo standard POSIX lascia indefinito questo comportamento.} è +possibile aprire le fifo anche in lettura/scrittura, operazione che avrà +sempre successo immediato qualunque sia la modalità di apertura (bloccante e +non bloccante); questo può essere utilizzato per aprire comunque una fifo in +scrittura anche se non ci sono ancora processi il lettura; è possibile anche +usare la fifo all'interno di un solo processo, nel qual caso però occorre +stare molto attenti alla possibili deadlock.\footnote{se si cerca di leggere + da una fifo che non contiene dati si avrà un deadlock immediato, dato che il + processo si blocca e non potrà quindi mai eseguire le funzioni di + scrittura.} + +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} \label{sec:ipc_sysv} -Per ovviare ai vari limiti dei meccanismo tradizionale di comunicazione fra -processi visto in \secref{sec:ipc_unix}, nello sviluppo di System V vennero -introdotti una serie di nuovi oggetti e relative interdacce che garantissero -una maggiore flessibilità; in questa sezione esamineremo quello che viene -ormai chiamato il \textit{System V Inter-Process Comunication System}, più -comunemente noto come \textit{SystemV IPC}. - +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; 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. + + + \subsection{Code di messaggi} \label{sec:ipc_messque} -Il primo oggetto introdotto dal \textit{SystemV IPC} è quello delle code di +Il primo oggetto introdotto dal \textit{System V IPC} è quello delle code di messaggi. \subsection{Semafori} \label{sec:ipc_semaph} -Il secondo oggetto introdotto dal \textit{SystemV IPC} è quello dei semafori. +Il secondo oggetto introdotto dal \textit{System V IPC} è quello dei semafori. \subsection{Memoria condivisa} \label{sec:ipc_shar_mem} -Il terzo oggetto introdotto dal \textit{SystemV IPC} è quello della memoria +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"