X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=ipc.tex;h=e5631536c274f154f1304afa51cb82241931b9e3;hp=7ec9f2d47f0a45e1f2f728cbc7de818c352ee180;hb=31ab2d2cca0ca061a00cd688c850fca860827e26;hpb=3d44c36183fe67ed64bff95a36596ad87f620683 diff --git a/ipc.tex b/ipc.tex index 7ec9f2d..e563153 100644 --- a/ipc.tex +++ b/ipc.tex @@ -1,32 +1,456 @@ -\chapter{La comunicazione fra porcessi} +\chapter{La comunicazione fra processi} \label{cha:IPC} -\section{Introduzione} -\label{sec:ipc_intro} +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. -Uno degli aspetti fondamentali della programmazione in unix è la comunicazione -fra processi. In questo testo affronteremo solo alcuni dei meccanismi -fondamentali che permettono di scrivere applicazioni, esistono pure sistemi -più complessi ed evoluti come le RPC (\textit{Remote Procedure Calls}) e -CORBA (\textit{Common Object Request Brocker Architecture}) non saranno -affrontati qui. +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{Le pipes standard} + + +\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. + + +\subsection{Le \textit{pipe} standard} \label{sec:ipc_pipes} -\section{Le pipes con nome} -\label{sec:ipc_nampipe} +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{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} 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à 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ò il secondo figlio anzitutto chiude (\texttt{\small 37}) il +capo in scrittura della seconda pipe, e 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 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} -\section{System V IPC} +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. + +Ci si trova dunque davanti al classico caso dell'uso delle pipe in cui si +devono eseguire più processi in fila, inviando l'output di ciascuno all'input +del successivo, per poi ottenere il risultato finale sullo standard output. +Dato che questo caso 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_barcode2_code}; +come si può notare l'ordine di invocazione dei programmi è l'inverso di quello +in cui ci si aspetta vengano effettivamente eseguiti. Questo non comporta +nessun problema; infatti 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 precendente, 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[]) +{ +} + + \end{lstlisting} + \end{minipage} + \normalsize + \caption{Codice del \textit{cgi-bin} \cmd{BarCode2}.} + \label{fig:ipc_barcode2_code} +\end{figure} + +Nel nostro caso il primo passo (\texttt{\small 12}) è 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. Il primo processo figlio ad essere invocato +(\texttt{\small 14}) è necessariamente l'ultimo della sequenza, in quanto è +lui che deve uscire sullo standard output, gli altri saranno tutti rediretti. + +Una volta lanciato il processo finale si può iniziare la catena delle +redirezioni; ogni volta (\texttt{\small 16} e \texttt{\small 20}) duplicheremo +il file restituito dalla chiamata precedente a \func{popen} sullo standard +output, in questo modo alla successiva chiamata di \func{popen} il processo +eseguito scriverà il suo standard output sulla pipe collegata allo standard +input del precedente. + +Alla fine tutto quello che resta da fare è scrivere (\texttt{\small 22}) la +stringa del codice a barre sulla pipe collegata allo standard input +dell'ultimo processo lanciato, e poi chiudere tutte le pipe create con +\func{pclose}. + + +\subsection{Le \textit{pipe} con nome, o \textit{fifo}} +\label{sec:ipc_named_pipe} + +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. + +Utilizzando una fifo tutti i dati passeranno attraverso un apposito buffer nel +kernel, senza transitare dal filesystem, l'inode serve solo a fornire un punto +d'appoggio per i vari processi che permetta loro di accedere alla stessa +fifo. + + + + +Abbiamo già visto in \secref{sec:file_mknod} le modalità che permettono di +creare una fifo, attraverso le funzioni \func{mknod} e \func{mkfifo}; per +utilizzarle un processo non avrà che da aprire il relativo file in lettura o +scrittura (a seconda della direzione che si vuole dare ai dati). + +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} -\section{Code di messaggi} +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}. + + +\subsection{Code di messaggi} \label{sec:ipc_messque} -\section{Semafori} +Il primo oggetto introdotto dal \textit{SystemV IPC} è quello delle code di +messaggi. + +\subsection{Semafori} \label{sec:ipc_semaph} -\section{Memoria condivisa} +Il secondo oggetto introdotto dal \textit{SystemV IPC} è quello dei semafori. + + +\subsection{Memoria condivisa} \label{sec:ipc_shar_mem} +Il terzo oggetto introdotto dal \textit{SystemV IPC} è quello della memoria +condivisa. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "gapil" +%%% End: