X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=ipc.tex;h=ed2d49267e852aa29b90c8141c9bca17c9a69791;hp=bdbdf2c13d9068e85617b49f2ff16cc321cf416e;hb=47a55b7d6c9a34b283e91e6294ca5b7924ac5e7b;hpb=6c705d16151fa82874be6aced255d346c9d2bd7b diff --git a/ipc.tex b/ipc.tex index bdbdf2c..ed2d492 100644 --- a/ipc.tex +++ b/ipc.tex @@ -6,26 +6,24 @@ Uno degli aspetti fondamentali della programmazione in un sistema unix-like la comunicazione fra processi. In questo capitolo affronteremo solo i meccanismi più elementari che permettono di mettere in comunicazione processi diversi, come quelli tradizionali che coinvolgono \textit{pipe} e -\textit{fifo} e i meccanismi di intercomunicazione di System V. +\textit{fifo} e i meccanismi di intercomunicazione di System V e quelli POSIX. Tralasceremo invece tutte le problematiche relative alla comunicazione attraverso la rete (e le relative interfacce) che saranno affrontate in -dettaglio in un secondo tempo. Non affronteremo invece meccanismi più +dettaglio in un secondo tempo. Non affronteremo neanche meccanismi più complessi ed evoluti come le RPC (\textit{Remote Procedure Calls}) e CORBA (\textit{Common Object Request Brocker Architecture}) che in genere sono implementati con un ulteriore livello sopra i meccanismi elementari. - \section{La comunicazione fra processi tradizionale} \label{sec:ipc_unix} -Il primo meccanismo di comunicazione fra processi usato dai sistemi unix-like, -e quello che viene correntemente usato di più, è quello delle \textit{pipe}, -che sono una delle caratteristiche peculiari del sistema, in particolar modo -dell'interfaccia a linea di comando. In questa sezione descriveremo le sue -basi, le funzioni che ne gestiscono l'uso e le varie forme in cui si è -evoluto. +Il primo meccanismo di comunicazione fra processi introdotto nei sistemi Unix, +è quello delle cosiddette \textit{pipe}; esse costituiscono una delle +caratteristiche peculiari del sistema, in particolar modo dell'interfaccia a +linea di comando. In questa sezione descriveremo le sue basi, le funzioni che +ne gestiscono l'uso e le varie forme in cui si è evoluto. \subsection{Le \textit{pipe} standard} @@ -33,39 +31,36 @@ evoluto. Le \textit{pipe} nascono sostanzialmente con Unix, e sono il primo, e tuttora uno dei più usati, meccanismi di comunicazione fra processi. Si tratta in -sostanza di uno speciale tipo di file descriptor, più precisamente una coppia -di file descriptor,\footnote{si tenga presente che le pipe sono oggetti creati - dal kernel e non risiedono su disco.} su cui da una parte si scrive e da -un'altra si legge. Si viene così a costituire un canale di comunicazione -tramite i due file descriptor, nella forma di un \textsl{tubo} (da cui il -nome) in cui in genere un processo immette dati che poi arriveranno ad un -altro. - -La funzione che permette di creare una pipe è appunto \func{pipe}; il suo -prototipo è: +sostanza di una una coppia di file descriptor\footnote{si tenga presente che + le pipe sono oggetti creati dal kernel e non risiedono su disco.} connessi +fra di loro in modo che se quanto scrive su di uno si può rileggere +dall'altro. Si viene così a costituire un canale di comunicazione tramite i +due file descriptor, nella forma di un \textsl{tubo} (da cui il nome) +attraverso cui fluiscono i dati. + +La funzione che permette di creare questa speciale coppia di file descriptor +associati ad una \textit{pipe} è appunto \func{pipe}, ed il suo prototipo è: \begin{prototype}{unistd.h} {int pipe(int filedes[2])} -Crea una coppia di file descriptor associati ad una pipe. +Crea una coppia di file descriptor associati ad una \textit{pipe}. \bodydesc{La funzione restituisce zero in caso di successo e -1 per un errore, nel qual caso \var{errno} potrà assumere i valori \macro{EMFILE}, \macro{ENFILE} e \macro{EFAULT}.} \end{prototype} -La funzione restituisce una coppia di file descriptor nell'array -\param{filedes}; il primo aperto in lettura ed il secondo in scrittura. Il -concetto di funzionamento di una pipe è relativamente semplice, quello che si +La funzione restituisce la coppia di file descriptor nell'array +\param{filedes}; il primo è aperto in lettura ed il secondo in scrittura. Come +accennato concetto di funzionamento di una pipe è semplice: quello che si scrive nel file descriptor aperto in scrittura viene ripresentato tale e quale -nel file descriptor aperto in lettura, da cui può essere riletto. - -I file descriptor infatti non sono connessi a nessun file reale, ma ad un -buffer nel kernel, la cui dimensione è specificata dalla costante -\macro{PIPE\_BUF}, (vedi \secref{sec:sys_file_limits}); lo schema di -funzionamento di una pipe è illustrato in \figref{fig:ipc_pipe_singular}, in -cui sono illustrati i due capi della pipe, associati a ciascun file -descriptor, con le frecce che indicano la direzione del flusso dei dati -attraverso la pipe. +nel file descriptor aperto in lettura. I file descriptor infatti non sono +connessi a nessun file reale, ma ad un buffer nel kernel, la cui dimensione è +specificata dalla costante \macro{PIPE\_BUF}, (vedi +\secref{sec:sys_file_limits}). Lo schema di funzionamento di una pipe è +illustrato in \figref{fig:ipc_pipe_singular}, in cui sono illustrati i due +capi della pipe, associati a ciascun file descriptor, con le frecce che +indicano la direzione del flusso dei dati. \begin{figure}[htb] \centering @@ -74,12 +69,12 @@ attraverso la pipe. \label{fig:ipc_pipe_singular} \end{figure} -Chiaramente creare una pipe all'interno di un processo non serve a niente; se -però ricordiamo quanto esposto in \secref{sec:file_sharing} riguardo al -comportamento dei file descriptor nei processi figli, è immediato capire come -una pipe possa diventare un meccanismo di intercomunicazione. Un processo -figlio infatti condivide gli stessi file descriptor del padre, compresi quelli -associati ad una pipe (secondo la situazione illustrata in +Chiaramente creare una pipe all'interno di un singolo processo non serve a +niente; se però ricordiamo quanto esposto in \secref{sec:file_sharing} +riguardo al comportamento dei file descriptor nei processi figli, è immediato +capire come una pipe possa diventare un meccanismo di intercomunicazione. Un +processo figlio infatti condivide gli stessi file descriptor del padre, +compresi quelli associati ad una pipe (secondo la situazione illustrata in \figref{fig:ipc_pipe_fork}). In questo modo se uno dei processi scrive su un capo della pipe, l'altro può leggere. @@ -92,16 +87,17 @@ capo della pipe, l'altro pu \end{figure} Tutto ciò ci mostra come sia immediato realizzare un meccanismo di -comunicazione fra processi attraverso una pipe, utilizzando le ordinarie -proprietà dei file, ma ci mostra anche qual'è il principale\footnote{Stevens +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, in realtà questo è un limite facilmente superabile usando + unidirezionale, ma in realtà questo è un limite facilmente superabile usando una coppia di pipe.} limite nell'uso delle pipe. È necessario infatti che i processi possano condividere i file descriptor della pipe, e per questo essi -devono comunque derivare da uno stesso processo padre che ha aperto la pipe, -o, più comunemente, essere nella relazione padre/figlio. +devono comunque essere \textsl{parenti} (dall'inglese \textit{siblings}), cioè +o derivare da uno stesso processo padre in cui è avvenuta la creazione della +pipe, o, più comunemente, essere nella relazione padre/figlio. -A differenza di quanto avviene con i file normali la lettura da una pipe può +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 @@ -122,14 +118,14 @@ da altri processi. \subsection{Un esempio dell'uso delle pipe} \label{sec:ipc_pipe_use} -Per capire meglio il funzionamento di una pipe faremo un esempio di quello che +Per capire meglio il funzionamento delle pipe faremo un esempio di quello che è il loro uso più comune, analogo a quello effettuato della shell, e che consiste nell'inviare l'output di un processo (lo standard output) sull'input -di un'altro. Realizzaremo il programma nella forma di un -\textit{CGI}\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. +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 @@ -143,13 +139,13 @@ 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 fare questo useremo in sequenza i programmi \cmd{barcode} e \cmd{gs}, il -primo infatti è in grado di generare immagini postscript di codici a barre -corrispondenti ad una qualunque stringa, mentre il secondo serve per poter -effettuare la conversione della stessa immagine in formato JPEG. Usando una -pipe potremo inviare l'output del primo sull'input del secondo, secondo lo -schema mostrato in \figref{fig:ipc_pipe_use}, in cui la direzione del flusso -dei dati è data dalle frecce continue. +Per 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 @@ -168,7 +164,8 @@ 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. +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 @@ -187,7 +184,7 @@ nel file \file{BarCodePage.c} che si trova nella directory dei sorgenti. int main(int argc, char *argv[], char *envp[]) { ... - /* create two pipes to handle process communication */ + /* create two pipes, pipein and pipeout, to handle communication */ if ( (retval = pipe(pipein)) ) { WriteMess("input pipe creation error"); exit(0); @@ -207,14 +204,14 @@ int main(int argc, char *argv[], char *envp[]) dup2(pipein[0], STDIN_FILENO); /* remap stdin to pipe read end */ close(pipeout[0]); dup2(pipeout[1], STDOUT_FILENO); /* remap stdout in pipe output */ - execlp("barcode", "barcode", size, NULL); //"-o", "-", NULL); + execlp("barcode", "barcode", size, NULL); } close(pipein[0]); /* close input side of input pipe */ write(pipein[1], argv[1], strlen(argv[1])); /* write parameter to pipe */ close(pipein[1]); /* closing write end */ waitpid(pid, NULL, 0); /* wait child completion */ /* Second fork: use child to run ghostscript */ - if ( (pid = fork()) == -1) { /* on error exit */ + if ( (pid = fork()) == -1) { WriteMess("child creation error"); exit(0); } @@ -243,7 +240,7 @@ La prima operazione del programma (\texttt{\small 4--12}) 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 +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 @@ -374,26 +371,27 @@ approccio diverso. Una possibilit programma, \cmd{epstopsf}, per convertire in PDF un file EPS (che può essere generato da \cmd{barcode} utilizzando lo switch \cmd{-E}). Utilizzando un PDF al posto di un EPS \cmd{gs} esegue la conversione rispettando le dimensioni -originarie del codice a barre e produce un JPEG delle dimensioni adeguate. - -Questo però ci porta a scontrarci con una caratteristica peculiare delle pipe, -che a prima vista non è evidente. Per poter effettuare la conversione di un -PDF infatti è necessario, per la struttura del formato, dover eseguire delle -\func{lseek} sul file da convertire; se si esegue \cmd{gs} su un file normale -non ci sono problemi, ma una pipe però è rigidamente sequenziale, ed il -tentativo di eseguire detta operazione su una pipe comporta l'immediato -fallimento con un errore di \macro{ESPIPE}. Questo ci dice che in generale la -concatenazione di vari programmi funzionerà soltanto quando tutti prevedono -una lettura sequenziale del loro input. - -Per questo motivo si è dovuto utilizzare una strada diversa, che prevede la -conversione attraverso \cmd{gs} del PS in un altro formato intermedio, il -PPM,\footnote{il \textit{Portable PixMap file format} è un formato usato - spesso come formato intermedio per effettuare conversioni, è estremamente - inefficiente, ma molto facile da manipolare dato che usa caratteri ASCII per - memorizzare le immagini.} dal quale poi si può ottenere un'immagine di -dimensioni corrette attraverso vari programmi di manipolazione (\cmd{pnmcrop}, -\cmd{pnmmargin}) che può essere infine trasformata in PNG (con \cmd{pnm2png}). +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 eseguela 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 @@ -472,7 +470,7 @@ Alla fine tutto quello che resta da fare 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 +ciclo che chiuda, nell'ordine inverso rispetto a quello in cui le si sono create, tutte le pipe create con \func{pclose}. @@ -512,52 +510,563 @@ nel qual caso l'apertura del capo in lettura avr l'altro capo è aperto, mentre l'apertura del capo in scrittura restituirà l'errore di \macro{ENXIO} fintanto che non verrà aperto il capo in lettura. -In Linux\footnote{lo standard POSIX lascia indefinito questo comportamento.} è -possibile aprire le fifo anche in lettura/scrittura, operazione che avrà -sempre successo immediato qualunque sia la modalità di apertura (bloccante e -non bloccante); questo può essere utilizzato per aprire comunque una fifo in -scrittura anche se non ci sono ancora processi il lettura; è possibile anche -usare la fifo all'interno di un solo processo, nel qual caso però occorre -stare molto attenti alla possibili deadlock.\footnote{se si cerca di leggere - da una fifo che non contiene dati si avrà un deadlock immediato, dato che il - processo si blocca e non potrà quindi mai eseguire le funzioni di - scrittura.} +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 +deadlock.\footnote{se si cerca di leggere da una fifo che non contiene dati si + avrà un deadlock immediato, dato che il processo si blocca e non potrà + quindi mai eseguire le funzioni di scrittura.} + +Per la loro caratteristica di essere accessibili attraverso il filesystem, è +piuttosto frequente l'utilizzo di una fifo come canale di comunicazione nelle +situazioni un processo deve ricevere informazioni 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 da Stevens in \cite{APUE}, in cui le risposte vengono inviate su +fifo temporanee identificate dal \acr{pid} dei client, ma in ogni caso il +sistema è macchinoso e continua ad avere vari inconvenienti\footnote{lo stesso + Stevens nota come sia impossibile per il server sapere se un client è andato + in crash, con la possibilità di far restare le fifo temporanee sul + filesystem, come sia necessario intercettare \macro{SIGPIPE} dato che un + client può terminare dopo aver fatto una richiesta, ma prima che la risposta + sia inviata, e come occorra gestire il caso in cui non ci sono client attivi + (e la lettura dalla fifo nota restituisca al serve un end-of-file.}; in +generale infatti l'interfaccia delle fifo non è adatta a risolvere questo tipo +di problemi, che possono essere affrontati in maniera più semplice ed efficace +o usando i \textit{socket}\index{socket} (che tratteremo in dettaglio a +partire da \capref{cha:socket_intro}) o ricorrendo a meccanismi di +comunicazione diversi, come quelli che esamineremo in seguito. + - \section{La comunicazione fra processi di System V} \label{sec:ipc_sysv} -Benché le pipe (e le fifo) siano ancora ampiamente usate, esse presentano -numerosi limiti, il principale dei quali è che il meccanismo di comunicazione -è rigidamente sequenziale; una situazione in cui un processo scrive qualcosa -che molti altri devono poter leggere non può essere implementata con una pipe. +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 superarne i vari limiti, nello sviluppo di System V vennero introdotti una -serie di nuovi oggetti di comunicazione e relative interfacce id -programmazione che garantissero una maggiore flessibilità; in questa sezione -esamineremo quello che viene ormai chiamato il \textsl{Sistema di - comunicazione inter-processo} di System V , più comunemente noto come +Per questo nello sviluppo di System V vennero introdotti una serie di nuovi +oggetti per la comunicazione fra processi ed una nuova interfaccia di +programmazione, che fossero in grado di garantire una maggiore flessibilità. +In questa sezione esamineremo come Linux supporta quello che viene ormai +chiamato il \textsl{Sistema di comunicazione inter-processo} di System V, o \textit{System V IPC (Inter-Process Comunication)}. - + + + +\subsection{Considerazioni generali} +\label{sec:ipc_sysv_generic} + +La principale caratteristica del sistema di IPC di System V è quella di essere +basato su oggetti permanenti che risiedono nel kernel. Questi, a differenza di +quanto avviene per i file descriptor, non mantengono un contatore dei +riferimenti, e non vengono cancellati dal sistema una volta che non sono più +in uso. + +Questo comporta due problemi: il primo è che, al contrario di quanto avviene +per pipe e fifo, la memoria allocata per questi oggetti non viene rilasciata +automaticamente quando nessuno li vuole più utilizzare, ed essi devono essere +cancellati esplicitamente, se non si vuole che restino attivi fino al riavvio +del sistema. Il secondo è che, dato che non c'è un contatore di riferimenti, +essi possono essere cancellati anche se ci sono dei processi che li stanno +utilizzando, con tutte le conseguenze (negative) del caso. + +Gli oggetti usati nel System V IPC vengono creati direttamente dal kernel, e +sono accessibili solo specificando il relativo \textsl{identificatore}. Questo +è un numero progressivo (un po' come il \acr{pid} dei processi) che il kernel +assegna a ciascuno di essi quanto vengono creati (sul prodedimento di +assegnazione torneremo in \secref{sec:ipc_sysv_id_use}). L'identificatore +viene restituito dalle funzioni che creano l'oggetto, ed è quindi locale al +processo che le ha eseguite. Dato che l'identificatore viene assegnato +dinamicamente dal kernel non è possibile prevedere quale sarà, ne utilizzare +un qualche valore statico, si pone perciò il problema di come processi diversi +possono accedere allo stesso oggetto. + +Per risolvere il problema il kernel associa a ciascun oggetto una struttura +\var{ipc\_perm}; questa contiene una \textsl{chiave}, identificata da una +variabile del tipo primitivo \type{key\_t}, che viene specificata in fase di +creazione e tramite la quale è possibile ricavare l'identificatore. La +struttura, la cui definizione è riportata in \figref{fig:ipc_ipc_perm}, +contiene anche le varie proprietà associate all'oggetto. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \begin{lstlisting}[labelstep=0]{}%,frame=,indent=1cm ]{} +struct ipc_perm +{ + key_t key; /* Key. */ + uid_t uid; /* Owner's user ID. */ + gid_t gid; /* Owner's group ID. */ + uid_t cuid; /* Creator's user ID. */ + gid_t cgid; /* Creator's group ID. */ + unsigned short int mode; /* Read/write permission. */ + unsigned short int seq; /* Sequence number. */ +}; + \end{lstlisting} + \end{minipage} + \normalsize + \caption{La struttura \var{ipc\_perm}, come definita in \file{sys/ipc.h}.} + \label{fig:ipc_ipc_perm} +\end{figure} + +Usando la stessa chiave due processi diversi possono ricavare l'identificatore +associato ad un oggetto ed accedervi. Il problema che sorge a questo punto è +come devono fare per accordarsi sull'uso di una stessa chiave. Se i processi +sono \textsl{parenti} la soluzione è relativamente semplice, in tal caso +infatti si può usare il valore speciale \texttt{IPC\_PRIVATE} per creare un +nuovo oggetto nel processo padre, l'idenficatore 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 potebbe comunque salvare l'identificatore su un file noto, ma +questo ovviamente comporta lo svantaggio di doverselo andare a rileggere. Una +alternativa più efficace è quella che i programmi usino un valore comune per +la chiave (che ad esempio può essere dichiarato in un header comune), ma c'è +sempre il rischio che questa chiave possa essere stata già utilizzata da +qualcun altro. Dato che non esiste una convenzione su come assegnare queste +chiavi in maniera univoca l'interfaccia mette a disposizione una funzione, +\func{ftok}, che permette di ottenere una chiave specificando il nome di un +file ed un numero di versione; il suo prototipo è: +\begin{functions} + \headdecl{sys/types.h} + \headdecl{sys/ipc.h} + + \funcdecl{key\_t ftok(const char *pathname, int proj\_id)} + + Restituisce una chiave per identificare un oggetto del System V IPC. + + \bodydesc{La funzione restituisce la chiave in caso di successo e -1 + altrimenti, nel qual caso \var{errno} viene settata ad uno dei possibili + codici di errore di \func{stat}.} +\end{functions} + +La funzione determina un valore della chiave sulla base di \param{pathname}, +che deve specificare il pathname di un file effettivamente esistente e di un +numero di progetto \param{proj\_id)}, che di norma viene specificato come +carattere, dato che ne vengono utilizzati solo gli 8 bit meno +significativi.\footnote{nelle libc4 e libc5, come avviene in SunOS, + l'argomento \param{proj\_id)} è dichiarato tipo \ctyp{char}, le \acr{glibc} + han modificato il prototipo, 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 device su cui è il file. Diventa +perciò relativamente facile ottenere delle collisioni, specie se i file sono +su dispositivi con lo stesso \textit{minor number}, come \file{/dev/hda1} e +\file{/dev/sda1}. + +In genere quello che si fa è utilizzare un file comune usato dai programmi che +devono comunicare (ad esempio un haeder, o uno dei programmi che devono usare +l'oggetto in questione), utilizzando il numero di progetto per ottere 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 della validità di una certa chiave. + +Questo è, insieme al fatto che gli oggetti sono permanenti e non mantengono un +contatore di riferimenti per la cancellazione automatica, il principale +problema del sistema di IPC di System V. Non esiste infatti una modalità +chiara per identificare un oggetto, come sarebbe stato se lo si fosse +associato ad in file, e tutta l'interfaccia è inutilmente complessa. Per +questo ne è stata effettuata una revisione completa nello standard POSIX.1b, +che tratteremo in \secref{sec:ipc_posix}. + + +\subsection{Il controllo di accesso} +\label{sec:ipc_sysv_access_control} + +Oltre alle chiavi, abbiamo visto che ad ogni oggetto sono associate in +\var{ipc\_perm} ulteriori informazioni, come gli identificatori del creatore +(nei campi \var{cuid} e \var{cgid}) e del proprietario (nei campi \var{uid} e +\var{gid}) dello stesso, e un insieme di permessi (nel campo \var{mode}). In +questo modo è possibile definire un controllo di accesso sugli oggetti, simile +a quello che si ha per i file (vedi \secref{sec:file_perm_overview}). + +Benché il controllo di accesso relativo agli oggetti di intercomunicazione sia +molto simile a quello dei file, restano delle importanti differenze. La prima +è che il permesso di esecuzione non esiste (e viene ignorato), per cui si può +parlare solo di permessi di lettura e scrittura (nel caso dei semafori poi +quest'ultimo è più propriamente il permesso di modificarne lo stato). 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. + +Si tenga presente che per gli oggetti di IPC han senso solo i permessi di +lettura e scrittura, quelli di esecuzione vengono ignorati. 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, \var{cuid} e \var{cgid} +restano sempre gli stessi. + +Il controllo di accesso è effettuato a due livelli. Il primo è nelle funzioni +che richiedono l'identificatore di un oggetto data la chiave, che specificano +tutte un argomento \param{flag}. In tal caso quando viene effettuata la +ricerca di una chiave, se \param{flag} specifica dei permessi, questi vengono +controllati e l'identificatore viene restituito solo se essi corrispondono a +quelli dell'oggetto. Se sono presenti dei permessi non presenti in \var{mode} +l'accesso sarà invece negato. Questo però è di utilità indicativa, dato che è +sempre possibile specificare un valore nullo per \param{flag}, nel qual caso +il controllo avrà sempre successo. + +Il secondo livello è quello delle varie funzioni che accedono (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 amministatore 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'altra differenza è +che per gli oggetti di IPC il valore di \var{umask} (si ricordi quanto esposto +in \secref{sec:file_umask}) non ha alcun effetto. + + +\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 +``\textit{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 comportamente 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 ed inferiori al numero massimo di oggetti diponibili. + +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 questo numero, ed altri limiti relativi al \textit{System V + IPC}, potevano essere cambiati solo con una ricompilazione del kernel, + andando a modificare le costanti definite nei relativi haeder file. A + partire dal kernel 2.4.x è possibile cambiare questi valori a sistema attivo + scrivendo sui file \file{shmmni}, \file{msgmni} e \file{sem} di + \file{/proc/sys/kernel} o con \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 oggetti di IPC, il cui valore è + 32768.} si evita così il riutilizzo degli stessi numeri, e si fa sì che +l'identificatore assuma tutti i valori possibili. + +In \figref{fig:ipc_sysv_idtest} è riportato il codice di un semplice programma +di test che si limita a creare un oggetto (specificato a riga di comando), +stamparne il numero di identificatore e cancellarlo per un numero specificato +di volte. + +\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