X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=ipc.tex;h=898305fdd703de19aeb56ca31e5e53d9f23f2bb6;hp=6c1041ef5271eadb33f5027e1b0fd98d9080868d;hb=9790ea16d447835b894169fa997314fd5fb0ebd5;hpb=525464ecdd7e6cbdccc733e73b5f1cc9dbe6022a diff --git a/ipc.tex b/ipc.tex index 6c1041e..898305f 100644 --- a/ipc.tex +++ b/ipc.tex @@ -3,72 +3,385 @@ Uno degli aspetti fondamentali della programmazione in un sistema unix-like è -la comunicazione fra processi. In questo capitolo affronteremo solo alcuni dei +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. -Esistono pure sistemi più complessi ed evoluti come le RPC (\textit{Remote - Procedure Calls}) e CORBA (\textit{Common Object Request Brocker - Architecture}) che non saranno affrontati qui. Inoltre tratteremo nei -capitoli successivi tutta la problematica relativa alla comunicazione -attraverso la rete. +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ù +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 -è quello delle \textit{pipe}, 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 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. \subsection{Le \textit{pipe} standard} \label{sec:ipc_pipes} -Le \textit{pipe} nascono sostanzialmente con Unix, e sono il primo, e più -comunemente usato, meccanismo di comunicazione fra processi. Esse sono un file -speciale 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 immette dati che arriveranno -all'altro. +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 è: +\begin{prototype}{unistd.h} +{int pipe(int filedes[2])} + +Crea una coppia di file descriptor associati ad una 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 +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. + +\begin{figure}[htb] + \centering + \includegraphics[height=5cm]{img/pipe} + \caption{Schema della struttura di una 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 +\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}.} + \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 + in \cite{APUE} riporta come limite anche il fatto che la comunicazione è + unidirezionale, 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. + + + +\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 +è il loro uso più comune, analogo a quello effettuato della shell, e che +consiste nell'inviare l'output di un processo (lo standard output) sull'input +di un'altro. Realizzaremo il programma nella forma di un +\textit{cgi}\footnote{NdA, inserire una breve descrizione di cosa è un cgi.} +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} 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: +\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 apache possa +reinviarlo al browser che ha effettuato la richiesta. + + +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. + +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} 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. + + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \begin{lstlisting}{} +int main(int argc, char *argv[], char *envp[]) +{ + ... + /* create two pipes to handle process 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); //"-o", "-", 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 */ + 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{Codice del \textit{cgi-bin} \cmd{BarCode}.} + \label{fig:ipc_barcode_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 +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.} + +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. + +Per utilizzare queste caratteristiche il primo figlio chiude (\texttt{\small + 20}) 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 21}) il capo in lettura della pipe allo +standard input usando \func{dup2}. 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 per l'immagine) quest'ultimo leggerà la +stringa da codificare che gli viene inviata dal padre dalla prima pipe e +scriverà l'immagine postscript del codice a barre sulla seconda. + +Dall'altra parte il processo padre prima chiude (\texttt{\small 26}) il capo +inutilizzato della prima pipe (quello in input), 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 è +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à effettuato 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} sullo +standard input per convertirla in JPEG. + +Per fare tutto ciò il secondo figlio anzitutto chiude (\texttt{\small 37}) il +capo in scrittura della seconda pipe, e 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 mime-type in testa allo standard output. A +questo punto si può invocare \texttt{\small 41}) il programma \cmd{gs}, +provvedendo gli appositi switch che consentono di leggere il file da +convertire dallo standard input, ed inviare la conversione sullo standard +output. + +Per concludere 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}), per poi uscire \texttt{\small 46}). Si tenga conto che, +l'operazione di chiudere il capo in scrittura della seconda pipe è necessaria, +infatti non chiudendola \cmd{gs}, che legge il suo stardard input da detta +pipe, resterebbe bloccato in attesa di ulteriore input (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} +\noindent e serve per semplificare l'uso di \func{pipe}. + +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 stringa \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 +standard visti in \secref{cha:files_std_interface}, e viene sempre aperto in +modalità \textit{fully-buffered} (vedi \secref{sec:file_buffering}); l'unica +differenza è che deve essere chiuso dalla seconda delle due funzioni, +\func{pclose}, il cui prototipo è: +\begin{prototype}{stdio.h} +{int pclose(FILE *stream)} + +Chiude il file \param{stream}, restituito da una prededente \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 si incarica anche di attendere la conclusione del processo +creato dalla precedente \func{popen}. + +Per illustrare l'uso di queste due funzioni riprendiamo l'esempio in +\figref{fig:ipc_barcode_code}: esso per quanto 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}. Utilizzando un file in PDF invece, \cmd{gs} esegue +la conversione rispettando le dimensioni originarie del codice a barre. + -Perché questo accada però, e questo è il principale limite nell'uso delle -\textit{pipe}, è necessario che questi processi possano condividere il file -descriptor della \textit{pipe}; per questo essi devono comunque derivare da -uno stesso processo padre, o, più comunemente, essere nella relazione -padre/figlio. \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 invece che essere struttura interne del kernel +visibili solo attraverso un file descriptor comune, possono essere viste +attraverso un inode che risiede sul filesystem. + + +Abbiamo già accennato in \secref{sec:file_mknod} +che invece possono risiedere +sul filesystem, e che i processi possono usare per le comunicazioni senza +dovere per forza essere in relazione diretta. + +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, + \section{La comunicazione fra processi di System V} \label{sec:ipc_sysv} Per ovviare ai vari limiti dei meccanismo tradizionale di comunicazione fra -processi basato sulle \textit{pipe}, nello sviluppo di System V vennero -introdotti una serie di nuovi oggetti 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}. +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}. + \subsection{Code di messaggi} \label{sec:ipc_messque} Il primo oggetto introdotto dal \textit{SystemV IPC} è quello delle code di -messaggi. +messaggi. \subsection{Semafori} \label{sec:ipc_semaph}