X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=ipc.tex;h=9dec79a907d64106343fb941ceac99990aed297b;hp=24ec8f4685d898ded65d4675bc4cc6c6020a62e5;hb=3498a6fc0fd13e07cacdea210cb99126d5052fbc;hpb=aa5d63f2af4475ae1188f695a33e4d0bee55f787 diff --git a/ipc.tex b/ipc.tex index 24ec8f4..9dec79a 100644 --- a/ipc.tex +++ b/ipc.tex @@ -6,46 +6,40 @@ Uno degli aspetti fondamentali della programmazione in un sistema unix-like la comunicazione fra processi. In questo capitolo affronteremo solo i meccanismi più elementari che permettono di mettere in comunicazione processi diversi, come quelli tradizionali che coinvolgono \textit{pipe} e -\textit{fifo} e i meccanismi di intercomunicazione di System V. +\textit{fifo} e i meccanismi di intercomunicazione di System V e quelli POSIX. Tralasceremo invece tutte le problematiche relative alla comunicazione -attraverso la rete (e le relative interfacce) che saranno affrontate in gran -dettaglio in un secondo tempo. Non affronteremo invece meccanismi più +attraverso la rete (e le relative interfacce) che saranno affrontate in +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 -è 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 introdotto nei sistemi Unix, +è quello delle cosiddette \textit{pipe}; esse costituiscono una delle +caratteristiche peculiari del sistema, in particolar modo dell'interfaccia a +linea di comando. In questa sezione descriveremo le sue basi, le funzioni che +ne gestiscono l'uso e le varie forme in cui si è evoluto. \subsection{Le \textit{pipe} standard} \label{sec:ipc_pipes} -Le \textit{pipe} nascono sostanzialmente con Unix, e sono il primo, ed uno dei -più usati, meccanismi di comunicazione fra processi. Si tratta in sostanza uno -speciale tipo di file\footnote{più precisamente un file descriptor; le pipe - sono create dal kernel e non risiedono su disco.} in cui un processo scrive -ed un altro legge. Si viene così a costituire un canale di comunicazione fra i -due processi, nella forma di un \textsl{tubo} (da cui il nome) in cui uno dei -processi immette dati che poi arriveranno all'altro. - -Perché questo accada però, e questo è il principale\footnote{Stevens riporta - in APUE come limite anche il fatto che la comunicazione è unidirezionale, in - realtà questo è un limite facilemente risolvibile usando una coppia di - \textit{pipe}.} e 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. - -La funzione che permette di creare una \textit{pipe} è appunto \func{pipe}; il -suo prototipo è: +Le \textit{pipe} nascono sostanzialmente con Unix, e sono il primo, e tuttora +uno dei più usati, meccanismi di comunicazione fra processi. Si tratta in +sostanza di una una coppia di file descriptor\footnote{si tenga presente che + le pipe sono oggetti creati dal kernel e non risiedono su disco.} connessi +fra di loro in modo che se quanto scrive su di uno si può rileggere +dall'altro. Si viene così a costituire un canale di comunicazione tramite i +due file descriptor, nella forma di un \textsl{tubo} (da cui il nome) +attraverso cui fluiscono i dati. + +La funzione che permette di creare questa speciale coppia di file descriptor +associati ad una \textit{pipe} è appunto \func{pipe}, ed il suo prototipo è: \begin{prototype}{unistd.h} {int pipe(int filedes[2])} @@ -56,51 +50,3010 @@ Crea una coppia di file descriptor associati ad una \textit{pipe}. \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 +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. I file descriptor infatti non sono +connessi a nessun file reale, ma ad un buffer nel kernel, la cui dimensione è +specificata dal parametro di sistema \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 + \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 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 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 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 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 delle pipe faremo un esempio di quello che +è il loro uso più comune, analogo a quello effettuato della shell, e che +consiste nell'inviare l'output di un processo (lo standard output) sull'input +di un'altro. Realizzeremo il programma di esempio nella forma di un +\textit{CGI}\footnote{Un CGI (\textit{Common Gateway Interface}) è un + programma che permette la creazione dinamica di un oggetto da inserire + all'interno di una pagina HTML.} per Apache, che genera una immagine JPEG +di un codice a barre, specificato come parametro di input. + +Un programma che deve essere eseguito come \textit{CGI} deve rispondere a +delle caratteristiche specifiche, esso infatti non viene lanciato da una +shell, ma dallo stesso web server, alla richiesta di una specifica URL, che di +solito ha la forma: +\begin{verbatim} + http://www.sito.it/cgi-bin/programma?parametro +\end{verbatim} +ed il risultato dell'elaborazione deve essere presentato (con una intestazione +che ne descrive il mime-type) sullo standard output, in modo che il web-server +possa reinviarlo al browser che ha effettuato la richiesta, che in questo modo +è in grado di visualizzarlo opportunamente. + +Per realizzare quanto voluto useremo in sequenza i programmi \cmd{barcode} e +\cmd{gs}, il primo infatti è in grado di generare immagini postscript di +codici a barre corrispondenti ad una qualunque stringa, mentre il secondo +serve per poter effettuare la conversione della stessa immagine in formato +JPEG. Usando una pipe potremo inviare l'output del primo sull'input del +secondo, secondo lo schema mostrato in \figref{fig:ipc_pipe_use}, in cui la +direzione del flusso dei dati è data dalle frecce continue. + +\begin{figure}[htb] + \centering + \includegraphics[height=5cm]{img/pipeuse} + \caption{Schema dell'uso di una pipe come mezzo di comunicazione fra + due processi attraverso attraverso l'esecuzione una \func{fork} e la + chiusura dei capi non utilizzati.} + \label{fig:ipc_pipe_use} +\end{figure} + +Si potrebbe obiettare che sarebbe molto più semplice salvare il risultato +intermedio su un file temporaneo. Questo però non tiene conto del fatto che un +\textit{CGI} deve poter gestire più richieste in concorrenza, e si avrebbe una +evidente race condition in caso di accesso simultaneo a detto +file.\footnote{il problema potrebbe essere superato determinando in anticipo + un nome appropriato per il file temporaneo, che verrebbe utilizzato dai vari + sotto-processi, e cancellato alla fine della loro esecuzione; ma a questo le + cose non sarebbero più tanto semplici.} L'uso di una pipe invece permette +di risolvere il problema in maniera semplice ed elegante, oltre ad essere +molto più efficiente, dato che non si deve scrivere su disco. + +Il programma ci servirà anche come esempio dell'uso delle funzioni di +duplicazione dei file descriptor che abbiamo trattato in +\secref{sec:file_dup}, in particolare di \func{dup2}. È attraverso queste +funzioni infatti che è possibile dirottare gli stream standard dei processi +(che abbiamo visto in \secref{sec:file_std_descr} e +\secref{sec:file_std_stream}) sulla pipe. In \figref{fig:ipc_barcodepage_code} +abbiamo riportato il corpo del programma, il cui codice completo è disponibile +nel file \file{BarCodePage.c} che si trova nella directory dei sorgenti. + + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \begin{lstlisting}{} +int main(int argc, char *argv[], char *envp[]) +{ + ... + /* create two pipes, pipein and pipeout, to handle communication */ + if ( (retval = pipe(pipein)) ) { + WriteMess("input pipe creation error"); + exit(0); + } + if ( (retval = pipe(pipeout)) ) { + WriteMess("output pipe creation error"); + exit(0); + } + /* First fork: use child to run barcode program */ + if ( (pid = fork()) == -1) { /* on error exit */ + WriteMess("child creation error"); + exit(0); + } + /* if child */ + if (pid == 0) { + close(pipein[1]); /* close pipe write end */ + dup2(pipein[0], STDIN_FILENO); /* remap stdin to pipe read end */ + close(pipeout[0]); + dup2(pipeout[1], STDOUT_FILENO); /* remap stdout in pipe output */ + execlp("barcode", "barcode", size, NULL); + } + close(pipein[0]); /* close input side of input pipe */ + write(pipein[1], argv[1], strlen(argv[1])); /* write parameter to pipe */ + close(pipein[1]); /* closing write end */ + waitpid(pid, NULL, 0); /* wait child completion */ + /* Second fork: use child to run ghostscript */ + if ( (pid = fork()) == -1) { + WriteMess("child creation error"); + exit(0); + } + /* second child, convert PS to JPEG */ + if (pid == 0) { + close(pipeout[1]); /* close write end */ + dup2(pipeout[0], STDIN_FILENO); /* remap read end to stdin */ + /* send mime type */ + write(STDOUT_FILENO, content, strlen(content)); + execlp("gs", "gs", "-q", "-sDEVICE=jpeg", "-sOutputFile=-", "-", NULL); + } + /* still parent */ + close(pipeout[1]); + waitpid(pid, NULL, 0); + exit(0); +} + \end{lstlisting} + \end{minipage} + \normalsize + \caption{Sezione principale del codice del \textit{CGI} + \file{BarCodePage.c}.} + \label{fig:ipc_barcodepage_code} +\end{figure} + +La prima operazione del programma (\texttt{\small 4--12}) è quella di creare +le due pipe che serviranno per la comunicazione fra i due comandi utilizzati +per produrre il codice a barre; si ha cura di controllare la riuscita della +chiamata, inviando in caso di errore un messaggio invece dell'immagine +richiesta.\footnote{la funzione \func{WriteMess} non è riportata in + \secref{fig:ipc_barcodepage_code}; essa si incarica semplicemente di + formattare l'uscita alla maniera dei CGI, aggiungendo l'opportuno + \textit{mime type}, e formattando il messaggio in HTML, in modo che + quest'ultimo possa essere visualizzato correttamente da un browser.} + +Una volta create le pipe, il programma può creare (\texttt{\small 13-17}) il +primo processo figlio, che si incaricherà (\texttt{\small 19--25}) di eseguire +\cmd{barcode}. Quest'ultimo legge dallo standard input una stringa di +caratteri, la converte nell'immagine postscript del codice a barre ad essa +corrispondente, e poi scrive il risultato direttamente sullo standard output. + +Per poter utilizzare queste caratteristiche prima di eseguire \cmd{barcode} si +chiude (\texttt{\small 20}) il capo aperto in scrittura della prima pipe, e se +ne collega (\texttt{\small 21}) il capo in lettura allo standard input, usando +\func{dup2}. Si ricordi che invocando \func{dup2} il secondo file, qualora +risulti aperto, viene, come nel caso corrente, chiuso prima di effettuare la +duplicazione. Allo stesso modo, dato che \cmd{barcode} scrive l'immagine +postscript del codice a barre sullo standard output, per poter effettuare una +ulteriore redirezione il capo in lettura della seconda pipe viene chiuso +(\texttt{\small 22}) mentre il capo in scrittura viene collegato allo standard +output (\texttt{\small 23}). + +In questo modo all'esecuzione (\texttt{\small 25}) di \cmd{barcode} (cui si +passa in \var{size} la dimensione della pagina per l'immagine) quest'ultimo +leggerà dalla prima pipe la stringa da codificare che gli sarà inviata dal +padre, e scriverà l'immagine postscript del codice a barre sulla seconda. + +Al contempo una volta lanciato il primo figlio, il processo padre prima chiude +(\texttt{\small 26}) il capo inutilizzato della prima pipe (quello in input) e +poi scrive (\texttt{\small 27}) la stringa da convertire sul capo in output, +così che \cmd{barcode} possa riceverla dallo standard input. A questo punto +l'uso della prima pipe da parte del padre è finito ed essa può essere +definitivamente chiusa (\texttt{\small 28}), si attende poi (\texttt{\small + 29}) che l'esecuzione di \cmd{barcode} sia completata. + +Alla conclusione della sua esecuzione \cmd{barcode} avrà inviato l'immagine +postscript del codice a barre sul capo in scrittura della seconda pipe; a +questo punto si può eseguire la seconda conversione, da PS a JPEG, usando il +programma \cmd{gs}. Per questo si crea (\texttt{\small 30--34}) un secondo +processo figlio, che poi (\texttt{\small 35--42}) eseguirà questo programma +leggendo l'immagine postscript creata da \cmd{barcode} dallo standard input, +per convertirla in JPEG. + +Per fare tutto ciò anzitutto si chiude (\texttt{\small 37}) il capo in +scrittura della seconda pipe, e se ne collega (\texttt{\small 38}) il capo in +lettura allo standard input. Per poter formattare l'output del programma in +maniera utilizzabile da un browser, si provvede anche \texttt{\small 40}) alla +scrittura dell'apposita stringa di identificazione del mime-type in testa allo +standard output. A questo punto si può invocare \texttt{\small 41}) \cmd{gs}, +provvedendo gli appositi switch che consentono di leggere il file da +convertire dallo standard input e di inviare la conversione sullo standard +output. + +Per completare le operazioni il processo padre chiude (\texttt{\small 44}) il +capo in scrittura della seconda pipe, e attende la conclusione del figlio +(\texttt{\small 45}); a questo punto può (\texttt{\small 46}) uscire. Si tenga +conto che l'operazione di chiudere il capo in scrittura della seconda pipe è +necessaria, infatti, se non venisse chiusa, \cmd{gs}, che legge il suo +standard input da detta pipe, resterebbe bloccato in attesa di ulteriori dati +in ingresso (l'unico modo che un programma ha per sapere che l'input è +terminato è rilevare che lo standard input è stato chiuso), e la \func{wait} +non ritornerebbe. + + +\subsection{Le funzioni \func{popen} e \func{pclose}} +\label{sec:ipc_popen} + +Come si è visto la modalità più comune di utilizzo di una pipe è quella di +utilizzarla per fare da tramite fra output ed input di due programmi invocati +in sequenza; per questo motivo lo standard POSIX.2 ha introdotto due funzioni +che permettono di sintetizzare queste operazioni. La prima di esse si chiama +\func{popen} ed il suo prototipo è: +\begin{prototype}{stdio.h} +{FILE *popen(const char *command, const char *type)} + +Esegue il programma \param{command}, di cui, a seconda di \param{type}, +restituisce, lo standard input o lo standard output nella pipe collegata allo +stream restituito come valore di ritorno. + +\bodydesc{La funzione restituisce l'indirizzo dello stream associato alla pipe + in caso di successo e \macro{NULL} per un errore, nel qual caso \var{errno} + potrà assumere i valori relativi alle sottostanti invocazioni di \func{pipe} + e \func{fork} o \macro{EINVAL} se \param{type} non è valido.} +\end{prototype} + +La funzione crea una pipe, esegue una \func{fork}, ed invoca il programma +\param{command} attraverso la shell (in sostanza esegue \file{/bin/sh} con il +flag \code{-c}); l'argomento \param{type} deve essere una delle due stringhe +\verb|"w"| o \verb|"r"|, per indicare se la pipe sarà collegata allo standard +input o allo standard output del comando invocato. + +La funzione restituisce il puntatore allo stream associato alla pipe creata, +che sarà aperto in sola lettura (e quindi associato allo standard output del +programma indicato) in caso si sia indicato \code{"r"}, o in sola scrittura (e +quindi associato allo standard input) in caso di \code{"w"}. + +Lo stream restituito da \func{popen} è identico a tutti gli effetti ai file +stream visti in \secref{cha:files_std_interface}, anche se è collegato ad una +pipe e non ad un inode, e viene sempre aperto in modalità +\textit{fully-buffered} (vedi \secref{sec:file_buffering}); l'unica differenza +con gli usuali stream è che dovrà essere chiuso dalla seconda delle due nuove +funzioni, \func{pclose}, il cui prototipo è: +\begin{prototype}{stdio.h} +{int pclose(FILE *stream)} + +Chiude il file \param{stream}, restituito da una precedente \func{popen} +attendendo la terminazione del processo ad essa associato. + +\bodydesc{La funzione restituisce 0 in caso di successo e -1 in caso di + errore; nel quel caso il valore di \func{errno} deriva dalle sottostanti + chiamate.} +\end{prototype} +\noindent che oltre alla chiusura dello stream si incarica anche di attendere +(tramite \func{wait4}) la conclusione del processo creato dalla precedente +\func{popen}. + +Per illustrare l'uso di queste due funzioni riprendiamo il problema +precedente: il programma mostrato in \figref{fig:ipc_barcodepage_code} per +quanto funzionante, è (volutamente) codificato in maniera piuttosto complessa, +inoltre nella pratica sconta un problema di \cmd{gs} che non è in +grado\footnote{nella versione GNU Ghostscript 6.53 (2002-02-13).} di +riconoscere correttamente l'encapsulated postscript, per cui deve essere usato +il postscript e tutte le volte viene generata una pagina intera, invece che +una immagine delle dimensioni corrispondenti al codice a barre. + +Se si vuole generare una immagine di dimensioni appropriate si deve usare un +approccio diverso. Una possibilità sarebbe quella di ricorrere ad ulteriore +programma, \cmd{epstopsf}, per convertire in PDF un file EPS (che può essere +generato da \cmd{barcode} utilizzando lo switch \cmd{-E}). Utilizzando un PDF +al posto di un EPS \cmd{gs} esegue la conversione rispettando le dimensioni +originarie del codice a barre e produce un JPEG 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} originali, che consentono -la comunicazione solo fra processi correlati, passando attraverso strutture -interne del kernel, sono stati introdotti dei nuovi oggetti, le \textit{fifo}, -che invece possono risiedere sul filesystem, e che i processi possono usare -per le comunicazioni senza dovere per forza essere in relazione diretta. +Come accennato in \secref{sec:ipc_pipes} il problema delle \textit{pipe} è che +esse possono essere utilizzate solo da processi con un progenitore comune o +nella relazione padre/figlio; per superare questo problema lo standard POSIX.1 +ha definito dei nuovi oggetti, le \textit{fifo}, che hanno le stesse +caratteristiche delle pipe, ma che invece di essere strutture interne del +kernel, visibili solo attraverso un file descriptor, sono accessibili +attraverso un inode che risiede sul filesystem, così che i processi le possono +usare senza dovere per forza essere in una relazione di \textsl{parentela}. + +Utilizzando una \textit{fifo} tutti i dati passeranno, come per le pipe, +attraverso un apposito buffer nel kernel, senza transitare dal filesystem; +l'inode allocato sul filesystem serve infatti solo a fornire un punto di +riferimento per i processi, che permetta loro di accedere alla stessa fifo; il +comportamento delle funzioni di lettura e scrittura è identico a quello +illustrato per le pipe in \secref{sec:ipc_pipes}. + +Abbiamo già visto in \secref{sec:file_mknod} le funzioni \func{mknod} e +\func{mkfifo} che permettono di creare una fifo; per utilizzarne una un +processo non avrà che da aprire il relativo file speciale o in lettura o +scrittura; nel primo caso sarà collegato al capo di uscita della fifo, e dovrà +leggere, nel secondo al capo di ingresso, e dovrà scrivere. + +Il kernel crea una singola pipe per ciascuna fifo che sia stata aperta, che può +essere acceduta contemporaneamente da più processi, sia in lettura che in +scrittura. Dato che per funzionare deve essere aperta in entrambe le +direzioni, per una fifo di norma la funzione \func{open} si blocca se viene +eseguita quando l'altro capo non è aperto. + +Le fifo però possono essere anche aperte in modalità \textsl{non-bloccante}, +nel qual caso l'apertura del capo in lettura avrà successo solo quando anche +l'altro capo è aperto, mentre l'apertura del capo in scrittura restituirà +l'errore di \macro{ENXIO} fintanto che non verrà aperto il capo in lettura. + +In Linux è 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'occasione. + +\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 problemi, 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, dopo di che 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. + + + +\subsection{La funzione \func{socketpair}} +\label{sec:ipc_socketpair} + +Un meccanismo di comunicazione molto simile alle pipe, ma che non presenta il +problema della unidirezionalità del flusso dei dati, è quello dei cosiddetti +\textsl{socket locali} (o \textit{Unix domain socket}). Tratteremo l'argomento +dei \textit{socket} in \capref{cha:socket_intro},\footnote{si tratta comunque + di oggetti di comunicazione che, come le pipe, sono utilizzati attraverso + dei file descriptor.} nell'ambito dell'interfaccia generale che essi +forniscono per la programmazione di rete; e vedremo anche +(in~\secref{sec:sock_sa_local}) come si possono definire dei file speciali (di +tipo \textit{socket}, analoghi a quello associati alle fifo) cui si accede +però attraverso quella medesima interfaccia; vale però la pena esaminare qui +una modalità di uso dei socket locali\footnote{la funzione \func{socketpair} è + stata introdotta in BSD4.4, ma è supportata in genere da qualunque sistema + che fornisca l'interfaccia dei socket.} che li rende sostanzialmente +identici ad una pipe bidirezionale. +La funzione \func{socketpair} infatti consente di creare una coppia di file +descriptor connessi fra di loro (tramite un socket, appunto), senza dover +ricorrere ad un file speciale sul filesystem, i descrittori sono del tutto +analoghi a quelli che si avrebbero con una chiamata a \func{pipe}, con la sola +differenza è che in questo caso il flusso dei dati può essere effettuato in +entrambe le direzioni. Il prototipo della funzione è: +\begin{functions} + \headdecl{sys/types.h} + \headdecl{sys/socket.h} + + \funcdecl{int socketpair(int domain, int type, int protocol, int sv[2])} + + Crea una coppia di socket connessi fra loro. + + \bodydesc{La funzione restituisce 0 in caso di successo e -1 in caso di + errore, nel qual caso \var{errno} assumerà uno dei valori: + \begin{errlist} + \item[\macro{EAFNOSUPPORT}] I socket locali non sono supportati. + \item[\macro{EPROTONOSUPPORT}] Il protocollo specificato non è supportato. + \item[\macro{EOPNOTSUPP}] Il protocollo specificato non supporta la + creazione di coppie di socket. + \end{errlist} + ed inoltre \macro{EMFILE}, \macro{EFAULT}. +} +\end{functions} +La funzione restituisce in \param{sv} la coppia di descrittori connessi fra di +loro: quello che si scrive su uno di essi sarà ripresentato in input +sull'altro e viceversa. I parametri \param{domain}, \param{type} e +\param{protocol} derivano dall'interfaccia dei socket (che è quella che +fornisce il substrato per connettere i due descrittori), ma in questo caso i +soli valori validi che possono essere specificati sono rispettivamente +\macro{AF\_UNIX}, \macro{SOCK\_STREAM} e \macro{0}. + +L'utilità di chiamare questa funzione per evitare due chiamate a \func{pipe} +può sembrare limitata; in realtà l'utilizzo di questa funzione (e dei socket +locali in generale) permette di trasmettere attraverso le linea non solo dei +dati, ma anche dei file descriptor: si può cioè passare da un processo ad un +altro un file descriptor, con una sorta di duplicazione dello stesso non +all'interno di uno stesso processo, ma fra processi distinti (torneremo su +questa funzionalità in \secref{sec:xxx_fd_passing}). \section{La comunicazione fra processi di System V} \label{sec:ipc_sysv} -Per ovviare ai vari limiti dei meccanismo tradizionale di comunicazione fra -processi visto in \secref{sec:ipc_unix}, nello sviluppo di System V vennero -introdotti una serie di nuovi oggetti e relative interdacce che garantissero -una maggiore flessibilità; in questa sezione esamineremo quello che viene -ormai chiamato il \textit{System V Inter-Process Comunication System}, più -comunemente noto come \textit{SystemV IPC}. +Benché le pipe e le fifo siano ancora ampiamente usate, esse 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 chiamato il +\textsl{Sistema di comunicazione inter-processo} di System V, cui da qui in +avanti faremo riferimento come \textit{SysV IPC} (dove IPC è la sigla di +\textit{Inter-Process Comunication}). + + + +\subsection{Considerazioni generali} +\label{sec:ipc_sysv_generic} + +La principale caratteristica del \textit{SysV IPC} è 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 non c'è più nessuno che li utilizzi, ed essi devono +essere cancellati esplicitamente, se non si vuole che restino attivi fino al +riavvio del sistema. Il secondo problema è che, dato che non c'è, come per i +file, un contatore del numero di riferimenti che ne indichi l'essere in uso, +essi possono essere cancellati anche se ci sono dei processi che li stanno +utilizzando, con tutte le conseguenze (negative) del caso. + +Un'ulteriore caratteristica negativa è che gli oggetti usati nel \textit{SysV + 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à, né utilizzare un qualche valore statico, +si pone perciò il problema di come processi diversi possono accedere allo +stesso oggetto. + +Per risolvere il problema nella struttura \var{ipc\_perm} che il kernel +associa a ciascun oggetto, viene mantenuto anche un campo apposito che +contiene anche una \textsl{chiave}, identificata da una variabile del tipo +primitivo \type{key\_t}, da specificare in fase di creazione dell'oggetto, e +tramite la quale è possibile ricavare l'identificatore.\footnote{in sostanza + si sposta il problema dell'accesso dalla classificazione in base + all'identificatore alla classificazione in base alla chiave, una delle tante + complicazioni inutili presenti nel \textit{SysV IPC}.} Oltre la chiave, la +struttura, la cui definizione è riportata in \figref{fig:ipc_ipc_perm}, +mantiene varie proprietà ed informazioni 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 \textit{SysV IPC}. + + \bodydesc{La funzione restituisce la chiave in caso di successo e -1 + altrimenti, nel qual caso \var{errno} sarà 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 header 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 \textit{SysV IPC}. 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{SysV IPC}) solo con una ricompilazione del + kernel, andando a modificarne la definizione nei relativi header 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 0 */ + char mtext[LENGTH]; /* message data */ + }; + \end{lstlisting} + \end{minipage} + \normalsize + \caption{Schema della struttura \var{msgbuf}, da utilizzare come argomento + per inviare/ricevere messaggi.} + \label{fig:ipc_msbuf} +\end{figure} + +Per capire meglio il funzionamento della funzione riprendiamo in +considerazione la struttura della coda illustrata in +\figref{fig:ipc_mq_schema}. Alla chiamata di \func{msgsnd} il nuovo messaggio +sarà aggiunto in fondo alla lista inserendo una nuova struttura \var{msg}, il +puntatore \var{msg\_last} di \var{msqid\_ds} verrà aggiornato, come pure il +puntatore al messaggio successivo per quello che era il precedente ultimo +messaggio; il valore di \var{mtype} verrà mantenuto in \var{msg\_type} ed il +valore di \param{msgsz} in \var{msg\_ts}; il testo del messaggio sarà copiato +all'indirizzo specificato da \var{msg\_spot}. + +Il valore dell'argomento \param{flag} permette di specificare il comportamento +della funzione. Di norma, quando si specifica un valore nullo, la funzione +ritorna immediatamente a meno che si sia ecceduto il valore di +\var{msg\_qbytes}, o il limite di sistema sul numero di messaggi, nel qual +caso si blocca mandando il processo in stato di \textit{sleep}. Se si +specifica per \param{flag} il valore \macro{IPC\_NOWAIT} la funzione opera in +modalità non bloccante, ed in questi casi ritorna immediatamente con un errore +di \macro{EAGAIN}. + +Se non si specifica \macro{IPC\_NOWAIT} la funzione resterà bloccata fintanto +che non si liberano risorse sufficienti per poter inserire nella coda il +messaggio, nel qual caso ritornerà normalmente. La funzione può ritornare, con +una condizione di errore anche in due altri casi: quando la coda viene rimossa +(nel qual caso si ha un errore di \macro{EIDRM}) o quando la funzione viene +interrotta da un segnale (nel qual caso si ha un errore di \macro{EINTR}). + +Una volta completato con successo l'invio del messaggio sulla coda, la +funzione aggiorna i dati mantenuti in \var{msqid\_ds}, in particolare vengono +modificati: +\begin{itemize*} +\item Il valore di \var{msg\_lspid}, che viene impostato al \acr{pid} del + processo chiamante. +\item Il valore di \var{msg\_qnum}, che viene incrementato di uno. +\item Il valore \var{msg\_stime}, che viene impostato al tempo corrente. +\end{itemize*} + +La funzione che viene utilizzata per estrarre un messaggio da una coda è +\func{msgrcv}; il suo prototipo è: +\begin{functions} + \headdecl{sys/types.h} + \headdecl{sys/ipc.h} + \headdecl{sys/msg.h} + + \funcdecl{ssize\_t msgrcv(int msqid, struct msgbuf *msgp, size\_t msgsz, + long msgtyp, int msgflg)} + + Legge un messaggio dalla coda \param{msqid}. + + \bodydesc{La funzione restituisce il numero di byte letti in caso di + successo, e -1 in caso di errore, nel qual caso \var{errno} assumerà uno + dei valori: + \begin{errlist} + \item[\macro{EACCES}] Non si hanno i privilegi di accesso sulla coda. + \item[\macro{EIDRM}] La coda è stata cancellata. + \item[\macro{E2BIG}] Il testo del messaggio è più lungo di \param{msgsz} e + non si è specificato \macro{MSG\_NOERROR} in \param{msgflg}. + \item[\macro{EINTR}] La funzione è stata interrotta da un segnale mentre era + in attesa di ricevere un messaggio. + \item[\macro{EINVAL}] Si è specificato un \param{msgid} invalido o un valore + di \param{msgsz} negativo. + \end{errlist} + ed inoltre \macro{EFAULT}. +} +\end{functions} + +La funzione legge un messaggio dalla coda specificata, scrivendolo sulla +struttura puntata da \param{msgp}, che dovrà avere un formato analogo a quello +di \figref{fig:ipc_msbuf}. Una volta estratto, il messaggio sarà rimosso dalla +coda. L'argomento \param{msgsz} indica la lunghezza massima del testo del +messaggio (equivalente al valore del parametro \macro{LENGTH} nell'esempio di +\figref{fig:ipc_msbuf}). + +Se il testo del messaggio ha lunghezza inferiore a \param{msgsz} esso viene +rimosso dalla coda; in caso contrario, se \param{msgflg} è impostato a +\macro{MSG\_NOERROR}, il messaggio viene troncato e la parte in eccesso viene +perduta, altrimenti il messaggio non viene estratto e la funzione ritorna con +un errore di \macro{E2BIG}. + +L'argomento \param{msgtyp} permette di restringere la ricerca ad un +sottoinsieme dei messaggi presenti sulla coda; la ricerca infatti è fatta con +una scansione della struttura mostrata in \figref{fig:ipc_mq_schema}, +restituendo il primo messaggio incontrato che corrisponde ai criteri +specificati (che quindi, visto come i messaggi vengono sempre inseriti dalla +coda, è quello meno recente); in particolare: +\begin{itemize*} +\item se \param{msgtyp} è 0 viene estratto il messaggio in cima alla coda, cioè + quello fra i presenti che è stato inserito inserito per primo. +\item se \param{msgtyp} è positivo viene estratto il primo messaggio il cui + tipo (il valore del campo \var{mtype}) corrisponde al valore di + \param{msgtyp}. +\item se \param{msgtyp} è negativo viene estratto il primo fra i messaggi con + il valore più basso del tipo, fra tutti quelli il cui tipo ha un valore + inferiore al valore assoluto di \param{msgtyp}. +\end{itemize*} + +Il valore di \param{msgflg} permette di controllare il comportamento della +funzione, esso può essere nullo o una maschera binaria composta da uno o più +valori. Oltre al precedente \macro{MSG\_NOERROR}, sono possibili altri due +valori: \macro{MSG\_EXCEPT}, che permette, quando \param{msgtyp} è positivo, +di leggere il primo messaggio nella coda con tipo diverso da \param{msgtyp}, e +\macro{IPC\_NOWAIT} che causa il ritorno immediato della funzione quando non +ci sono messaggi sulla coda. + +Il comportamento usuale della funzione infatti, se non ci sono messaggi +disponibili per la lettura, è di bloccare il processo in stato di +\textit{sleep}. Nel caso però si sia specificato \macro{IPC\_NOWAIT} la +funzione ritorna immediatamente con un errore \macro{ENOMSG}. Altrimenti la +funzione ritorna normalmente non appena viene inserito un messaggio del tipo +desiderato, oppure ritorna con errore qualora la coda sia rimossa (con +\var{errno} impostata a \macro{EIDRM}) o se il processo viene interrotto da un +segnale (con \var{errno} impostata a \macro{EINTR}). + +Una volta completata con successo l'estrazione del messaggio dalla coda, la +funzione aggiorna i dati mantenuti in \var{msqid\_ds}, in particolare vengono +modificati: +\begin{itemize*} +\item Il valore di \var{msg\_lrpid}, che viene impostato al \acr{pid} del + processo chiamante. +\item Il valore di \var{msg\_qnum}, che viene decrementato di uno. +\item Il valore \var{msg\_rtime}, che viene impostato al tempo corrente. +\end{itemize*} + +Le code di messaggi presentano il solito problema di tutti gli oggetti del +SysV IPC; essendo questi permanenti restano nel sistema occupando risorse +anche quando un processo è terminato, al contrario delle pipe per le quali +tutte le risorse occupate vengono rilasciate quanto l'ultimo processo che le +utilizzava termina. Questo comporta che in caso di errori si può saturare il +sistema, e che devono comunque essere esplicitamente previste delle funzioni +di rimozione in caso di interruzioni o uscite dal programma (come vedremo in +\figref{fig:ipc_mq_fortune_server}). + +L'altro problema è non facendo uso di file descriptor le tecniche di +\textit{I/O multiplexing} descritte in \secref{sec:file_multiplexing} non +possono essere utilizzate, e non si ha a disposizione niente di analogo alle +funzioni \func{select} e \func{poll}. Questo rende molto scomodo usare più di +una di queste strutture alla volta; ad esempio non si può scrivere un server +che aspetti un messaggio su più di una coda senza fare ricorso ad una tecnica +di \textit{polling}\index{polling} che esegua un ciclo di attesa su ciascuna +di esse. + +Come esempio dell'uso delle code di messaggi possiamo riscrivere il nostro +server di \textit{fortunes} usando queste al posto delle fifo. In questo caso +useremo una sola coda di messaggi, usando il tipo di messaggio per comunicare +in maniera indipendente con client diversi. + +\begin{figure}[!bht] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \begin{lstlisting}{} +int msgid; /* Message queue identifier */ +int main(int argc, char *argv[]) +{ +/* Variables definition */ + int i, n = 0; + char **fortune; /* array of fortune message string */ + char *fortunefilename; /* fortune file name */ + struct msgbuf_read { /* message struct to read request from clients */ + long mtype; /* message type, must be 1 */ + long pid; /* message data, must be the pid of the client */ + } msg_read; + struct msgbuf_write { /* message struct to write result to clients */ + long mtype; /* message type, will be the pid of the client*/ + char mtext[MSGMAX]; /* message data, will be the fortune */ + } msg_write; + key_t key; /* Message queue key */ + int size; /* message size */ + ... + Signal(SIGTERM, HandSIGTERM); /* set handlers for termination */ + Signal(SIGINT, HandSIGTERM); + Signal(SIGQUIT, HandSIGTERM); + if (n==0) usage(); /* if no pool depth exit printing usage info */ + i = FortuneParse(fortunefilename, fortune, n); /* parse phrases */ + /* Create the queue */ + key = ftok("./MQFortuneServer.c", 1); + msgid = msgget(key, IPC_CREAT|0666); + if (msgid < 0) { + perror("Cannot create message queue"); + exit(1); + } + /* Main body: loop over requests */ + while (1) { + msgrcv(msgid, &msg_read, sizeof(int), 1, MSG_NOERROR); + n = random() % i; /* select random value */ + strncpy(msg_write.mtext, fortune[n], MSGMAX); + size = min(strlen(fortune[n])+1, MSGMAX); + msg_write.mtype=msg_read.pid; /* use request pid as type */ + msgsnd(msgid, &msg_write, size, 0); + } +} +/* + * Signal Handler to manage termination + */ +void HandSIGTERM(int signo) { + msgctl(msgid, IPC_RMID, NULL); /* remove message queue */ + exit(0); +} + \end{lstlisting} + \end{minipage} + \normalsize + \caption{Sezione principale del codice del server di \textit{fortunes} + basato sulle \textit{message queue}.} + \label{fig:ipc_mq_fortune_server} +\end{figure} + +In \figref{fig:ipc_mq_fortune_server} si è riportato un estratto delle parti +principali del codice del nuovo server (il codice completo è nel file +\file{MQFortuneServer.c} nei sorgenti allegati). Il programma è basato su un +uso accorto della caratteristica di poter associate un ``tipo'' ai messaggi +per permettere una comunicazione indipendente fra il server ed i vari client, +usando il \acr{pid} di questi ultimi come identificativo. Questo è possibile +in quanto, al contrario di una fifo, la lettura di una coda di messaggi può +non essere sequenziale, proprio grazie alla classificazione dei messaggi sulla +base del loro tipo. + +Il programma, oltre alle solite variabili per il nome del file da cui leggere +le \textit{fortunes} e per il vettore di stringhe che contiene le frasi, +definisce due strutture appositamente per la comunicazione; con +\var{msgbuf\_read} (\texttt{\small 8--11}) vengono passate le richieste mentre +con \var{msgbuf\_write} (\texttt{\small 12--15}) vengono restituite le frasi. + +La gestione delle opzioni si è al solito omessa, essa si curerà di impostare +in \var{n} il numero di frasi da leggere specificato a linea di comando ed in +\var{fortunefilename} il file da cui leggerle; dopo aver installato +(\texttt{\small 19--21}) dei manipolatori per gestire l'uscita dal server, +viene prima controllato (\texttt{\small 22}) il numero di frasi richieste +abbia senso (cioè sia maggiore di zero), le quali poi (\texttt{\small 23}) +vengono lette nel vettore in memoria con la stessa funzione +\code{FortuneParse()} usata anche per il server basato sulle fifo. + +Una volta inizializzato il vettore di stringhe coi messaggi presi dal file +delle \textit{fortune} si procede (\texttt{\small 25}) con la generazione di +una chiave per identificare la coda di messaggi (si usa il nome del file dei +sorgenti del server) con la quale poi si esegue (\texttt{\small 26}) la +creazione della stessa (si noti come si sia chiamata \func{msgget} con un +valore opportuno per l'argomento \param{flag}), avendo cura di abortire il +programma (\texttt{\small 27--29}) in caso di errore. + +Finita la fase di inizializzazione il server esegue in permanenza il ciclo +principale (\texttt{\small 32--41}). Questo inizia (\texttt{\small 33}) con il +porsi in attesa di un messaggio di richiesta da parte di un client; si noti +infatti come \func{msgrcv} richieda un messaggio con \var{mtype} uguale a 1: +questo è il valore usato per le richieste dato che corrisponde al \acr{pid} di +\cmd{init}, che non può essere un client. L'uso del flag \macro{MSG\_NOERROR} +è solo per sicurezza, dato che i messaggi di richiesta sono di dimensione +fissa (e contengono solo il \acr{pid} del client). + +Se non sono presenti messaggi di richiesta \func{msgrcv} si bloccherà, +ritornando soltanto in corrispondenza dell'arrivo sulla coda di un messaggio +di richiesta da parte di un client, in tal caso il ciclo prosegue +(\texttt{\small 34}) selezionando una frase a caso, copiandola (\texttt{\small + 35}) nella struttura \var{msgbuf\_write} usata per la risposta e +calcolandone (\texttt{\small 36}) la dimensione. + +Per poter permettere a ciascun client di ricevere solo la risposta indirizzata +a lui il tipo del messaggio in uscita viene inizializzato (\texttt{\small 37}) +al valore del \acr{pid} del client ricevuto nel messaggio di richiesta. +L'ultimo passo del ciclo (\texttt{\small 38}) è inviare sulla coda il +messaggio di risposta. Si tenga conto che se la coda è piena anche questa +funzione potrà bloccarsi fintanto che non venga liberato dello spazio. + +Si noti che il programma può terminare solo grazie ad una interruzione da +parte di un segnale; in tal caso verrà eseguito il manipolatore +\code{HandSIGTERM}, che semplicemente si limita a cancellare la coda +(\texttt{\small 44}) ed ad uscire (\texttt{\small 45}). + +\begin{figure}[!bht] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \begin{lstlisting}{} +int main(int argc, char *argv[]) +{ + ... + key = ftok("./MQFortuneServer.c", 1); + msgid = msgget(key, 0); + if (msgid < 0) { + perror("Cannot find message queue"); + exit(1); + } + /* Main body: do request and write result */ + msg_read.mtype = 1; /* type for request is always 1 */ + msg_read.pid = getpid(); /* use pid for communications */ + size = sizeof(msg_read.pid); + msgsnd(msgid, &msg_read, size, 0); /* send request message */ + msgrcv(msgid, &msg_write, MSGMAX, msg_read.pid, MSG_NOERROR); + printf("%s", msg_write.mtext); +} + \end{lstlisting} + \end{minipage} + \normalsize + \caption{Sezione principale del codice del client di \textit{fortunes} + basato sulle \textit{message queue}.} + \label{fig:ipc_mq_fortune_client} +\end{figure} + +In \figref{fig:ipc_mq_fortune_client} si è riportato un estratto il codice del +programma client. Al solito il codice completo è con i sorgenti allegati, nel +file \file{MQFortuneClient.c}. Come sempre si sono rimosse le parti relative +alla gestione delle opzioni, ed in questo caso, anche la dichiarazione delle +variabili, che, per la parte relative alle strutture usate per la +comunicazione tramite le code, sono le stesse viste in +\figref{fig:ipc_mq_fortune_server}. + +Il client in questo caso è molto semplice; la prima parte del programma +(\texttt{\small 4--9}) si occupa di accedere alla coda di messaggi, ed è +identica a quanto visto per il server, solo che in questo caso \func{msgget} +non viene chiamata con il flag di creazione in quanto la coda deve essere +preesistente. In caso di errore (ad esempio se il server non è stato avviato) +il programma termina immediatamente. + +Una volta acquisito l'identificatore della coda il client compone il +messaggio di richiesta (\texttt{\small 12--13}) in \var{msg\_read}, usando 1 +per il tipo ed inserendo il proprio \acr{pid} come dato da passare al server. +Calcolata (\texttt{\small 14}) la dimensione, provvede (\texttt{\small 15}) ad +immettere la richiesta sulla coda. + +A questo punto non resta che (\texttt{\small 16}) rileggere dalla coda la +risposta del server richiedendo a \func{msgrcv} di selezionare i messaggi di +tipo corrispondente al valore del \acr{pid} inviato nella richiesta. L'ultimo +passo (\texttt{\small 17}) prima di uscire è quello di stampare a video il +messaggio ricevuto. + +Benché funzionante questa architettura risente dello stesso inconveniente +visto anche nel caso del precedente server basato sulle fifo; se il client +viene interrotto dopo l'invio del messaggio di richiesta e prima della lettura +della risposta, quest'ultima resta nella coda (così come per le fifo si aveva +il problema delle fifo che restavano nel filesystem). In questo caso però il +problemi sono maggiori, sia perché è molto più facile esaurire la memoria +dedicata ad una coda di messaggi che gli inode di un filesystem, sia perché, +con il riutilizzo dei \acr{pid} da parte dei processi, un client eseguito in +un momento successivo potrebbe ricevere un messaggio non indirizzato a +lui. + + + +\subsection{Semafori} +\label{sec:ipc_sysv_sem} + +I semafori non sono meccanismi di intercomunicazione diretta come quelli +(pipe, fifo e code di messaggi) visti finora, e non consentono di scambiare +dati fra processi, ma servono piuttosto come meccanismi di sincronizzazione o +di protezione per le \textsl{sezioni critiche}\index{sezioni critiche} del +codice (si ricordi quanto detto in \secref{sec:proc_race_cond}). + +Un semaforo è uno speciale contatore, mantenuto nel kernel, che permette, a +seconda del suo valore, di consentire o meno la prosecuzione dell'esecuzione +di un programma. In questo modo l'accesso ad una risorsa condivisa da più +processi può essere controllato, associando ad essa un semaforo che consente +di assicurare che non più di un processo alla volta possa usarla. + +Il concetto di semaforo è uno dei concetti base nella programmazione ed è +assolutamente generico, così come del tutto generali sono modalità con cui lo +si utilizza. Un processo che deve accedere ad una risorsa eseguirà un +controllo del semaforo: se questo è positivo il suo valore sarà decrementato, +indicando che si è consumato una unità della risorsa, ed il processo potrà +proseguire nell'utilizzo di quest'ultima, provvedendo a rilasciarla, una volta +completate le operazioni volute, reincrementando il semaforo. + +Se al momento del controllo il valore del semaforo è nullo, siamo invece in +una situazione in cui la risorsa non è disponibile, ed il processo si +bloccherà in stato di \textit{sleep} fin quando chi la sta utilizzando non la +rilascerà, incrementando il valore del semaforo. Non appena il semaforo torna +positivo, indicando che la risorsa è disponibile, il processo sarà svegliato, +e si potrà operare come nel caso precedente (decremento del semaforo, accesso +alla risorsa, incremento del semaforo). + +Per poter implementare questo tipo di logica le operazioni di controllo e +decremento del contatore associato al semaforo devono essere atomiche, +pertanto una realizzazione di un oggetto di questo tipo è necessariamente +demandata al kernel. La forma più semplice di semaforo è quella del +\textsl{semaforo binario}, o \textit{mutex}, in cui un valore diverso da zero +(normalmente 1) indica la libertà di accesso, e un valore nullo l'occupazione +della risorsa; in generale però si possono usare semafori con valori interi, +utilizzando il valore del contatore come indicatore del ``numero di risorse'' +ancora disponibili. + +Il sistema di comunicazione interprocesso di \textit{SysV IPC} prevede anche i +semafori, ma gli oggetti utilizzati non sono semafori singoli, ma gruppi di +semafori detti \textsl{insiemi} (o \textit{semaphore set}); la funzione che +permette di creare o ottenere l'identificatore di un insieme di semafori è +\func{semget}, ed il suo prototipo è: +\begin{functions} + \headdecl{sys/types.h} + \headdecl{sys/ipc.h} + \headdecl{sys/sem.h} + + \funcdecl{int semget(key\_t key, int nsems, int flag)} + + Restituisce l'identificatore di un insieme di semafori. + + \bodydesc{La funzione restituisce l'identificatore (un intero positivo) o -1 + in caso di errore, nel qual caso \var{errno} assumerà i valori: + \begin{errlist} + \item[\macro{ENOSPC}] Si è cercato di creare una insieme di semafori + quando è stato superato o il limite per il numero totale di semafori + (\macro{SEMMNS}) o quello per il numero totale degli insiemi + (\macro{SEMMNI}) nel sistema. + \item[\macro{EINVAL}] L'argomento \param{nsems} è minore di zero o + maggiore del limite sul numero di semafori per ciascun insieme + (\macro{SEMMSL}), o se l'insieme già esiste, maggiore del numero di + semafori che contiene. + \item[\macro{ENOMEM}] Il sistema non ha abbastanza memoria per poter + contenere le strutture per un nuovo insieme di semafori. + \end{errlist} + ed inoltre \macro{EACCES}, \macro{ENOENT}, \macro{EEXIST}, \macro{EIDRM}, + con lo stesso significato che hanno per \func{msgget}.} +\end{functions} + +La funzione è del tutto analoga a \func{msgget}, solo che in questo caso +restituisce l'identificatore di un insieme di semafori, in particolare è +identico l'uso degli argomenti \param{key} e \param{flag}, per cui non +ripeteremo quanto detto al proposito in \secref{sec:ipc_sysv_mq}. L'argomento +\param{nsems} permette di specificare quanti semafori deve contenere l'insieme +quando se ne richieda la creazione, e deve essere nullo quando si effettua una +richiesta dell'identificatore di un insieme già esistente. + +Purtroppo questa implementazione complica inutilmente lo schema elementare che +abbiamo descritto, dato che non è possibile definire un singolo semaforo, ma +se ne deve creare per forza un insieme. Ma questa in definitiva è solo una +complicazione inutile, il problema è che i semafori del \textit{SysV IPC} +soffrono di altri due, ben più gravi, difetti. + +Il primo difetto è che non esiste una funzione che permetta di creare ed +inizializzare un semaforo in un'unica chiamata; occorre prima creare l'insieme +dei semafori con \func{semget} e poi inizializzarlo con \func{semctl}, si +perde così ogni possibilità di eseguire atomicamente questa operazione. + +Il secondo difetto deriva dalla caratteristica generale degli oggetti del +\textit{SysV IPC} di essere risorse globali di sistema, che non vengono +cancellate quando nessuno le usa più; ci si così a trova a dover affrontare +esplicitamente il caso in cui un processo termina per un qualche errore, +lasciando un semaforo occupato, che resterà tale fino al successivo riavvio +del sistema. Come vedremo esistono delle modalità per evitare tutto ciò, ma +diventa necessario indicare esplicitamente che si vuole il ripristino del +semaforo all'uscita del processo. + + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \begin{lstlisting}[labelstep=0]{} +struct semid_ds +{ + struct ipc_perm sem_perm; /* operation permission struct */ + time_t sem_otime; /* last semop() time */ + time_t sem_ctime; /* last time changed by semctl() */ + unsigned long int sem_nsems; /* number of semaphores in set */ +}; + \end{lstlisting} + \end{minipage} + \normalsize + \caption{La struttura \var{semid\_ds}, associata a ciascun insieme di + semafori.} + \label{fig:ipc_semid_ds} +\end{figure} + +A ciascun insieme di semafori è associata una struttura \var{semid\_ds}, +riportata in \figref{fig:ipc_semid_ds}.\footnote{non si sono riportati i campi + ad uso interno del kernel, che vedremo in \figref{fig:ipc_sem_schema}, che + dipendono dall'implementazione.} Come nel caso delle code di messaggi quando +si crea un nuovo insieme di semafori con \func{semget} questa struttura viene +inizializzata, in particolare il campo \var{sem\_perm} viene inizializzato +come illustrato in \secref{sec:ipc_sysv_access_control} (si ricordi che in +questo caso il permesso di scrittura è in realtà permesso di alterare il +semaforo), per quanto riguarda gli altri campi invece: +\begin{itemize*} +\item il campo \var{sem\_nsems}, che esprime il numero di semafori + nell'insieme, viene inizializzato al valore di \param{nsems}. +\item il campo \var{sem\_ctime}, che esprime il tempo di creazione + dell'insieme, viene inizializzato al tempo corrente. +\item il campo \var{sem\_otime}, che esprime il tempo dell'ultima operazione + effettuata, viene inizializzato a zero. +\end{itemize*} + + +Ciascun semaforo dell'insieme è realizzato come una struttura di tipo +\var{sem} che ne contiene i dati essenziali, la sua definizione\footnote{si è + riportata la definizione originaria del kernel 1.0, che contiene la prima + realizzazione del \textit{SysV IPC} in Linux. In realtà questa struttura + ormai è ridotta ai soli due primi membri, e gli altri vengono calcolati + dinamicamente. La si è utilizzata a scopo di esempio, perché indica tutti i + valori associati ad un semaforo, restituiti dalle funzioni di controllo, e + citati dalle pagine di manuale.} è riportata in \figref{fig:ipc_sem}. Questa +struttura, non è accessibile in user space, ma i valori in essa specificati +possono essere letti in maniera indiretta, attraverso l'uso delle funzioni di +controllo. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \begin{lstlisting}[labelstep=0]{} +struct sem { + short sempid; /* pid of last operation */ + ushort semval; /* current value */ + ushort semncnt; /* num procs awaiting increase in semval */ + ushort semzcnt; /* num procs awaiting semval = 0 */ +}; + \end{lstlisting} + \end{minipage} + \normalsize + \caption{La struttura \var{sem}, che contiene i dati di un singolo semaforo.} + \label{fig:ipc_sem} +\end{figure} + +I dati mantenuti nella struttura, ed elencati in \figref{fig:ipc_sem}, +indicano rispettivamente: +\begin{description*} +\item[\var{semval}] il valore numerico del semaforo. +\item[\var{sempid}] il \acr{pid} dell'ultimo processo che ha eseguito una + operazione sul semaforo. +\item[\var{semncnt}] il numero di processi in attesa che esso venga + incrementato. +\item[\var{semzcnt}] il numero di processi in attesa che esso si annulli. +\end{description*} + +\begin{table}[htb] + \footnotesize + \centering + \begin{tabular}[c]{|c|r|p{8cm}|} + \hline + \textbf{Costante} & \textbf{Valore} & \textbf{Significato} \\ + \hline + \hline + \macro{SEMMNI}& 128 & Numero massimo di insiemi di semafori. \\ + \macro{SEMMSL}& 250 & Numero massimo di semafori per insieme.\\ + \macro{SEMMNS}&\macro{SEMMNI}*\macro{SEMMSL}& Numero massimo di semafori + nel sistema .\\ + \macro{SEMVMX}& 32767 & Massimo valore per un semaforo.\\ + \macro{SEMOPM}& 32 & Massimo numero di operazioni per chiamata a + \func{semop}. \\ + \macro{SEMMNU}&\macro{SEMMNS}& Massimo numero di strutture di ripristino.\\ + \macro{SEMUME}&\macro{SEMOPM}& Massimo numero di voci di ripristino.\\ + \macro{SEMAEM}&\macro{SEMVMX}& valore massimo per l'aggiustamento + all'uscita. \\ + \hline + \end{tabular} + \caption{Valori delle costanti associate ai limiti degli insiemi di + semafori, definite in \file{linux/sem.h}.} + \label{tab:ipc_sem_limits} +\end{table} + +Come per le code di messaggi anche per gli insiemi di semafori esistono una +serie di limiti, i cui valori sono associati ad altrettante costanti, che si +sono riportate in \tabref{tab:ipc_sem_limits}. Alcuni di questi limiti sono al +solito accessibili e modificabili attraverso \func{sysctl} o scrivendo +direttamente nel file \file{/proc/sys/kernel/sem}. + +La funzione che permette di effettuare le varie operazioni di controllo sui +semafori (fra le quali, come accennato, è impropriamente compresa anche la +loro inizializzazione) è \func{semctl}; il suo prototipo è: +\begin{functions} + \headdecl{sys/types.h} + \headdecl{sys/ipc.h} + \headdecl{sys/sem.h} + + \funcdecl{int semctl(int semid, int semnum, int cmd)} + \funcdecl{int semctl(int semid, int semnum, int cmd, union semun arg)} + + Esegue le operazioni di controllo su un semaforo o un insieme di semafori. + + \bodydesc{La funzione restituisce in caso di successo un valore positivo + quanto usata con tre argomenti ed un valore nullo quando usata con + quattro. In caso di errore restituisce -1, ed \var{errno} assumerà uno dei + valori: + \begin{errlist} + \item[\macro{EACCES}] Il processo non ha i privilegi per eseguire + l'operazione richiesta. + \item[\macro{EIDRM}] L'insieme di semafori è stato cancellato. + \item[\macro{EPERM}] Si è richiesto \macro{IPC\_SET} o \macro{IPC\_RMID} ma + il processo non ha privilegi sufficienti ad eseguire l'operazione. + \item[\macro{ERANGE}] Si è richiesto \macro{SETALL} \macro{SETVAL} ma il + valore a cui si vuole impostare il semaforo è minore di zero o maggiore + di \macro{SEMVMX}. + \end{errlist} + ed inoltre \macro{EFAULT} ed \macro{EINVAL}. +} +\end{functions} + +La funzione può avere tre o quattro parametri, a seconda dell'operazione +specificata con \param{cmd}, ed opera o sull'intero insieme specificato da +\param{semid} o sul singolo semaforo di un insieme, specificato da +\param{semnum}. + +Qualora la funzione operi con quattro argomenti \param{arg} è +un argomento generico, che conterrà un dato diverso a seconda dell'azione +richiesta; per unificare l'argomento esso deve essere passato come una +\var{union semun}, la cui definizione, con i possibili valori che può +assumere, è riportata in \figref{fig:ipc_semun}. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \begin{lstlisting}[labelstep=0]{} +union semun { + int val; /* value for SETVAL */ + struct semid_ds *buf; /* buffer for IPC_STAT, IPC_SET */ + unsigned short *array; /* array for GETALL, SETALL */ + /* Linux specific part: */ + struct seminfo *__buf; /* buffer for IPC_INFO */ +}; + \end{lstlisting} + \end{minipage} + \normalsize + \caption{La definizione dei possibili valori di una \var{union semun}, usata + come quarto argomento della funzione \func{semctl}.} + \label{fig:ipc_semun} +\end{figure} + +Come già accennato sia il comportamento della funzione che il numero di +parametri con cui deve essere invocata, dipendono dal valore dell'argomento +\param{cmd}, che specifica l'azione da intraprendere; i valori validi (che +cioè non causano un errore di \macro{EINVAL}) per questo argomento sono i +seguenti: +\begin{basedescript}{\desclabelwidth{2.2cm}\desclabelstyle{\nextlinelabel}} +\item[\macro{IPC\_STAT}] Legge i dati dell'insieme di semafori, copiando il + contenuto della relativa struttura \var{semid\_ds} all'indirizzo specificato + con \var{arg.buf}. Occorre avere il permesso di lettura. L'argomento + \param{semnum} viene ignorato. +\item[\macro{IPC\_RMID}] Rimuove l'insieme di semafori e le relative strutture + dati, con effetto immediato. Tutti i processi che erano stato di + \textit{sleep} vengono svegliati, ritornando con un errore di \macro{EIDRM}. + L'userid effettivo del processo deve corrispondere o al creatore o al + proprietario dell'insieme, o all'amministratore. L'argomento \param{semnum} + viene ignorato. +\item[\macro{IPC\_SET}] Permette di modificare i permessi ed il proprietario + dell'insieme. I valori devono essere passati in una struttura + \var{semid\_ds} puntata da \param{arg.buf} di cui saranno usati soltanto i + campi \var{sem\_perm.uid}, \var{sem\_perm.gid} e i nove bit meno + significativi di \var{sem\_perm.mode}. L'userid effettivo del processo deve + corrispondere o al creatore o al proprietario dell'insieme, o + all'amministratore. L'argomento \param{semnum} viene ignorato. +\item[\macro{GETALL}] Restituisce il valore corrente di ciascun semaforo + dell'insieme (corrispondente al campo \var{semval} di \var{sem}) nel vettore + indicato da \param{arg.array}. Occorre avere il permesso di lettura. + L'argomento \param{semnum} viene ignorato. +\item[\macro{GETNCNT}] Restituisce come valore di ritorno della funzione il + numero di processi in attesa che il semaforo \param{semnum} dell'insieme + \param{semid} venga incrementato (corrispondente al campo \var{semncnt} di + \var{sem}); va invocata con tre argomenti. Occorre avere il permesso di + lettura. +\item[\macro{GETPID}] Restituisce come valore di ritorno della funzione il + \acr{pid} dell'ultimo processo che ha compiuto una operazione sul semaforo + \param{semnum} dell'insieme \param{semid} (corrispondente al campo + \var{sempid} di \var{sem}); va invocata con tre argomenti. Occorre avere il + permesso di lettura. +\item[\macro{GETVAL}] Restituisce come valore di ritorno della funzione il il + valore corrente del semaforo \param{semnum} dell'insieme \param{semid} + (corrispondente al campo \var{semval} di \var{sem}); va invocata con tre + argomenti. Occorre avere il permesso di lettura. +\item[\macro{GETZCNT}] Restituisce come valore di ritorno della funzione il + numero di processi in attesa che il valore del semaforo \param{semnum} + dell'insieme \param{semid} diventi nullo (corrispondente al campo + \var{semncnt} di \var{sem}); va invocata con tre argomenti. Occorre avere + il permesso di lettura. +\item[\macro{SETALL}] Inizializza il valore di tutti i semafori dell'insieme, + aggiornando il campo \var{sem\_ctime} di \var{semid\_ds}. I valori devono + essere passati nel vettore indicato da \param{arg.array}. Si devono avere i + privilegi di scrittura sul semaforo. L'argomento \param{semnum} viene + ignorato. +\item[\macro{SETVAL}] Inizializza il semaforo \param{semnum} al valore passato + dall'argomento \param{arg.val}, aggiornando il campo \var{sem\_ctime} di + \var{semid\_ds}. Si devono avere i privilegi di scrittura sul semaforo. +\end{basedescript} + +Quando si imposta il valore di un semaforo (sia che lo si faccia per tutto +l'insieme con \macro{SETALL}, che per un solo semaforo con \macro{SETVAL}), i +processi in attesa su di esso reagiscono di conseguenza al cambiamento di +valore. Inoltre la coda delle operazioni di ripristino viene cancellata per +tutti i semafori il cui valore viene modificato. + +\begin{table}[htb] + \footnotesize + \centering + \begin{tabular}[c]{|c|l|} + \hline + \textbf{Operazione} & \textbf{Valore restituito} \\ + \hline + \hline + \macro{GETNCNT}& valore di \var{semncnt}.\\ + \macro{GETPID} & valore di \var{sempid}.\\ + \macro{GETVAL} & valore di \var{semval}.\\ + \macro{GETZCNT}& valore di \var{semzcnt}.\\ + \hline + \end{tabular} + \caption{Valori di ritorno della funzione \func{semctl}.} + \label{tab:ipc_semctl_returns} +\end{table} + +Il valore di ritorno della funzione in caso di successo dipende +dall'operazione richiesta; per tutte le operazioni che richiedono quattro +argomenti esso è sempre nullo, per le altre operazioni, elencate in +\tabref{tab:ipc_semctl_returns} viene invece restituito il valore richiesto, +corrispondente al campo della struttura \var{sem} indicato nella seconda +colonna della tabella. + +Le operazioni ordinarie sui semafori, come l'acquisizione o il rilascio degli +stessi (in sostanza tutte quelle non comprese nell'uso di \func{semctl}) +vengono effettuate con la funzione \func{semop}, il cui prototipo è: +\begin{functions} + \headdecl{sys/types.h} + \headdecl{sys/ipc.h} + \headdecl{sys/sem.h} + + \funcdecl{int semop(int semid, struct sembuf *sops, unsigned nsops)} + + Esegue le operazioni ordinarie su un semaforo o un insieme di semafori. + + \bodydesc{La funzione restituisce 0 in caso di successo e -1 in caso di + errore, nel qual caso \var{errno} assumerà uno dei valori: + \begin{errlist} + \item[\macro{EACCES}] Il processo non ha i privilegi per eseguire + l'operazione richiesta. + \item[\macro{EIDRM}] L'insieme di semafori è stato cancellato. + \item[\macro{ENOMEM}] Si è richiesto un \macro{SEM\_UNDO} ma il sistema + non ha le risorse per allocare la struttura di ripristino. + \item[\macro{EAGAIN}] Un'operazione comporterebbe il blocco del processo, + ma si è specificato \macro{IPC\_NOWAIT} in \var{sem\_flg}. + \item[\macro{EINTR}] La funzione, bloccata in attesa dell'esecuzione + dell'operazione, viene interrotta da un segnale. + \item[\macro{E2BIG}] L'argomento \param{nsops} è maggiore del numero + massimo di operazioni \macro{SEMOPM}. + \item[\macro{ERANGE}] Per alcune operazioni il valore risultante del + semaforo viene a superare il limite massimo \macro{SEMVMX}. + \end{errlist} + ed inoltre \macro{EFAULT} ed \macro{EINVAL}. +} +\end{functions} + +La funzione permette di eseguire operazioni multiple sui singoli semafori di +un insieme. La funzione richiede come primo argomento l'identificatore +\param{semid} dell'insieme su cui si vuole operare. Il numero di operazioni da +effettuare viene specificato con l'argomento \param{nsop}, mentre il loro +contenuto viene passato con un puntatore ad un vettore di strutture +\var{sembuf} nell'argomento \param{sops}. Le operazioni richieste vengono +effettivamente eseguite se e soltanto se è possibile effettuarle tutte quante. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \begin{lstlisting}[labelstep=0]{} +struct sembuf +{ + unsigned short int sem_num; /* semaphore number */ + short int sem_op; /* semaphore operation */ + short int sem_flg; /* operation flag */ +}; + \end{lstlisting} + \end{minipage} + \normalsize + \caption{La struttura \var{sembuf}, usata per le operazioni sui + semafori.} + \label{fig:ipc_sembuf} +\end{figure} + +Il contenuto di ciascuna operazione deve essere specificato attraverso una +opportuna struttura \var{sembuf} (la cui definizione è riportata in +\figref{fig:ipc_sembuf}) che il programma chiamante deve avere cura di +allocare in un opportuno vettore. La struttura permette di indicare il +semaforo su cui operare, il tipo di operazione, ed un flag di controllo. +Il campo \var{sem\_num} serve per indicare a quale semaforo dell'insieme fa +riferimento l'operazione; si ricordi che i semafori sono numerati come in un +vettore, per cui il primo semaforo corrisponde ad un valore nullo di +\var{sem\_num}. + +Il campo \var{sem\_flg} è un flag, mantenuto come maschera binaria, per il +quale possono essere impostati i due valori \macro{IPC\_NOWAIT} e +\macro{SEM\_UNDO}. Impostando \macro{IPC\_NOWAIT} si fa si che, invece di +bloccarsi (in tutti quei casi in cui l'esecuzione di una operazione richiede +che il processo vada in stato di \textit{sleep}), \func{semop} ritorni +immediatamente con un errore di \macro{EAGAIN}. Impostando \macro{SEM\_UNDO} +si richiede invece che l'operazione venga registrata in modo che il valore del +semaforo possa essere ripristinato all'uscita del processo. + +Infine \var{sem\_op} è il campo che controlla l'operazione che viene eseguita +e determina il comportamento della chiamata a \func{semop}; tre sono i casi +possibili: +\begin{basedescript}{\desclabelwidth{2.0cm}} +\item[\var{sem\_op}$>0$] In questo caso il valore di \var{sem\_op} viene + aggiunto al valore corrente di \var{semval}. La funzione ritorna + immediatamente (con un errore di \macro{ERANGE} qualora si sia superato il + limite \macro{SEMVMX}) ed il processo non viene bloccato in nessun caso. + Specificando \macro{SEM\_UNDO} si aggiorna il contatore per il ripristino + del valore del semaforo. Al processo chiamante è richiesto il privilegio di + alterazione (scrittura) sull'insieme di semafori. + +\item[\var{sem\_op}$=0$] Nel caso \var{semval} sia zero l'esecuzione procede + immediatamente. Se \var{semval} è diverso da zero il comportamento è + controllato da \var{sem\_flg}, se è stato impostato \macro{IPC\_NOWAIT} la + funzione ritorna con un errore di \macro{EAGAIN}, altrimenti viene + incrementato \var{semzcnt} di uno ed il processo resta in stato di + \textit{sleep} fintanto che non si ha una delle condizioni seguenti: + \begin{itemize*} + \item \var{semval} diventa zero, nel qual caso \var{semzcnt} viene + decrementato di uno. + \item l'insieme di semafori viene rimosso, nel qual caso \func{semop} ritorna + un errore di \macro{EIDRM}. + \item il processo chiamante riceve un segnale, nel qual caso \var{semzcnt} + viene decrementato di uno e \func{semop} ritorna un errore di + \macro{EINTR}. + \end{itemize*} + Al processo chiamante è richiesto il privilegio di lettura dell'insieme dei + semafori. + +\item[\var{sem\_op}$<0$] Nel caso in cui \var{semval} è maggiore o uguale del + valore assoluto di \var{sem\_op} (se cioè la somma dei due valori resta + positiva o nulla) i valori vengono sommati e la funzione ritorna + immediatamente; qualora si sia impostato \macro{SEM\_UNDO} viene anche + aggiornato il contatore per il ripristino del valore del semaforo. In caso + contrario (quando cioè la somma darebbe luogo ad un valore di \var{semval} + negativo) se si è impostato \macro{IPC\_NOWAIT} la funzione ritorna con un + errore di \macro{EAGAIN}, altrimenti viene incrementato di uno \var{semncnt} + ed il processo resta in stato di \textit{sleep} fintanto che non si ha una + delle condizioni seguenti: + \begin{itemize*} + \item \var{semval} diventa maggiore o uguale del valore assoluto di + \var{sem\_op}, nel qual caso \var{semncnt} viene decrementato di uno, il + valore di \var{sem\_op} viene sommato a \var{semval}, e se era stato + impostato \macro{SEM\_UNDO} viene aggiornato il contatore per il + ripristino del valore del semaforo. + \item l'insieme di semafori viene rimosso, nel qual caso \func{semop} ritorna + un errore di \macro{EIDRM}. + \item il processo chiamante riceve un segnale, nel qual caso \var{semncnt} + viene decrementato di uno e \func{semop} ritorna un errore di + \macro{EINTR}. + \end{itemize*} + Al processo chiamante è richiesto il privilegio di alterazione (scrittura) + sull'insieme di semafori. +\end{basedescript} + +In caso di successo della funzione viene aggiornato di \var{sempid} per ogni +semaforo modificato al valore del \acr{pid} del processo chiamante; inoltre +vengono pure aggiornati al tempo corrente i campi \var{sem\_otime} e +\var{sem\_ctime}. + +Dato che, come già accennato in precedenza, in caso di uscita inaspettata i +semafori possono restare occupati, abbiamo visto come \func{semop} permetta di +attivare un meccanismo di ripristino attraverso l'uso del flag +\macro{SEM\_UNDO}. Il meccanismo è implementato tramite una apposita struttura +\var{sem\_undo}, associata ad ogni processo per ciascun semaforo che esso ha +modificato; all'uscita i semafori modificati vengono ripristinati, e le +strutture disallocate. Per mantenere coerente il comportamento queste +strutture non vengono ereditate attraverso una \func{fork} (altrimenti si +avrebbe un doppio ripristino), mentre passano inalterate nell'esecuzione di +una \func{exec} (altrimenti non si avrebbe ripristino). + +Tutto questo però ha un problema di fondo. Per capire di cosa si tratta +occorre fare riferimento all'implementazione usata in Linux, che è riportata +in maniera semplificata nello schema di \figref{fig:ipc_sem_schema}. Si è +presa come riferimento l'architettura usata fino al kernel 2.2.x che è più +semplice (ed illustrata in dettaglio in \cite{tlk}); nel kernel 2.4.x la +struttura del \textit{SysV IPC} è stata modificata, ma le definizioni relative +a queste strutture restano per compatibilità.\footnote{in particolare con le + vecchie versioni delle librerie del C, come le libc5.} + +\begin{figure}[htb] + \centering \includegraphics[width=15cm]{img/semtruct} + \caption{Schema della struttura di un insieme di semafori.} + \label{fig:ipc_sem_schema} +\end{figure} + +Alla creazione di un nuovo insieme viene allocata una nuova strutture +\var{semid\_ds} ed il relativo vettore di strutture \var{sem}. Quando si +richiede una operazione viene anzitutto verificato che tutte le operazioni +possono avere successo; se una di esse comporta il blocco del processo il +kernel crea una struttura \var{sem\_queue} che viene aggiunta in fondo alla +coda di attesa associata a ciascun insieme di semafori\footnote{che viene + referenziata tramite i campi \var{sem\_pending} e \var{sem\_pending\_last} + di \var{semid\_ds}.}. Nella struttura viene memorizzato il riferimento alle +operazioni richieste (nel campo \var{sops}, che è un puntatore ad una +struttura \var{sembuf}) e al processo corrente (nel campo \var{sleeper}) poi +quest'ultimo viene messo stato di attesa e viene invocato lo +scheduler\index{scheduler} per passare all'esecuzione di un altro processo. + +Se invece tutte le operazioni possono avere successo queste vengono eseguite +immediatamente, dopo di che il kernel esegue una scansione della coda di +attesa (a partire da \var{sem\_pending}) per verificare se qualcuna delle +operazioni sospese in precedenza può essere eseguita, nel qual caso la +struttura \var{sem\_queue} viene rimossa e lo stato del processo associato +all'operazione (\var{sleeper}) viene riportato a \textit{running}; il tutto +viene ripetuto fin quando non ci sono più operazioni eseguibili o si è +svuotata la coda. + +Per gestire il meccanismo del ripristino tutte le volte che per un'operazione +si è specificato il flag \macro{SEM\_UNDO} viene mantenuta per ciascun insieme +di semafori una apposita struttura \var{sem\_undo} che contiene (nel vettore +puntato dal campo \var{semadj}) un valore di aggiustamento per ogni semaforo +cui viene sommato l'opposto del valore usato per l'operazione. + +Queste strutture sono mantenute in due liste,\footnote{rispettivamente + attraverso i due campi \var{id\_next} e \var{proc\_next}.} una associata +all'insieme di cui fa parte il semaforo, che viene usata per invalidare le +strutture se questo viene cancellato o per azzerarle se si è eseguita una +operazione con \func{semctl}; l'altra associata al processo che ha eseguito +l'operazione;\footnote{attraverso il campo \var{semundo} di + \var{task\_struct}, come mostrato in \ref{fig:ipc_sem_schema}.} quando un +processo termina, la lista ad esso associata viene scandita e le operazioni +applicate al semaforo. + +Siccome un processo può accumulare delle richieste di ripristino per semafori +differenti chiamate attraverso diverse chiamate a \func{semop}, si pone il +problema di come eseguire il ripristino dei semafori all'uscita del processo, +ed in particolare se questo può essere fatto atomicamente. Il punto è cosa +succede quando una delle operazioni previste per il ripristino non può essere +eseguita immediatamente perché ad esempio il semaforo è occupato; in tal caso +infatti, se si pone il processo in stato di \textit{sleep} aspettando la +disponibilità del semaforo (come faceva l'implementazione originaria) si perde +l'atomicità dell'operazione. La scelta fatta dal kernel è pertanto quella di +effettuare subito le operazioni che non prevedono un blocco del processo e di +ignorare silenziosamente le altre; questo però comporta il fatto che il +ripristino non è comunque garantito in tutte le occasioni. + +Come esempio di uso dell'interfaccia dei semafori vediamo come implementare +con essa dei semplici \textit{mutex} (cioè semafori binari), tutto il codice +in questione, contenuto nel file \file{wrappers.h} allegato ai sorgenti, è +riportato in \figref{fig:ipc_mutex_create}. Utilizzeremo l'interfaccia per +creare un insieme contenente un singolo semaforo, per il quale poi useremo un +valore unitario per segnalare la disponibilità della risorsa, ed un valore +nullo per segnalarne l'indisponibilità. + +\begin{figure}[!bht] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \begin{lstlisting}{} +/* + * Function MutexCreate: create a mutex/semaphore + */ +inline int MutexCreate(key_t ipc_key) +{ + const union semun semunion={1}; /* semaphore union structure */ + int sem_id, ret; + sem_id = semget(ipc_key, 1, IPC_CREAT|0666); /* get semaphore ID */ + if (sem_id == -1) { /* if error return code */ + return sem_id; + } + ret = semctl(sem_id, 0, SETVAL, semunion); /* init semaphore */ + if (ret == -1) { + return ret; + } + return sem_id; +} +/* + * Function MutexFind: get the semaphore/mutex Id given the IPC key value + */ +inline int MutexFind(key_t ipc_key) +{ + return semget(ipc_key,1,0); +} +/* + * Function MutexRead: read the current value of the mutex/semaphore + */ +inline int MutexRead(int sem_id) +{ + return semctl(sem_id, 0, GETVAL); +} +/* + * Define sembuf structures to lock and unlock the semaphore + */ +struct sembuf sem_lock={ /* to lock semaphore */ + 0, /* semaphore number (only one so 0) */ + -1, /* operation (-1 to use resource) */ + SEM_UNDO}; /* flag (set for undo at exit) */ +struct sembuf sem_ulock={ /* to unlock semaphore */ + 0, /* semaphore number (only one so 0) */ + 1, /* operation (1 to release resource) */ + SEM_UNO}; /* flag (in this case 0) */ +/* + * Function MutexLock: to lock a mutex/semaphore + */ +inline int MutexLock(int sem_id) +{ + return semop(sem_id, &sem_lock, 1); +} +/* + * Function MutexUnlock: to unlock a mutex/semaphore + */ +inline int MutexUnlock(int sem_id) +{ + return semop(sem_id, &sem_ulock, 1); +} + \end{lstlisting} + \end{minipage} + \normalsize + \caption{Il codice delle funzioni che permettono di creare o recuperare + l'identificatore di un semaforo da utilizzare come \textit{mutex}.} + \label{fig:ipc_mutex_create} +\end{figure} + +La prima funzione (\texttt{\small 1--17}) è \func{MutexCreate} che data una +chiave crea il semaforo usato per il mutex e lo inizializza, restituendone +l'identificatore. Il primo passo (\texttt{\small 8}) è chiamare \func{semget} +con \macro{IPC\_CREATE} per creare il semaforo qualora non esista, +assegnandogli i privilegi di lettura e scrittura per tutti. In caso di errore +(\texttt{\small 9--11}) si ritorna subito il risultato di \func{semget}, +altrimenti (\texttt{\small 12}) si inizializza il semaforo chiamando +\func{semctl} con il comando \macro{SETVAL}, utilizzando l'unione +\var{semunion} dichiarata ed avvalorata in precedenza (\texttt{\small 6}) ad 1 +per significare che risorsa è libera. In caso di errore (\texttt{\small + 13--16}) si restituisce il valore di ritorno di \func{semctl}, altrimenti si +ritorna l'identificatore del semaforo. + +La seconda funzione (\texttt{\small 18--24}) è \func{MutexFind}, che data una +chiave, restituisce l'identificatore del semaforo ad essa associato. La +comprensione del suo funzionamento è immediata in quanto è solo un +\textit{wrapper}\footnote{si chiama così una funzione usata per fare da + \textsl{involucro} alla chiamata di un altra, usata in genere per + semplificare un'interfaccia (come in questo caso) o per utilizzare con la + stessa funzione diversi substrati (librerie, ecc.) che possono fornire le + stesse funzionalità.} di \func{semget} per cercare l'identificatore +associato alla chiave, restituendo direttamente il valore di ritorno della +funzione. + +La terza funzione (\texttt{\small 25--31}) è \func{MutexRead} che, dato +l'identificatore, restituisce il valore del mutex. Anche in questo caso la +funzione è un \textit{wrapper} per la chiamata di \func{semctl}, questa volta +con il comando \macro{GETVAL}, che permette di restituire il valore del +semaforo. + +La quarta e la quinta funzione (\texttt{\small 43--56}) sono \func{MutexLock}, +e \func{MutexUnlock}, che permettono rispettivamente di bloccare e sbloccare +il mutex. Entrambe fanno da wrapper per \func{semop}, utilizzando le due +strutture \var{sem\_lock} e \var{sem\_unlock} definite in precedenza +(\texttt{\small 32--42}). Si noti come per queste ultime si sia fatto uso +dell'opzione \macro{SEM\_UNDO} per evitare che il semaforo resti bloccato in +caso di terminazione imprevista del processo. Si noti infine come, essendo +tutte le funzioni riportate in \figref{fig:ipc_mutex_create} estremamente +semplici, se si sono definite tutte come \ctyp{inline}.\footnote{la direttiva + \func{inline} viene usata per dire al compilatore di non trattare la + funzione cui essa fa riferimento come una funzione, ma di inserire il codice + direttamente nel testo del programma. Anche se i compilatori più moderni + sono in grado di effettuare da soli queste manipolazioni (impostando le + opportune ottimizzazioni) questa è una tecnica usata per migliorare le + prestazioni per le funzioni piccole ed usate di frequente, in tal caso + infatti le istruzioni per creare un nuovo frame nello stack per chiamare la + funzione costituirebbero una parte rilevante del codice, appesantendo + inutilmente il programma. Originariamente questa era fatto utilizzando delle + macro, ma queste hanno tutta una serie di problemi di sintassi nel passaggio + degli argomenti (si veda ad esempio \cite{PratC} che in questo modo possono + essere evitati.} + + +Chiamare \func{MutexLock} decrementa il valore del semaforo: se questo è +libero (ha già valore 1) sarà bloccato (valore nullo), se è bloccato la +chiamata a \func{semop} si bloccherà fintanto che la risorsa non venga +rilasciata. Chiamando \func{MutexUnlock} il valore del semaforo sarà +incrementato di uno, sbloccandolo qualora fosse bloccato. Si noti che occorre +eseguire sempre prima \func{MutexLock} e poi \func{MutexUnlock}, perché se per +un qualche errore si esegue più volte quest'ultima il valore del semaforo +crescerebbe oltre 1, e \func{MutexLock} non avrebbe più l'effetto aspettato +(bloccare la risorsa quando questa è considerata libera). Si tenga presente +che usare \func{MutexRead} per controllare il valore dei mutex prima di +proseguire non servirebbe comunque, dato che l'operazione non sarebbe atomica. +Vedremo in \secref{sec:ipc_posix_sem} come è possibile ottenere un'interfaccia +analoga senza questo problemi usando il file locking. + + + + + +\subsection{Memoria condivisa} +\label{sec:ipc_sysv_shm} + +Il terzo oggetto introdotto dal \textit{SysV IPC} è quello dei segmenti di +memoria condivisa. La funzione che permette di ottenerne uno è \func{shmget}, +ed il suo prototipo è: +\begin{functions} + \headdecl{sys/types.h} + \headdecl{sys/ipc.h} + \headdecl{sys/shm.h} + + \funcdecl{int shmget(key\_t key, int size, int flag)} + + Restituisce l'identificatore di una memoria condivisa. + + \bodydesc{La funzione restituisce l'identificatore (un intero positivo) o -1 + in caso di errore, nel qual caso \var{errno} assumerà i valori: + \begin{errlist} + \item[\macro{ENOSPC}] Si è superato il limite (\macro{SHMMNI}) sul numero + di segmenti di memoria nel sistema, o cercato di allocare un segmento le + cui dimensioni fanno superare il limite di sistema (\macro{SHMALL}) per + la memoria ad essi riservata. + \item[\macro{EINVAL}] Si è richiesta una dimensione per un nuovo segmento + maggiore di \macro{SHMMAX} o minore di \macro{SHMMIN}, o se il segmento + già esiste \param{size} è maggiore delle sue dimensioni. + \item[\macro{ENOMEM}] Il sistema non ha abbastanza memoria per poter + contenere le strutture per un nuovo segmento di memoria condivisa. + \end{errlist} + ed inoltre \macro{EACCES}, \macro{ENOENT}, \macro{EEXIST}, \macro{EIDRM}, + con lo stesso significato che hanno per \func{msgget}.} +\end{functions} + +La funzione, come \func{semget}, è del tutto analoga a \func{msgget}, ed +identico è l'uso degli argomenti \param{key} e \param{flag} per cui non +ripeteremo quanto detto al proposito in \secref{sec:ipc_sysv_mq}. L'argomento +\param{size} specifica invece la dimensione, in byte, del segmento, che viene +comunque arrotondata al multiplo superiore di \macro{PAGE\_SIZE}. + +La memoria condivisa è la forma più veloce di comunicazione fra due processi, +in quanto permette agli stessi di vedere nel loro spazio di indirizzi una +stessa sezione di memoria. Pertanto non è necessaria nessuna operazione di +copia per trasmettere i dati da un processo all'altro, in quanto ciascuno può +accedervi direttamente con le normali operazioni di lettura e scrittura dei +dati in memoria. + +Ovviamente tutto questo ha un prezzo, ed il problema fondamentale della +memoria condivisa è la sincronizzazione degli accessi. È evidente infatti che +se un processo deve scambiare dei dati con un altro, si deve essere sicuri che +quest'ultimo non acceda al segmento di memoria condivisa prima che il primo +non abbia completato le operazioni di scrittura, inoltre nel corso di una +lettura si deve essere sicuri che i dati restano coerenti e non vengono +sovrascritti da un accesso in scrittura sullo stesso segmento da parte di un +altro processo; per questo in genere la memoria condivisa viene sempre +utilizzata in abbinamento ad un meccanismo di sincronizzazione, il che, di +norma, significa insieme a dei semafori. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \begin{lstlisting}[labelstep=0]{} +struct shmid_ds { + struct ipc_perm shm_perm; /* operation perms */ + int shm_segsz; /* size of segment (bytes) */ + time_t shm_atime; /* last attach time */ + time_t shm_dtime; /* last detach time */ + time_t shm_ctime; /* last change time */ + unsigned short shm_cpid; /* pid of creator */ + unsigned short shm_lpid; /* pid of last operator */ + short shm_nattch; /* no. of current attaches */ +}; + \end{lstlisting} + \end{minipage} + \normalsize + \caption{La struttura \var{shmid\_ds}, associata a ciascun segmento di + memoria condivisa.} + \label{fig:ipc_shmid_ds} +\end{figure} + +A ciascun segmento di memoria condivisa è associata una struttura +\var{shmid\_ds}, riportata in \figref{fig:ipc_shmid_ds}. Come nel caso delle +code di messaggi quando si crea un nuovo segmento di memoria condivisa con +\func{shmget} questa struttura viene inizializzata, in particolare il campo +\var{shm\_perm} viene inizializzato come illustrato in +\secref{sec:ipc_sysv_access_control}, e valgono le considerazioni ivi fatte +relativamente ai permessi di accesso; per quanto riguarda gli altri campi +invece: +\begin{itemize*} +\item il campo \var{shm\_segsz}, che esprime la dimensione del segmento, viene + inizializzato al valore di \param{size}. +\item il campo \var{shm\_ctime}, che esprime il tempo di creazione del + segmento, viene inizializzato al tempo corrente. +\item i campi \var{shm\_atime} e \var{shm\_atime}, che esprimono + rispettivamente il tempo dell'ultima volta che il segmento è stato + agganciato o sganciato da un processo, vengono inizializzati a zero. +\item il campo \var{shm\_lpid}, che esprime il \acr{pid} del processo che ha + eseguito l'ultima operazione, viene inizializzato a zero. +\item il campo \var{shm\_cpid}, che esprime il \acr{pid} del processo che ha + creato il segmento, viene inizializzato al \acr{pid} del processo chiamante. +\item il campo \var{shm\_nattac}, che esprime il numero di processi agganciati + al segmento viene inizializzato a zero. +\end{itemize*} + +Come per le code di messaggi e gli insiemi di semafori, anche per i segmenti +di memoria condivisa esistono una serie di limiti, i cui valori, riportati in +\tabref{tab:ipc_shm_limits} sono associati ad altrettante costanti. Alcuni di +questi limiti sono al solito accessibili e modificabili attraverso +\func{sysctl} o scrivendo direttamente nei rispettivi file di +\file{/proc/sys/kernel/}. + +\begin{table}[htb] + \footnotesize + \centering + \begin{tabular}[c]{|c|r|c|p{7cm}|} + \hline + \textbf{Costante} & \textbf{Valore} & \textbf{File in \texttt{proc}} + & \textbf{Significato} \\ + \hline + \hline + \macro{SHMALL}&0x200000&\file{shmall}& Numero massimo di pagine che + possono essere usate per i segmenti di + memoria condivisa. \\ + \macro{SHMMAX}&0x2000000&\file{shmmax}& Dimensione massima di un segmento + di memoria condivisa.\\ + \macro{SHMMNI}&4096&\file{msgmni}& Numero massimo di segmenti di memoria + condivisa presenti nel kernel.\\ + \macro{SHMMIN}& 1& --- & Dimensione minima di un segmento di + memoria condivisa. \\ + \hline + \end{tabular} + \caption{Valori delle costanti associate ai limiti dei segmenti di memoria + condivisa, insieme al relativo file in \file{/proc/sys/kernel/} ed al + valore preimpostato presente nel sistema.} + \label{tab:ipc_shm_limits} +\end{table} + +Al solito la funzione che permette di effettuare le operazioni di controllo su +un segmento di memoria condivisa è \func{shmctl}; il suo prototipo è: +\begin{functions} + \headdecl{sys/ipc.h} + \headdecl{sys/shm.h} + + \funcdecl{int shmctl(int shmid, int cmd, struct shmid\_ds *buf)} + + Esegue le operazioni di controllo su un segmento di memoria condivisa. + + \bodydesc{La funzione restituisce 0 in caso di successo e -1 in caso di + errore, nel qual caso \var{errno} assumerà i valori: + \begin{errlist} + \item[\macro{EACCES}] Si è richiesto \macro{IPC\_STAT} ma i permessi non + consentono l'accesso in lettura al segmento. + \item[\macro{EINVAL}] O \param{shmid} o \param{cmd} hanno valori non + validi. + \item[\macro{EIDRM}] L'argomento \param{shmid} fa riferimento ad un + segmento che è stato cancellato. + \item[\macro{EPERM}] Si è specificato un comando con \macro{IPC\_SET} o + \macro{IPC\_RMID} senza i permessi necessari. + \item[\macro{EOVERFLOW}] L'argomento \param{shmid} fa riferimento ad un + segmento che è stato cancellato. + \end{errlist} + ed inoltre \macro{EFAULT}.} +\end{functions} + +Il comportamento della funzione dipende dal valore del comando passato +attraverso l'argomento \param{cmd}, i valori possibili sono i seguenti: +\begin{basedescript}{\desclabelwidth{2.2cm}\desclabelstyle{\nextlinelabel}} +\item[\macro{IPC\_STAT}] Legge le informazioni riguardo il segmento di memoria + condivisa nella struttura \var{shmid\_ds} puntata da \param{buf}. Occorre + avere il permesso di lettura sulla coda. +\item[\macro{IPC\_RMID}] Marca il segmento di memoria condivisa per la + rimozione, questo verrà cancellato effettivamente solo quando l'ultimo + processo ad esso agganciato si sarà staccato. Questo comando può essere + eseguito solo da un processo con userid effettivo, corrispondente al + creatore o al proprietario della coda, o all'amministratore. +\item[\macro{IPC\_SET}] Permette di modificare i permessi ed il proprietario + del segmento. Per modificare i valori di \var{shm\_perm.mode}, + \var{shm\_perm.uid} e \var{shm\_perm.gid} occorre essere il proprietario o + il creatore della coda, oppure l'amministratore. Compiuta l'operazione + aggiorna anche il valore del campo \var{shm\_ctime}. +\item[\macro{SHM\_LOCK}] Abilita il \textit{memory locking}\index{memory + locking} (vedi \secref{sec:proc_mem_lock}) sul segmento di memoria + condivisa. Solo l'amministratore può utilizzare questo comando. +\item[\macro{SHM\_UNLOCK}] Disabilita il \textit{memory locking}. Solo + l'amministratore può utilizzare questo comando. +\end{basedescript} +i primi tre comandi sono gli stessi già visti anche per le code ed i semafori, +gli ultimi due sono delle estensioni previste da Linux. + +Per utilizzare i segmenti di memoria condivisa l'interfaccia prevede due +funzioni, la prima è \func{shmat}, che serve ad agganciare un segmento al +processo chiamante, in modo che quest'ultimo possa vederlo nel suo spazio di +indirizzi; il suo prototipo è: +\begin{functions} + \headdecl{sys/types.h} + \headdecl{sys/shm.h} + + \funcdecl{void *shmat(int shmid, const void *shmaddr, int shmflg)} + Aggancia al processo un segmento di memoria condivisa. + + \bodydesc{La funzione restituisce l'indirizzo del segmento in caso di + successo, e -1 in caso di errore, nel qual caso \var{errno} assumerà i + valori: + \begin{errlist} + \item[\macro{EACCES}] Il processo non ha i privilegi per accedere al + segmento nella modalità richiesta. + \item[\macro{EINVAL}] Si è specificato un identificatore invalido per + \param{shmid}, o un indirizzo non allineato sul confine di una pagina + per \param{shmaddr}. + \end{errlist} + ed inoltre \macro{ENOMEM}.} +\end{functions} + +La funzione inserisce un segmento di memoria condivisa all'interno dello +spazio di indirizzi del processo, in modo che questo possa accedervi +direttamente, la situazione dopo l'esecuzione di \func{shmat} è illustrata in +\figref{fig:ipc_shmem_layout} (per la comprensione del resto dello schema si +ricordi quanto illustrato al proposito in \secref{sec:proc_mem_layout}). Si +tenga presente che la funzione ha successo anche se il segmento è stato +marcato per la cancellazione. + +\begin{figure}[htb] + \centering + \includegraphics[height=10cm]{img/sh_memory_layout} + \caption{Disposizione dei segmenti di memoria di un processo quando si è + agganciato un segmento di memoria condivisa.} + \label{fig:ipc_shmem_layout} +\end{figure} + +L'argomento \param{shmaddr} specifica a quale indirizzo\footnote{Lo standard + SVID prevede che l'argomento \param{shmaddr} sia di tipo \ctyp{char *}, così + come il valore di ritorno della funzione. In Linux è stato così con le + \acr{libc4} e le \acr{libc5}, con il passaggio alle \acr{glibc} il tipo di + \param{shmaddr} è divenuto un \ctyp{const void *} e quello del valore di + ritorno un \ctyp{void *}.} deve essere associato il segmento, se il valore +specificato è \macro{NULL} è il sistema a scegliere opportunamente un'area di +memoria libera (questo è il modo più portabile e sicuro di usare la funzione). +Altrimenti il kernel aggancia il segmento all'indirizzo specificato da +\param{shmaddr}; questo però può avvenire solo se l'indirizzo coincide con il +limite di una pagina, cioè se è un multiplo esatto del parametro di sistema +\macro{SHMLBA}, che in Linux è sempre uguale \macro{PAGE\_SIZE}. + +L'argomento \param{shmflg} permette di cambiare il comportamento della +funzione; esso va specificato come maschera binaria, i bit utilizzati sono +solo due e sono identificati dalle costanti \macro{SHM\_RND} e +\macro{SHM\_RDONLY}, che vanno combinate con un OR aritmetico. Specificando +\macro{SHM\_RND} si evita che \func{shmat} ritorni un errore quando +\param{shmaddr} non è allineato ai confini di una pagina. Si può quindi usare +un valore qualunque per \param{shmaddr}, e il segmento verrà comunque +agganciato, ma al più vicino multiplo di \macro{SHMLBA} (il nome della +costante sta infatti per \textit{rounded}, e serve per specificare un +indirizzo come arrotondamento). + +Il secondo bit permette di agganciare il segmento in sola lettura (si ricordi +che anche le pagine di memoria hanno dei permessi), in tal caso un tentativo +di scrivere sul segmento comporterà una violazione di accesso con l'emissione +di un segnale di \macro{SIGSEGV}. Il comportamento usuale di \func{shmat} è +quello di agganciare il segmento con l'accesso in lettura e scrittura (ed il +processo deve aver questi permessi in \var{shm\_perm}), non è prevista la +possibilità di agganciare un segmento in sola scrittura. + +In caso di successo la funzione aggiorna anche i seguenti campi di +\var{shmid\_ds}: +\begin{itemize*} +\item il tempo \var{shm\_atime} dell'ultima operazione di aggancio viene + impostato al tempo corrente. +\item il \acr{pid} \var{shm\_lpid} dell'ultimo processo che ha operato sul + segmento viene impostato a quello del processo corrente. +\item il numero \var{shm\_nattch} di processi agganciati al segmento viene + aumentato di uno. +\end{itemize*} + +Come accennato in \secref{sec:proc_fork} un segmento di memoria condivisa +agganciato ad un processo viene ereditato da un figlio attraverso una +\func{fork}, dato che quest'ultimo riceve una copia dello spazio degli +indirizzi del padre. Invece, dato che attraverso una \func{exec} viene +eseguito un diverso programma con uno spazio di indirizzi completamente +diverso, tutti i segmenti agganciati al processo originario vengono +automaticamente sganciati. Lo stesso avviene all'uscita del processo +attraverso una \func{exit}. + + +Una volta che un segmento di memoria condivisa non serve più, si può +sganciarlo esplicitamente dal processo usando l'altra funzione +dell'interfaccia, \func{shmdt}, il cui prototipo è: +\begin{functions} + \headdecl{sys/types.h} + \headdecl{sys/shm.h} + + \funcdecl{int shmdt(const void *shmaddr)} + Sgancia dal processo un segmento di memoria condivisa. + + \bodydesc{La funzione restituisce 0 in caso di successo, e -1 in caso di + errore, la funzione fallisce solo quando non c'è un segmento agganciato + all'indirizzo \func{shmaddr}, con \var{errno} che assume il valore + \macro{EINVAL}.} +\end{functions} + +La funzione sgancia dallo spazio degli indirizzi del processo un segmento di +memoria condivisa; questo viene identificato con l'indirizzo \param{shmaddr} +restituito dalla precedente chiamata a \func{shmat} con il quale era stato +agganciato al processo. + +Per capire meglio il funzionamento delle funzioni facciamo ancora una volta +riferimento alle strutture con cui il kernel implementa i segmenti di memoria +condivisa; uno schema semplificato della struttura è illustrato in +\figref{fig:ipc_shm_struct}. + +\begin{figure}[htb] + \centering + \includegraphics[width=10cm]{img/shmstruct} + \caption{Schema dell'implementazione dei segmenti di memoria condivisa in + Linux.} + \label{fig:ipc_shm_struct} +\end{figure} + + + + +\section{Tecniche alternative} +\label{sec:ipc_alternatives} + +Come abbiamo visto in \secref{sec:ipc_sysv_generic} il \textit{SysV IPC} +presenta numerosi problemi; in \cite{APUE}\footnote{in particolare nel + capitolo 14.} Stevens effettua una accurata analisi (alcuni dei concetti +sono già stati accennati in precedenza) ed elenca alcune possibili +alternative, che vogliamo riprendere in questa sezione. + + +\subsection{Alternative alle code di messaggi} +\label{sec:ipc_mq_alternative} +Le code di messaggi sono probabilmente il meno usato degli oggetti del +\textit{SysV IPC}; esse infatti nacquero principalmente come meccanismo di +comunicazione bidirezionale quando ancora le pipe erano unidirezionali; con la +disponibilità di \func{socketpair} (vedi \secref{sec:ipc_socketpair}) si può +ottenere lo stesso risultato senza incorrere nelle complicazioni introdotte +dal \textit{SysV IPC}. + +In realtà, grazie alla presenza del campo \var{mtype}, le code di messaggi +hanno delle caratteristiche ulteriori, consentendo una classificazione dei +messaggi ed un accesso non rigidamente sequenziale, due caratteristiche che +sono impossibili da ottenere con le pipe e i socket di \func{socketpair}; +a queste esigenze però si può comunque ovviare in maniera diversa con un uso +combinato della memoria condivisa e dei meccanismi di sincronizzazione, per +cui alla fine l'uso delle code di messaggi classiche è poco diffuso. + + +\subsection{La sincronizzazione con il \textit{file locking}} +\label{sec:ipc_file_lock} + +Come illustrato in \secref{sec:ipc_sysv_sem} i semafori del \textit{SysV IPC} +presentano una interfaccia inutilmente complessa e con alcuni difetti +strutturali, per questo quando si ha una semplice esigenza di sincronizzazione +per la quale basterebbe un semaforo binario (quello che abbiamo definito come +\textit{mutex}, che indica la disponibilità o meno di una risorsa, e non ha +associato un contatore come i semafori) si possono utilizzare metodi +alternativi. + +La prima possibilità, utilizzata fin dalle origini di Unix, è quella di usare +dei \textsl{file di lock} (per i quali esiste anche una opportuna directory, +\file{/var/lock}, nel filesystem standard). Per questo si usa la +caratteristica della funzione \func{open} (illustrata in +\secref{sec:file_open}) che prevede\footnote{questo è quanto dettato dallo + standard POSIX.1, ciò non toglie che in alcune implementazioni questa + tecnica possa non funzionare; in particolare per Linux, nel caso di NFS, si + è comunque soggetti alla possibilità di una race condition.} che essa +ritorni un errore quando usata con i flag di \macro{O\_CREAT} e +\macro{O\_EXCL}. In tal modo la creazione di un file di lock può essere +eseguita atomicamente, il processo che crea il file con successo si può +considerare come titolare del lock (e della risorsa ad esso associata) mentre +il rilascio si può eseguire con una chiamata ad +\func{unlink}.\footnote{abbiamo già accennato in \secref{sec:file_open} che + questa tecnica può non funzionare se il filesystem su cui si va ad operare è + su NFS; in tal caso si può adottare una tecnica alternativa che prevede + l'uso di \func{link} per creare come file di lock un hard link ad un file + esistente; se il link esiste già e la funzione fallisce, la risorsa + significa che la risorsa è bloccata e potrà essere sbloccata solo con un + \func{unlink}, altrimenti il link è creato ed il lock acquisito; il + controllo e l'eventuale acquisizione sono atomici; il difetto di questa + soluzione è che funziona solo se si opera all'interno di uno stesso + filesystem.} + +L'uso di un file di lock presenta però parecchi problemi, che non lo rendono +una alternativa praticabile per la sincronizzazione:\footnote{ma può essere + una tecnica usata con successo quando l'esigenza è solo quella di segnalare + l'occupazione di una risorsa, senza necessità di attendere che questa si + liberi; ad esempio la si usa spesso per evitare interferenze sull'uso delle + porte seriali da parte di più programmi: qualora trovi un file di lock il + programma che cerca di accedere alla seriale si limita a segnalare che la + risorsa non è disponibile.} anzitutto anche in questo caso in caso di +terminazione imprevista del processo lascia allocata la risorsa (il file di +lock) e questa deve essere sempre cancellata esplicitamente. Inoltre il +controllo della disponibilità può essere fatto solo con una tecnica di +polling\index{polling}, che è molto inefficiente. + +Per questo motivo la tecnica alternativa più pulita è quella di fare ricorso +al \textit{file locking} visto in \secref{sec:file_locking} ed utilizzare +\func{fcntl} su un file creato per l'occasione per ottenere un write lock; in +questo modo potremo usare il lock come un \textit{mutex}: per bloccare la +risorsa basterà acquisire il lock, per sbloccarla basterà rilasciare il lock; +una richiesta fatta con un write lock metterà automaticamente il processo in +stato di attesa, senza necessità di ricorrere al +\textit{polling}\index{polling} per determinare la disponibilità della +risorsa, e al rilascio della stessa da parte del processo che la occupava si +otterrà il nuovo lock atomicamente. + +Questo approccio presenta il notevole vantaggio che alla terminazione di un +processo tutti i lock acquisiti vengono rilasciati automaticamente (alla +chiusura dei relativi file) e non ci si deve preoccupare di niente, e non +consuma risorse permanentemente allocate nel sistema, lo svantaggio è che +dovendo fare ricorso a delle operazioni sul filesystem esso è in genere +leggermente più lento. + + + +\subsection{Il \textit{memory mapping} anonimo} +\label{sec:ipc_mmap_anonymous} + +Abbiamo visto in \secref{sec:file_memory_map} come sia possibile + + +\section{La comunicazione fra processi di POSIX} +\label{sec:ipc_posix} + +Per superare i numerosi problemi del \textit{SysV IPC}, evidenziati per i suoi +aspetti generali in coda a \secref{sec:ipc_sysv_generic} e per i singoli +oggetti nei paragrafi successivi, lo standard POSIX.1b ha introdotto dei nuovi +meccanismi di comunicazione, che vanno sotto il nome di POSIX IPC, definendo +una interfaccia completamente nuova, che tratteremo in questa sezione. + + + +\subsection{Considerazioni generali} +\label{sec:ipc_posix_generic} + +Il Linux non tutti gli oggetti del POSIX IPC sono supportati nel kernel +ufficiale; solo la memoria condivisa è presente, ma solo a partire dal kernel +2.4.x, per gli altri oggetti esistono patch e librerie non +ufficiali. Nonostante questo è importante esaminare questa interfaccia per la +sua netta superiorità nei confronti di quella del \textit{SysV IPC}. + \subsection{Code di messaggi} -\label{sec:ipc_messque} +\label{sec:ipc_posix_mq} + +Le code di messaggi non sono supportate a livello del kernel, esse però +possono essere implementate, usando la memoria condivisa ed i mutex, con +funzioni di libreria. In generale esse sono comunque poco usate, i socket, nei +casi in cui sono sufficienti, sono più comodi, e negli altri casi la +comunicazione può essere gestita direttamente con la stessa metodologia usata +per implementare le code di messaggi. Per questo ci limiteremo ad una +descrizione essenziale. + -Il primo oggetto introdotto dal \textit{SystemV IPC} è quello delle code di -messaggi. \subsection{Semafori} -\label{sec:ipc_semaph} +\label{sec:ipc_posix_sem} -Il secondo oggetto introdotto dal \textit{SystemV IPC} è quello dei semafori. +Dei semafori POSIX esistono sostanzialmente due implementazioni; una è fatta a +livello di libreria ed è fornita dalla libreria dei thread; questa però li +implementa solo a livello di thread e non di processi. Esiste una \subsection{Memoria condivisa} -\label{sec:ipc_shar_mem} +\label{sec:ipc_posix_shm} + +La memoria condivisa è l'unico degli oggetti di IPC POSIX già presente nel +kernel ufficiale. -Il terzo oggetto introdotto dal \textit{SystemV IPC} è quello della memoria -condivisa. %%% Local Variables: %%% mode: latex