X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=ipc.tex;h=b5e7981f69c17cfbb835dccc66e431d374ec811c;hp=0b7818c00a9e8ae83a62f18c426ce5f689db6452;hb=7d036afbf1cd25abdb0d6b5ea78bc6dca461719a;hpb=a29d9eebcd859ca662351848cc856e69f50c3ece diff --git a/ipc.tex b/ipc.tex index 0b7818c..b5e7981 100644 --- a/ipc.tex +++ b/ipc.tex @@ -6,26 +6,24 @@ 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 -dettaglio in un secondo tempo. Non affronteremo invece meccanismi più +dettaglio in un secondo tempo. Non affronteremo neanche meccanismi più complessi ed evoluti come le RPC (\textit{Remote Procedure Calls}) e CORBA (\textit{Common Object Request Brocker Architecture}) che in genere sono 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 +31,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 nel vettore +\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,81 +69,112 @@ 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. \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 facilmente 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 essere \textsl{parenti} (dall'inglese \textit{siblings}), cioè +o 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 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-bin}\footnote{NdA, inserire una breve descrizione di cosa è un - cgi-bin.} 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-bin} per apache 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: +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 + 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 apache possa -reinviarlo al browser che ha effettuato la richiesta. +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. -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. +\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 il -\textit{cgi-bin} deve poter gestire più richieste in concorrenza, e si avrebbe -una evidente race condition in caso di accesso simultaneo a detto -file.\footnote{la questione potrebbe essere evitata creando prima dei file - temporanei, da comunicare poi ai vari sotto-processi, da cancellare alla - fine dell'esecuzione; ma a questo punto avremmo perso tutta la semplicità.} -L'uso di una pipe invece permette di risolvere il problema in maniera semplice -ed elegante. - -Il programma ci servirà anche come esempio dell'uso di alcune delle funzioni -di manipolazione dei file descriptor, come \func{dup} e \func{dup2}, viste in -\secref{sec:file_dup}; è attraverso queste funzioni 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. Le -sezioni significative del programma è riportato in -\figref{fig:ipc_barcode_code}, il codice è disponibile nel file -\file{BarCode.c} nella directory dei sorgenti. +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] @@ -158,7 +184,7 @@ sezioni significative del programma 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); @@ -178,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); } @@ -198,60 +224,85 @@ int main(int argc, char *argv[], char *envp[]) execlp("gs", "gs", "-q", "-sDEVICE=jpeg", "-sOutputFile=-", "-", NULL); } /* still parent */ - close(pipeout[1]); + close(pipeout[1]); waitpid(pid, NULL, 0); exit(0); } \end{lstlisting} \end{minipage} \normalsize - \caption{Codice del \textit{cgi-bin} \cmd{BarCode}.} - \label{fig:ipc_barcode_code} + \caption{Sezione principale del codice del \textit{CGI} + \file{BarCodePage.c}.} + \label{fig:ipc_barcodepage_code} \end{figure} - -Il primo passo (\texttt{\small 4-12}) è quello di creare le due pipe che -servono per la comunicazione fra i due programmi che verranno utilizzati per -produrre il codice a barre; si ha cura di controllare la riuscita della +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 - \ref{fig:ipc_barcode_code}, ma si incarica semplicemente di formattare - l'uscita, aggiungendo un \textit{mime type}, in modo che possa essere - interpretata direttamente da un browser.} +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 -il programma \cmd{barcode}: quest'ultimo funziona ricevendo dallo standard -input la stringa da convertire nell'immagine postscript del codice a barre, -che sarà scritta sullo standard output. +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 utilizzare queste caratteristiche il primo figlio chiude (\texttt{\small - 21}) il capo aperto in scrittura della prima pipe, dato che userà il capo -aperto in lettura per ricevere dal padre la stringa da codificare; per far -questo collega (\texttt{\small 22}) il capo in lettura allo standard input -usando \func{dup2}; +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. -Analogamente il capo in lettura della seconda pipe sarà chiuso mentre il capo -in scrittura viene collegato allo standard output (\texttt{\small 23-24}. In -questo modo all'esecuzione (\texttt{\small 25}) di \cmd{barcode} quest'ultimo -leggerà la stringa da codificare dalla prima pipe e scriverà l'immagine -postscript nella seconda. - -Dall'altra parte il processo padre prima chiude (\texttt{\small 28-29}) i due -capi inutilizzati delle pipe (input della prima ed output della seconda), poi -scrive (\texttt{\small 30}) la stringa da convertire sull'output della prima -pipe così che \cmd{barcode} possa riceverla dallo standard input; a questo -punto l'uso della prima pipe è finito ed essa può essere definitivamente -chiusa (\texttt{\small 31}), si attenderà poi (\texttt{\small 32}) che -l'esecuzione di \cmd{barcode} venga completata. - -Alla conclusione della sua esecuzione \cmd{barcode} avrà effettuato inviato -l'immagine postscript del codice a barre sul capo in scrittura della seconda -pipe; a questo punto +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}} @@ -260,65 +311,1135 @@ pipe; a questo punto 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 comuni in una sola -chiamata. La prima di esse si chiama \func{popen} ed il suo prototipo è: +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)} -L'esempio in \figref{fig:ipc_barcode_code} per quanto perfettamente -funzionante, è piuttosto complesso; 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 tutte le volte generata una pagina intera, invece che una semplice figura. -Se si vuole generare una immagine di dimensioni corrette si deve allora -ricorrere ad ulteriore programma, \cmd{epstopsf}, per convertire in PDF il -file EPS generato da \cmd{barcode}, che invece viene trattato correttamente. +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 di dimensioni corrette. +Questo approccio però non funziona, per via di una delle caratteristiche +principali delle pipe. Per poter effettuare la conversione di un PDF infatti è +necessario, per la struttura del formato, potersi spostare (con \func{lseek}) +all'interno del file da convertire; se si esegue la conversione con \cmd{gs} su +un file regolare non ci sono problemi, una pipe però è rigidamente +sequenziale, e l'uso di \func{lseek} su di essa fallisce sempre con un errore +di \macro{ESPIPE}, rendendo impossibile la conversione. 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 un procedimento diverso, eseguendo +prima la conversione (sempre con \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, è + infatti molto facile da manipolare, dato che usa caratteri ASCII per + memorizzare le immagini, anche se per questo è estremamente inefficiente.} +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}, illustrato in -\secref{sec:ipc_pipes}, che ne consente l'uso solo fra processi con un -progenitore comune o nella relazione padre/figlio, lo standard POSIX.1 -definisce 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 è possibile aprire le fifo anche in lettura/scrittura,\footnote{lo + standard POSIX lascia indefinito il comportamento in questo caso.} +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 +situazioni di stallo.\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 da 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 caso 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 client ed server (il modello + \textit{client-server} è illustrato in \secref{sec:net_cliserv}). +\end{itemize} + +Nel primo caso quello che si fa è creare tante fifo, da usare come standard +input, quanti sono i processi a cui i vogliono inviare i dati, questi ultimi +saranno stati posti in esecuzione ridirigendo lo standard input dalle fifo, si +potrà poi eseguire il processo che fornisce l'output replicando quest'ultimo, +con il comando \cmd{tee}, sulle varie fifo. + +Il secondo caso è relativamente semplice qualora si debba comunicare con un +processo alla volta (nel qual caso basta usare due fifo, 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 in \figref{fig:ipc_fifo_server_arch} in cui i client inviano le +richieste al server su una fifo nota mentre le risposte vengono reinviate dal +server a ciascuno di essi su una fifo temporanea creata per l'occazione. + +\begin{figure}[htb] + \centering + \includegraphics[height=9cm]{img/fifoserver} + \caption{Schema dell'utilizzo delle fifo nella realizzazione di una + architettura di comunicazione client/server.} + \label{fig:ipc_fifo_server_arch} +\end{figure} + +Come esempio di uso questa architettura e dell'uso delle fifo, abbiamo scritto +un server di \textit{fortunes}, che restituisce, alle richieste di un client, +un detto a caso estratto da un insieme di frasi; sia il numero delle frasi +dell'insieme, che i file da cui esse vengono lette all'avvio, sono importabili +da riga di comando. Il corpo principale del server è riportato in +\figref{fig:ipc_fifo_server}, dove si è tralasciata la parte che tratta la +gestione delle opzioni a riga di comando, che effettua il settaggio delle +variabili \var{fortunefilename}, che indica il file da cui leggere le frasi, +ed \var{n}, che indica il numero di frasi tenute in memoria, ad un valore +diverso da quelli preimpostati. Il codice completo è nel file +\file{FortuneServer.c}. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \begin{lstlisting}{} +char *fifoname = "/tmp/fortune.fifo"; +int main(int argc, char *argv[]) +{ +/* Variables definition */ + int i, n = 0; + char *fortunefilename = "/usr/share/games/fortunes/italia"; + char **fortune; + char line[80]; + int fifo_server, fifo_client; + int nread; + ... + if (n==0) usage(); /* if no pool depth exit printing usage info */ + Signal(SIGTERM, HandSIGTERM); /* set handlers for termination */ + Signal(SIGINT, HandSIGTERM); + Signal(SIGQUIT, HandSIGTERM); + i = FortuneParse(fortunefilename, fortune, n); /* parse phrases */ + if (mkfifo(fifoname, 0622)) { /* create well known fifo if does't exist */ + if (errno!=EEXIST) { + perror("Cannot create well known fifo"); + exit(1); + } + } + /* open fifo two times to avoid EOF */ + fifo_server = open(fifoname, O_RDONLY); + if (fifo_server < 0) { + perror("Cannot open read only well known fifo"); + exit(1); + } + if (open(fifoname, O_WRONLY) < 0) { + perror("Cannot open write only well known fifo"); + exit(1); + } + /* Main body: loop over requests */ + while (1) { + nread = read(fifo_server, line, 79); /* read request */ + if (nread < 0) { + perror("Read Error"); + exit(1); + } + line[nread] = 0; /* terminate fifo name string */ + n = random() % i; /* select random value */ + fifo_client = open(line, O_WRONLY); /* open client fifo */ + if (fifo_client < 0) { + perror("Cannot open"); + exit(1); + } + nread = write(fifo_client, /* write phrase */ + fortune[n], strlen(fortune[n])+1); + close(fifo_client); /* close client fifo */ + } +} + \end{lstlisting} + \end{minipage} + \normalsize + \caption{Sezione principale del codice del server di \textit{fortunes} + basato sulle fifo.} + \label{fig:ipc_fifo_server} +\end{figure} + +Il server richiede (\texttt{\small 12}) che sia stata impostata una dimensione +dell'insieme delle frasi non nulla, dato che l'inizializzazione del vettore +\var{fortune} avviene solo quando questa dimensione viene specificata, la +presenza di un valore nullo provoca l'uscita dal programma attraverso la +routine (non riportata) che ne stampa le modalità d'uso. Dopo di che installa +(\texttt{\small 13--15}) la funzione che gestisce i segnali di interruzione +(anche questa non è riportata in \figref{fig:ipc_fifo_server}) che si limita a +rimuovere dal filesystem la fifo usata dal server per comunicare. + +Terminata l'inizializzazione (\texttt{\small 16}) si effettua la chiamata alla +funzione \code{FortuneParse} che legge dal file specificato in +\var{fortunefilename} le prime \var{n} frasi e le memorizza (allocando +dinamicamente la memoria necessaria) nel vettore di puntatori \var{fortune}. +Anche il codice della funzione non è riportato, in quanto non direttamente +attinente allo scopo dell'esempio. + +Il passo successivo (\texttt{\small 17--22}) è quello di creare con +\func{mkfifo} la fifo nota sulla quale il server ascolterà le richieste, +qualora si riscontri un errore il server uscirà (escludendo ovviamente il caso +in cui la funzione \func{mkfifo} fallisce per la precedente esistenza della +fifo). + +Una volta che si è certi che la fifo di ascolto esiste si procede +(\texttt{\small 23--32}) alla sua apertura. Questo viene fatto due volte +per evitare di dover gestire all'interno del ciclo principale il caso in cui +il server è in ascolto ma non ci sono client che effettuano richieste. +Si ricordi infatti che quando una fifo è aperta solo dal capo in lettura, +l'esecuzione di \func{read} ritorna con zero byte (si ha cioè una condizione +di end-of-file). + +Nel nostro caso la prima apertura si bloccherà fintanto che un qualunque +client non apre a sua volta la fifo nota in scrittura per effettuare la sua +richiesta. Pertanto all'inizio non ci sono probelmi, il client però, una volta +ricevuta la risposta, uscirà, chiudendo tutti i file aperti, compresa la fifo. +A questo punto il server resta (se non ci sono altri client che stanno +effettuando richieste) con la fifo chiusa sul lato in lettura e a questo punto +\func{read} non si bloccherà in attesa di input, ma ritornerà in continuazione +restituendo un end-of-file.\footnote{Si è usata questa tecnica per + compatibilità, Linux infatti supporta l'apertura delle fifo in + lettura/scrittura, per cui si sarebbe potuto effettuare una singola apertura + con \macro{O\_RDWR}, la doppia apertura comunque ha il vantaggio che non si + può scrivere per errore sul capo aperto in sola lettura.} + +Per questo motivo, dopo aver eseguito l'apertura in lettura (\texttt{\small + 24--28}),\footnote{di solito si effettua l'apertura del capo in lettura in + modalità non bloccante, per evitare il rischio di uno stallo (se nessuno + apre la fifo in scrittura il processo non ritornerà mai dalla \func{open}) + che nel nostro caso non esiste, mentre è necessario potersi bloccare in + lettura in attesa di una richiesta.} si esegue una seconda apertura in +scrittura (\texttt{\small 29--32}), scartando il relativo file descriptor che +non sarà mai usato, ma lasciando la fifo comunque aperta anche in scrittura, +cosicché le successive possano bloccarsi. + +A questo punto si può entrare nel ciclo principale del programma che fornisce +le risposte ai client (\texttt{\small 34--50}), che viene eseguito +indefinitamente (l'uscita del server viene effettuata inviando un segnale, in +modo da passare attraverso la routine di chiusura che cancella la fifo). + +Il server è progettato per accettare come richieste dai client delle stringhe +che contengono il nome della fifo sulla quale deve essere inviata la risposta. +Per cui prima (\texttt{\small 35--39}) si esegue la lettura dalla stringa di +richiesta dalla fifo nota (che a questo punto si bloccherà tutte le volte che +non ci sono richieste). Dopo di che, una volta terminata la stringa +(\texttt{\small 40}) e selezionato (\texttt{\small 41}) un numero casuale per +ricavare la frase da inviare, si procederà (\texttt{\small 42--46}) +all'apertura della fifo per la risposta, che \texttt{\small 47--48}) poi vi +sarà scritta. Infine (\texttt{\small 49}) si chiude la fifo di risposta che +non serve più. + +Il codice del client è invece riportato in \figref{fig:ipc_fifo_client}, anche +in questo caso si è omessa la gestione delle opzioni e la funzione che stampa +a video le informazioni di utilizzo ed esce, riportando solo la sezione +principale del programma e le definizioni delle variabili. Il codice completo +è nel file \file{FortuneClient.c} dei sorgenti allegati. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \begin{lstlisting}{} +int main(int argc, char *argv[]) +{ +/* Variables definition */ + int n = 0; + char *fortunefilename = "/tmp/fortune.fifo"; + char line[80]; + int fifo_server, fifo_client; + char fifoname[80]; + int nread; + char buffer[PIPE_BUF]; + ... + snprintf(fifoname, 80, "/tmp/fortune.%d", getpid()); /* compose name */ + if (mkfifo(fifoname, 0622)) { /* open client fifo */ + if (errno!=EEXIST) { + perror("Cannot create well known fifo"); + exit(-1); + } + } + fifo_server = open(fortunefilename, O_WRONLY); /* open server fifo */ + if (fifo_server < 0) { + perror("Cannot open well known fifo"); + exit(-1); + } + nread = write(fifo_server, fifoname, strlen(fifoname)+1); /* write name */ + close(fifo_server); /* close server fifo */ + fifo_client = open(fifoname, O_RDONLY); /* open client fifo */ + if (fifo_client < 0) { + perror("Cannot open well known fifo"); + exit(-1); + } + nread = read(fifo_client, buffer, sizeof(buffer)); /* read answer */ + printf("%s", buffer); /* print fortune */ + close(fifo_client); /* close client */ + close(fifo_server); /* close server */ + unlink(fifoname); /* remove client fifo */ +} + \end{lstlisting} + \end{minipage} + \normalsize + \caption{Sezione principale del codice del client di \textit{fortunes} + basato sulle fifo.} + \label{fig:ipc_fifo_client} +\end{figure} + +La prima istruzione (\texttt{\small 12}) compone il nome della fifo che dovrà +essere utilizzata per ricevere la risposta dal server. Si usa il \acr{pid} +del processo per essere sicuri di avere un nome univoco; dopo di che +(\texttt{\small 13-18}) si procede alla creazione del relativo file, uscendo +in caso di errore (a meno che il file non sia già presente sul filesystem). + +A questo punto il client può effettuare l'interrogazione del server, per +questo prima si apre la fifo nota (\texttt{\small 19--23}), e poi ci si scrive +(\texttt{\small 24}) la stringa composta in precedenza, che contiene il nome +della fifo da utilizzare per la risposta. Infine si richiude la fifo del +server che a questo punto non serve più (\texttt{\small 25}). + +Inoltrata la richiesta si può passare alla lettura della risposta; anzitutto +si apre (\texttt{\small 26--30}) la fifo appena creata, da cui si deve +riceverla, dopodiché si effettua una lettura (\texttt{\small 31}) +nell'apposito buffer; si è supposto, come è ragionevole, che le frasi inviate +dal server siano sempre di dimensioni inferiori a \macro{PIPE\_BUF}, +tralasciamo la gestione del caso in cui questo non è vero. Infine si stampa +(\texttt{\small 32}) a video la risposta, si chiude (\texttt{\small 33}) la +fifo e si cancella (\texttt{\small 34}) il relativo file. + +Si noti come la fifo per la risposta sia stata aperta solo dopo aver inviato +la richiesta, se non si fosse fatto così si avrebbe avuto uno stallo, in +quanto senza la richiesta, il server non avrebbe potuto aprirne il capo in +scrittura e l'apertura si sarebbe bloccata indefinitamente. + +Benché il nostro sistema client-server funzioni, la sua struttura è piuttosto +complessa e continua ad avere vari inconvenienti\footnote{lo stesso Stevens, + che esamina questa architettura in \cite{APUE}, 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, di come sia necessario + intercettare \macro{SIGPIPE} dato che un client può terminare dopo aver + fatto una richiesta, ma prima che la risposta sia inviata (cosa che nel + nostro esempio non è stata fatta).}; 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 meccanismi di comunicazione diversi, +come quelli che esamineremo in seguito. + + + \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 interfacce 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 scontano il +limite fondamentale che il meccanismo di comunicazione che forniscono è +rigidamente sequenziale: una situazione in cui un processo scrive qualcosa che +molti altri devono poter leggere non può essere implementata con una pipe. + +Per questo nello sviluppo di System V vennero introdotti una serie di nuovi +oggetti per la comunicazione fra processi ed una nuova interfaccia di +programmazione, che fossero in grado di garantire una maggiore flessibilità. +In questa sezione esamineremo come Linux supporta 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, e non vengono cancellati dal sistema una volta che non sono più +in uso. + +Questo comporta due problemi: il primo è che, al contrario di quanto avviene +per pipe e fifo, la memoria allocata per questi oggetti non viene rilasciata +automaticamente quando nessuno li vuole più utilizzare, ed essi devono essere +cancellati esplicitamente, se non si vuole che restino attivi fino al riavvio +del sistema. Il secondo è che, dato che non c'è un contatore di riferimenti, +essi possono essere cancellati anche se ci sono dei processi che li stanno +utilizzando, con tutte le conseguenze (negative) del caso. + +Gli oggetti usati nel System V IPC vengono creati direttamente dal kernel, e +sono accessibili solo specificando il relativo \textsl{identificatore}. Questo +è un numero progressivo (un po' come il \acr{pid} dei processi) che il kernel +assegna a ciascuno di essi quanto vengono creati (sul procedimento di +assegnazione torneremo in \secref{sec:ipc_sysv_id_use}). L'identificatore +viene restituito dalle funzioni che creano l'oggetto, ed è quindi locale al +processo che le ha eseguite. Dato che l'identificatore viene assegnato +dinamicamente dal kernel non è possibile prevedere quale sarà, ne utilizzare +un qualche valore statico, si pone perciò il problema di come processi diversi +possono accedere allo stesso oggetto. + +Per risolvere il problema il kernel associa a ciascun oggetto una struttura +\var{ipc\_perm}; questa contiene una \textsl{chiave}, identificata da una +variabile del tipo primitivo \type{key\_t}, che viene specificata in fase di +creazione e tramite la quale è possibile ricavare l'identificatore. La +struttura, la cui definizione è riportata in \figref{fig:ipc_ipc_perm}, +contiene anche le varie proprietà associate all'oggetto. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \begin{lstlisting}[labelstep=0]{}%,frame=,indent=1cm ]{} +struct ipc_perm +{ + key_t key; /* Key. */ + uid_t uid; /* Owner's user ID. */ + gid_t gid; /* Owner's group ID. */ + uid_t cuid; /* Creator's user ID. */ + gid_t cgid; /* Creator's group ID. */ + unsigned short int mode; /* Read/write permission. */ + unsigned short int seq; /* Sequence number. */ +}; + \end{lstlisting} + \end{minipage} + \normalsize + \caption{La struttura \var{ipc\_perm}, come definita in \file{sys/ipc.h}.} + \label{fig:ipc_ipc_perm} +\end{figure} + +Usando la stessa chiave due processi diversi possono ricavare l'identificatore +associato ad un oggetto ed accedervi. Il problema che sorge a questo punto è +come devono fare per accordarsi sull'uso di una stessa chiave. Se i processi +sono \textsl{parenti} la soluzione è relativamente semplice, in tal caso +infatti si può usare il valore speciale \texttt{IPC\_PRIVATE} per creare un +nuovo oggetto nel processo padre, l'identificatore così ottenuto sarà +disponibile in tutti i figli, e potrà essere passato come parametro attraverso +una \func{exec}. + +Però quando i processi non sono \textsl{parenti} (come capita tutte le volte +che si ha a che fare con un sistema client-server) tutto questo non è +possibile; si potrebbe comunque salvare l'identificatore su un file noto, ma +questo ovviamente comporta lo svantaggio di doverselo andare a rileggere. Una +alternativa più efficace è quella che i programmi usino un valore comune per +la chiave (che ad esempio può essere dichiarato in un header comune), ma c'è +sempre il rischio che questa chiave possa essere stata già utilizzata da +qualcun altro. Dato che non esiste una convenzione su come assegnare queste +chiavi in maniera univoca l'interfaccia mette a disposizione una funzione, +\func{ftok}, che permette di ottenere una chiave specificando il nome di un +file ed un numero di versione; il suo prototipo è: +\begin{functions} + \headdecl{sys/types.h} + \headdecl{sys/ipc.h} + + \funcdecl{key\_t ftok(const char *pathname, int proj\_id)} + + Restituisce una chiave per identificare un oggetto del System V IPC. + + \bodydesc{La funzione restituisce la chiave in caso di successo e -1 + altrimenti, nel qual caso \var{errno} viene settata ad uno dei possibili + codici di errore di \func{stat}.} +\end{functions} + +La funzione determina un valore della chiave sulla base di \param{pathname}, +che deve specificare il pathname di un file effettivamente esistente e di un +numero di progetto \param{proj\_id)}, che di norma viene specificato come +carattere, dato che ne vengono utilizzati solo gli 8 bit meno +significativi.\footnote{nelle libc4 e libc5, come avviene in SunOS, + l'argomento \param{proj\_id} è dichiarato tipo \ctyp{char}, le \acr{glibc} + usano il prototipo specificato da XPG4, ma vengono lo stesso utilizzati gli + 8 bit meno significativi.} + +Il problema è che anche così non c'è la sicurezza che il valore della chiave +sia univoco, infatti esso è costruito combinando il byte di \param{proj\_id)} +con i 16 bit meno significativi dell'inode del file \param{pathname} (che +vengono ottenuti attraverso \func{stat}, da cui derivano i possibili errori), +e gli 8 bit meno significativi del numero del dispositivo su cui è il file. +Diventa perciò relativamente facile ottenere delle collisioni, specie se i +file sono su dispositivi con lo stesso \textit{minor number}, come +\file{/dev/hda1} e \file{/dev/sda1}. + +In genere quello che si fa è utilizzare un file comune usato dai programmi che +devono comunicare (ad esempio un haeder comune, o uno dei programmi che devono +usare l'oggetto in questione), utilizzando il numero di progetto per ottenere +le chiavi che interessano. In ogni caso occorre sempre controllare, prima di +creare un oggetto, che la chiave non sia già stata utilizzata. Se questo va +bene in fase di creazione, le cose possono complicarsi per i programmi che +devono solo accedere, in quanto, a parte gli eventuali controlli sugli altri +attributi di \var{ipc\_perm}, non esiste una modalità semplice per essere +sicuri che l'oggetto associato ad una certa chiave sia stato effettivamente +creato da chi ci si aspetta. + +Questo è, insieme al fatto che gli oggetti sono permanenti e non mantengono un +contatore di riferimenti per la cancellazione automatica, il principale +problema del sistema di IPC di System V. Non esiste infatti una modalità +chiara per identificare un oggetto, come sarebbe stato se lo si fosse +associato ad in file, e tutta l'interfaccia è inutilmente complessa. Per +questo ne è stata effettuata una revisione completa nello standard POSIX.1b, +che tratteremo in \secref{sec:ipc_posix}. + + +\subsection{Il controllo di accesso} +\label{sec:ipc_sysv_access_control} + +Oltre alle chiavi, abbiamo visto che ad ogni oggetto sono associate in +\var{ipc\_perm} ulteriori informazioni, come gli identificatori del creatore +(nei campi \var{cuid} e \var{cgid}) e del proprietario (nei campi \var{uid} e +\var{gid}) dello stesso, e un insieme di permessi (nel campo \var{mode}). In +questo modo è possibile definire un controllo di accesso sugli oggetti di IPC, +simile a quello che si ha per i file (vedi \secref{sec:file_perm_overview}). + +Benché questo controllo di accesso sia molto simile a quello dei file, restano +delle importanti differenze. La prima è che il permesso di esecuzione non +esiste (e se specificato viene ignorato), per cui si può parlare solo di +permessi di lettura e scrittura (nel caso dei semafori poi quest'ultimo è più +propriamente un permesso di modifica). I valori di \var{mode} sono gli stessi +ed hanno lo stesso significato di quelli riportati in +\secref{tab:file_mode_flags}\footnote{se però si vogliono usare le costanti + simboliche ivi definite occorrerà includere il file \file{sys/stat.h}, + alcuni sistemi definiscono le costanti \macro{MSG\_R} (\texttt{0400}) e + \macro{MSG\_W} (\texttt{0200}) per indicare i permessi base di lettura e + scrittura per il proprietario, da utilizzare, con gli opportuni shift, pure + per il gruppo e gli altri, in Linux, visto la loro scarsa utilità, queste + costanti non sono definite.} e come per i file definiscono gli accessi per +il proprietario, il suo gruppo e tutti gli altri. + +Quando l'oggetto viene creato i campi \var{cuid} e \var{uid} di +\var{ipc\_perm} ed i campi \var{cgid} e \var{gid} vengono settati +rispettivamente al valore dell'userid e del groupid effettivo del processo che +ha chiamato la funzione, ma, mentre i campi \var{uid} e \var{gid} possono +essere cambiati, i campi \var{cuid} e \var{cgid} restano sempre gli stessi. + +Il controllo di accesso è effettuato a due livelli. Il primo livello è nelle +funzioni che richiedono l'identificatore di un oggetto data la chiave. Queste +specificano tutte un argomento \param{flag}, in tal caso quando viene +effettuata la ricerca di una chiave, qualora \param{flag} specifichi dei +permessi, questi vengono controllati e l'identificatore viene restituito solo +se corrispondono a quelli dell'oggetto. Se ci sono dei permessi non presenti +in \var{mode} l'accesso sarà negato. Questo controllo però è di utilità +indicativa, dato che è sempre possibile specificare per \param{flag} un valore +nullo, nel qual caso l'identificatore sarà restituito comunque. + +Il secondo livello di controllo è quello delle varie funzioni che accedono +direttamente (in lettura o scrittura) all'oggetto. In tal caso lo schema dei +controlli è simile a quello dei file, ed avviene secondo questa sequenza: +\begin{itemize} +\item se il processo ha i privilegi di amministratore l'accesso è sempre + consentito. +\item se l'userid effettivo del processo corrisponde o al valore del campo + \var{cuid} o a quello del campo \var{uid} ed il permesso per il proprietario + in \var{mode} è appropriato\footnote{per appropriato si intende che è + settato il permesso di scrittura per le operazioni di scrittura e quello + di lettura per le operazioni di lettura.} l'accesso è consentito. +\item se il groupid effettivo del processo corrisponde o al + valore del campo \var{cgid} o a quello del campo \var{gid} ed il permesso + per il gruppo in \var{mode} è appropriato l'accesso è consentito. +\item se il permesso per gli altri è appropriato l'accesso è consentito. +\end{itemize} +solo se tutti i controlli elencati falliscono l'accesso è negato. Si noti che +a differenza di quanto avviene per i permessi dei file, fallire in uno dei +passi elencati non comporta il fallimento dell'accesso. Un'ulteriore +differenza rispetto a quanto avviene per i file è che per gli oggetti di IPC +il valore di \var{umask} (si ricordi quanto esposto in +\secref{sec:file_umask}) non ha alcun significato. + + +\subsection{Gli identificatori ed il loro utilizzo} +\label{sec:ipc_sysv_id_use} + +L'unico campo di \var{ipc\_perm} del quale non abbiamo ancora parlato è +\var{seq}, che in \figref{fig:ipc_ipc_perm} è qualificato con un criptico +``\textsl{numero di sequenza}'', ne parliamo adesso dato che esso è +strettamente attinente alle modalità con cui il kernel assegna gli +identificatori degli oggetti del sistema di IPC. + +Quando il sistema si avvia, alla creazione di ogni nuovo oggetto di IPC viene +assegnato un numero progressivo, pari al numero di oggetti di quel tipo +esistenti. Se il comportamento fosse sempre questo sarebbe identico a quello +usato nell'assegnazione dei file descriptor nei processi, ed i valori degli +identificatori tenderebbero ad essere riutilizzati spesso e restare di piccole +dimensioni (inferiori al numero massimo di oggetti disponibili). + +Questo va benissimo nel caso dei file descriptor, che sono locali ad un +processo, ma qui il comportamento varrebbe per tutto il sistema, e per +processi del tutto scorrelati fra loro. Così si potrebbero avere situazioni +come quella in cui un server esce e cancella le sue code di messaggi, ed il +relativo identificatore viene immediatamente assegnato a quelle di un altro +server partito subito dopo, con la possibilità che i client del primo non +facciano in tempo ad accorgersi dell'avvenuto, e finiscano con l'interagire +con gli oggetti del secondo, con conseguenze imprevedibili. + +Proprio per evitare questo tipo di situazioni il sistema usa il valore di +\var{req} per provvedere un meccanismo che porti gli identificatori ad +assumere tutti i valori possibili, rendendo molto più lungo il periodo in cui +un identificatore può venire riutilizzato. + +Il sistema dispone sempre di un numero fisso di oggetti di IPC,\footnote{fino + al kernel 2.2.x questi valori, definiti dalle costanti \macro{MSGMNI}, + \macro{SEMMNI} e \macro{SHMMNI}, potevano essere cambiati (come tutti gli + altri limiti relativi al \textit{System V IPC}) solo con una ricompilazione + del kernel, andando a modificarne la definizione nei relativi haeder file. + A partire dal kernel 2.4.x è possibile cambiare questi valori a sistema + attivo scrivendo sui file \file{shmmni}, \file{msgmni} e \file{sem} di + \file{/proc/sys/kernel} o con l'uso di \texttt{syscntl}.} e per ciascuno di +essi viene mantenuto in \var{seq} un numero di sequenza progressivo che viene +incrementato di uno ogni volta che l'oggetto viene cancellato. Quando +l'oggetto viene creato usando uno spazio che era già stato utilizzato in +precedenza per restituire l'identificatore al numero di oggetti presenti viene +sommato il valore di \var{seq} moltiplicato per il numero massimo di oggetti +di quel tipo,\footnote{questo vale fino ai kernel della serie 2.2.x, dalla + serie 2.4.x viene usato lo stesso fattore per tutti gli oggetti, esso è dato + dalla costante \macro{IPCMNI}, definita in \file{include/linux/ipc.h}, che + indica il limite massimo per il numero di tutti oggetti di IPC, ed il cui + valore è 32768.} si evita così il riutilizzo degli stessi numeri, e si fa +sì che l'identificatore assuma tutti i valori possibili. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \begin{lstlisting}{} +int main(int argc, char *argv[]) +{ + ... + switch (type) { + case 'q': /* Message Queue */ + debug("Message Queue Try\n"); + for (i=0; i