X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=ipc.tex;h=951d4246d667333e9aecd9abfc9ddd6750c937fd;hp=bdf39a98adbc2484ebb977781ece42e459e21a36;hb=72b2686a82a62331891ca894a6c3b476365363fc;hpb=441dd1e480e625b9e8943261b706d0018c5f0faa diff --git a/ipc.tex b/ipc.tex index bdf39a9..951d424 100644 --- a/ipc.tex +++ b/ipc.tex @@ -349,7 +349,7 @@ 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 \capref{cha:files_std_interface}, anche se è collegato ad una -pipe e non ad un inode\index{inode}, e viene sempre aperto in modalità +pipe e non ad un file, 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, \funcd{pclose}, il cui prototipo è: @@ -600,7 +600,7 @@ int main(int argc, char *argv[]) { /* Variables definition */ int i, n = 0; - char *fortunefilename = "/usr/share/games/fortunes/italia"; + char *fortunefilename = "/usr/share/games/fortunes/linux"; char **fortune; char line[80]; int fifo_server, fifo_client; @@ -617,6 +617,7 @@ int main(int argc, char *argv[]) exit(1); } } + daemon(0, 0); /* open fifo two times to avoid EOF */ fifo_server = open(fifoname, O_RDONLY); if (fifo_server < 0) { @@ -676,41 +677,45 @@ qualora si riscontri un errore il server uscir 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). +Una volta che si è certi che la fifo di ascolto esiste la procedura di +inizializzazione è completata. A questo punto si può chiamare (\texttt{\small + 23}) la funzione \func{daemon} per far proseguire l'esecuzione del programma +in background come demone. Si può quindi procedere (\texttt{\small 24--33}) +alla apertura della fifo: si noti che questo viene fatto due volte, prima in +lettura e poi in scrittura, 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 \const{O\_RDWR}, la doppia apertura comunque ha il vantaggio che non si - può scrivere per errore sul capo aperto in sola lettura.} +effettuando richieste) con la fifo chiusa sul lato in lettura, ed in questo +stato la funzione \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 \const{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. + 24--28}),\footnote{di solito si effettua l'apertura del capo in lettura di + una fifo in modalità non bloccante, per evitare il rischio di uno stallo: se + infatti nessuno apre la fifo in scrittura il processo non ritornerà mai + dalla \func{open}. Nel nostro caso questo rischio 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, in questo modo però la +fifo resta comunque aperta anche in scrittura, cosicché le successive chiamate +a \func{read} possono 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 +le risposte ai client (\texttt{\small 34--50}); questo 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). +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. @@ -719,9 +724,9 @@ richiesta dalla fifo nota (che a questo punto si bloccher 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 +all'apertura della fifo per la risposta, che poi \texttt{\small 47--48}) vi sarà scritta. Infine (\texttt{\small 49}) si chiude la fifo di risposta che -non serve più. +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 @@ -802,6 +807,62 @@ la richiesta, se non si fosse fatto cos quanto senza la richiesta, il server non avrebbe potuto aprirne il capo in scrittura e l'apertura si sarebbe bloccata indefinitamente. +Verifichiamo allora il comportamento dei nostri programmi, in questo, come in +altri esempi precedenti, si fa uso delle varie funzioni di servizio, che sono +state raccolte nella libreria \file{libgapil.so}, per poter usare quest'ultima +occorrerà definire la speciale variabile di ambiente \code{LD\_LIBRARY\_PATH} +in modo che il linker dinamico possa accedervi. + +In generale questa variabile indica il pathname della directory contenente la +libreria. Nell'ipotesi (che daremo sempre per verificata) che si facciano le +prove direttamente nella directory dei sorgenti (dove di norma vengono creati +sia i programmi che la libreria), il comando da dare sarà \code{export + LD\_LIBRARY\_PATH=./}; a questo punto potremo lanciare il server, facendogli +leggere una decina di frasi, con: +\begin{verbatim} +[piccardi@gont sources]$ ./fortuned -n10 +\end{verbatim} + +Avendo usato \func{daemon} per eseguire il server in background il comando +ritornerà immediatamente, ma potremo verificare con \cmd{ps} che in effetti il +programma resta un esecuzione in background, e senza avere associato un +terminale di controllo (si ricordi quanto detto in \secref{sec:sess_daemon}): +\begin{verbatim} +[piccardi@gont sources]$ ps aux +... +piccardi 27489 0.0 0.0 1204 356 ? S 01:06 0:00 ./fortuned -n10 +piccardi 27492 3.0 0.1 2492 764 pts/2 R 01:08 0:00 ps aux +\end{verbatim}%$ +e si potrà verificare anche che in \file{/tmp} è stata creata la fifo di +ascolto \file{fortune.fifo}. A questo punto potremo interrogare il server con +il programma client; otterremo così: +\begin{verbatim} +[piccardi@gont sources]$ ./fortune +Linux ext2fs has been stable for a long time, now it's time to break it + -- Linuxkongreß '95 in Berlin +[piccardi@gont sources]$ ./fortune +Let's call it an accidental feature. + --Larry Wall +[piccardi@gont sources]$ ./fortune +......... Escape the 'Gates' of Hell + `:::' ....... ...... + ::: * `::. ::' + ::: .:: .:.::. .:: .:: `::. :' + ::: :: :: :: :: :: :::. + ::: .::. .:: ::. `::::. .:' ::. +...:::.....................::' .::::.. + -- William E. Roadcap +[piccardi@gont sources]$ ./fortune +Linux ext2fs has been stable for a long time, now it's time to break it + -- Linuxkongreß '95 in Berlin +\end{verbatim}%$ +e ripetendo varie volte il comando otterremo, in ordine casuale, le dieci +frasi tenute in memoria dal server. + +Infine per chiudere il server basterà inviare un segnale di terminazione con +\code{killall fortuned} e potremo verificare che il gestore del segnale ha +anche correttamente cancellato la fifo di ascolto da \file{/tmp}. + 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 @@ -1702,7 +1763,7 @@ int main(int argc, char *argv[]) /* Variables definition */ int i, n = 0; char **fortune; /* array of fortune message string */ - char *fortunefilename; /* fortune file name */ + char *fortunefilename = "/usr/share/games/fortunes/linux"; /* 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 */ @@ -1727,6 +1788,7 @@ int main(int argc, char *argv[]) exit(1); } /* Main body: loop over requests */ + daemon(0, 0); while (1) { msgrcv(msgid, &msg_read, sizeof(int), 1, MSG_NOERROR); n = random() % i; /* select random value */ @@ -1774,7 +1836,7 @@ in \var{n} il numero di frasi da leggere specificato a linea di comando ed in 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. +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 @@ -1784,33 +1846,35 @@ 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 \const{MSG\_NOERROR} -è solo per sicurezza, dato che i messaggi di richiesta sono di dimensione -fissa (e contengono solo il \acr{pid} del client). +Finita la fase di inizializzazione il server prima (\texttt{\small 32}) chiama +la funzione \func{daemon} per andare in background e poi esegue in permanenza +il ciclo principale (\texttt{\small 33--40}). Questo inizia (\texttt{\small + 34}) 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 \const{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. +(\texttt{\small 35}) selezionando una frase a caso, copiandola (\texttt{\small + 36}) nella struttura \var{msgbuf\_write} usata per la risposta e +calcolandone (\texttt{\small 37}) 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}) +a lui il tipo del messaggio in uscita viene inizializzato (\texttt{\small 38}) al valore del \acr{pid} del client ricevuto nel messaggio di richiesta. -L'ultimo passo del ciclo (\texttt{\small 38}) è inviare sulla coda il +L'ultimo passo del ciclo (\texttt{\small 39}) è 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 gestore -\code{HandSIGTERM}, che semplicemente si limita a cancellare la coda -(\texttt{\small 44}) ed ad uscire (\texttt{\small 45}). +parte di un segnale; in tal caso verrà eseguito (\texttt{\small 45--48}) il +gestore \code{HandSIGTERM}, che semplicemente si limita a cancellare la coda +(\texttt{\small 46}) ed ad uscire (\texttt{\small 47}). \begin{figure}[!bht] \footnotesize \centering @@ -1868,6 +1932,36 @@ 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. +Proviamo allora il nostro nuovo sistema, al solito occorre definire +\code{LD\_LIBRAY\_PATH}, dopo di che, in maniera del tutto analoga a quanto +fatto con il programma che usa le fifo, potremo far partire il server con: +\begin{verbatim} +[piccardi@gont sources]$ ./mqfortuned -n10 +\end{verbatim}%$ +come prima, essendo eseguito in background, il comando ritornerà +immediatamente, ma potremo verificare con \cmd{ps} che il programma è +effettivamente in esecuzione, e che ha creato una coda di messaggi: +\begin{verbatim} +[piccardi@gont sources]$ ipcs + +------ Shared Memory Segments -------- +key shmid owner perms bytes nattch status + +------ Semaphore Arrays -------- +key semid owner perms nsems + +------ Message Queues -------- +key msqid owner perms used-bytes messages +0x0102dc6a 0 piccardi 666 0 0 +\end{verbatim} + + + + + + + + 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 @@ -2471,9 +2565,7 @@ nullo per segnalarne l'indisponibilit \footnotesize \centering \begin{minipage}[c]{15cm} \begin{lstlisting}{} -/* - * Function MutexCreate: create a mutex/semaphore - */ +/* Function MutexCreate: create a mutex/semaphore */ int MutexCreate(key_t ipc_key) { const union semun semunion={1}; /* semaphore union structure */ @@ -2488,23 +2580,17 @@ int MutexCreate(key_t ipc_key) } return sem_id; } -/* - * Function MutexFind: get the semaphore/mutex Id given the IPC key value - */ +/* Function MutexFind: get the semaphore/mutex Id given the IPC key value */ int MutexFind(key_t ipc_key) { return semget(ipc_key,1,0); } -/* - * Function MutexRead: read the current value of the mutex/semaphore - */ +/* Function MutexRead: read the current value of the mutex/semaphore */ int MutexRead(int sem_id) { return semctl(sem_id, 0, GETVAL); } -/* - * Define sembuf structures to lock and unlock the semaphore - */ +/* 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) */ @@ -2513,19 +2599,20 @@ struct sembuf sem_ulock={ /* to unlock semaphore */ 0, /* semaphore number (only one so 0) */ 1, /* operation (1 to release resource) */ SEM_UNDO}; /* flag (in this case 0) */ -/* - * Function MutexLock: to lock a mutex/semaphore - */ +/* Function MutexLock: to lock a mutex/semaphore */ int MutexLock(int sem_id) { return semop(sem_id, &sem_lock, 1); } -/* - * Function MutexUnlock: to unlock a mutex/semaphore - */ +/* Function MutexUnlock: to unlock a mutex/semaphore */ int MutexUnlock(int sem_id) { return semop(sem_id, &sem_ulock, 1); +} +/* Function MutexRemove: remove a mutex/semaphore */ +int MutexRemove(int sem_id) +{ + return semctl(sem_id, 0, IPC_RMID); } \end{lstlisting} \end{minipage} @@ -2535,58 +2622,65 @@ int MutexUnlock(int sem_id) \label{fig:ipc_mutex_create} \end{figure} -La prima funzione (\texttt{\small 1--17}) è \func{MutexCreate} che data una +La prima funzione (\texttt{\small 2--15}) è \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} +l'identificatore. Il primo passo (\texttt{\small 6}) è chiamare \func{semget} con \const{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 +(\texttt{\small 7--9}) si ritorna subito il risultato di \func{semget}, +altrimenti (\texttt{\small 10}) si inizializza il semaforo chiamando \func{semctl} con il comando \const{SETVAL}, utilizzando l'unione -\struct{semunion} dichiarata ed avvalorata in precedenza (\texttt{\small 6}) +\struct{semunion} dichiarata ed avvalorata in precedenza (\texttt{\small 4}) 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. + 11--13}) si restituisce il valore di ritorno di \func{semctl}, altrimenti +(\texttt{\small 14}) si ritorna l'identificatore del semaforo. -La seconda funzione (\texttt{\small 18--24}) è \func{MutexFind}, che data una +La seconda funzione (\texttt{\small 17--20}) è \func{MutexFind}, che, data una chiave, restituisce l'identificatore del semaforo ad essa associato. La -comprensione del suo funzionamento è immediata in quanto è solo un +comprensione del suo funzionamento è immediata in quanto essa è soltanto 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. + stesse funzionalità.} di una chiamata a \func{semget} per cercare +l'identificatore associato alla chiave, il valore di ritorno di quest'ultima +viene passato all'indietro al chiamante. -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 \const{GETVAL}, che permette di restituire il valore del -semaforo. +La terza funzione (\texttt{\small 22--25}) è \func{MutexRead} che, dato un +identificatore, restituisce il valore del semaforo associato al mutex. Anche +in questo caso la funzione è un \textit{wrapper} per una chiamata a +\func{semctl} con il comando \const{GETVAL}, che permette di restituire il +valore del semaforo. -La quarta e la quinta funzione (\texttt{\small 43--56}) sono \func{MutexLock}, +La quarta e la quinta funzione (\texttt{\small 36--44}) 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 +(\texttt{\small 27--34}). Si noti come per queste ultime si sia fatto uso dell'opzione \const{SEM\_UNDO} per evitare che il semaforo resti bloccato in caso di terminazione imprevista del processo. +L'ultima funzione (\texttt{\small 46--49}) della serie, è \func{MutexRemove}, +che rimuove il mutex. Anche in questo caso si ha un wrapper per una chiamata a +\func{semctl} con il comando \const{IPC\_RMID}, che permette di cancellare il +smemaforo; il valore di ritorno di quest'ultima viene passato all'indietro. + 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\index{file!locking}. +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). Infine si tenga presente che usare \func{MutexRead} per +controllare il valore dei mutex prima di proseguire in una operazione di +sblocco non servirebbe comunque, dato che l'operazione non sarebbe atomica. +Vedremo in \secref{sec:ipc_lock_file} come sia possibile ottenere +un'interfaccia analoga a quella appena illustrata, senza incorrere in questi +problemi, usando il file locking\index{file!locking}. \subsection{Memoria condivisa} @@ -2968,9 +3062,6 @@ video; al solito il codice completo si trova con i sorgenti allegati nel file \footnotesize \centering \begin{minipage}[c]{15cm} \begin{lstlisting}{} -/* computation function for DirScan */ -int ComputeValues(struct dirent * direntry); -void HandSIGTERM(int signo); /* global variables for shared memory segment */ struct DirProp { int tot_size; @@ -2982,8 +3073,7 @@ struct DirProp { int tot_block; int tot_char; int tot_sock; -}; -struct DirProp *shmptr; +} *shmptr; int shmid; int mutex; /* main body */ @@ -2996,11 +3086,13 @@ int main(int argc, char *argv[]) printf("Wrong number of arguments %d\n", argc - optind); usage(); } + if (chdir(argv[1])) { /* chdir to be sure dir exist */ + perror("Cannot find directory to monitor"); + } Signal(SIGTERM, HandSIGTERM); /* set handlers for termination */ Signal(SIGINT, HandSIGTERM); Signal(SIGQUIT, HandSIGTERM); - /* create needed IPC objects */ - key = ftok("./DirMonitor.c", 1); /* define a key */ + key = ftok("~/gapil/sources/DirMonitor.c", 1); /* define a key */ shmid = shmget(key, 4096, IPC_CREAT|0666); /* get a shared memory */ if (shmid < 0) { perror("Cannot create shared memory"); @@ -3008,12 +3100,14 @@ int main(int argc, char *argv[]) } if ( (shmptr = shmat(shmid, NULL, 0)) == NULL ) { /* attach to process */ perror("Cannot attach segment"); + exit(1); } if ((mutex = MutexCreate(key)) == -1) { /* get a Mutex */ perror("Cannot create mutex"); exit(1); } /* main loop, monitor directory properties each 10 sec */ + daemon(1, 0); /* demonize process, staying in monitored dir */ while (1) { MutexLock(mutex); /* lock shared memory */ memset(shmptr, 0, sizeof(struct DirProp)); /* erase previous data */ @@ -3029,54 +3123,78 @@ int main(int argc, char *argv[]) \label{fig:ipc_dirmonitor_main} \end{figure} -Il programma usa delle variabili globali (\texttt{\small 4--18}) per mantenere +Il programma usa delle variabili globali (\texttt{\small 2--14}) per mantenere i valori relativi agli oggetti usati per la comunicazione inter-processo; si è definita inoltre una apposita struttura \struct{DirProp} che contiene i dati relativi alle proprietà che si vogliono mantenere nella memoria condivisa, per l'accesso da parte dei client. Il programma, dopo la sezione, omessa, relativa alla gestione delle opzioni da -riga di comando, che si limitano alla eventuale stampa di un messaggio di +riga di comando (che si limitano alla eventuale stampa di un messaggio di aiuto a video ed all'impostazione della durata dell'intervallo con cui viene -ripetuto il calcolo delle proprietà della directory, controlla (\texttt{\small - 25--28}) che sia stato specificato un parametro (il nome della directory da -tenere sotto controllo) senza il quale esce immediatamente con un messaggio di -errore. - -Il passo successivo (\texttt{\small 29--31}) è quello di installare i gestori -per i segnali di terminazione, infatti, visto che il programma è progettato -come server, non è prevista una conclusione esplicita nel corpo principale, -per cui, per gestire l'uscita, si è fatto uso di questi ultimi. - -Si può poi passare a creare (\texttt{\small 32--45}) gli oggetti di -intercomunicazione necessari. Si ricava (\texttt{\small 33}) una chiave usando -il nome del programma, con questa si ottiene (\texttt{\small 34--38}) un -segmento di memoria condivisa \var{shmid} (uscendo in caso di errore), che si -aggancia (\texttt{\small 39--41}) al processo all'indirizzo \var{shmptr}; sarà -attraverso questo puntatore che potremo accedere alla memoria condivisa, che -sarà vista nella forma data da \struct{DirProp}. Infine con la stessa chiave -si crea (\texttt{\small 42--45}) anche un mutex (utilizzando le funzioni di -interfaccia già descritte in \secref{sec:ipc_sysv_sem}) che utilizzeremo per -regolare l'accesso alla memoria condivisa. +ripetuto il calcolo delle proprietà della directory) controlla (\texttt{\small + 21--24}) che sia stato specificato il parametro necessario contenente il +nome della directory da tenere sotto controllo, senza il quale esce +immediatamente con un messaggio di errore. + +Poi, per verificare che il parametro specifichi effettivamente una directory, +si esegue (\texttt{\small 25--27}) su di esso una \func{chdir}, uscendo +immediatamente in caso di errore. Questa funzione serve anche per impostare +la directory di lavoro del programma nella directory da tenere sotto +controllo, in vista del successivo uso della funzione +\func{daemon}.\footnote{Si noti come si è potuta fare questa scelta, + nonostante le indicazioni illustrate in \secref{sec:sess_daemon}, per il + particolare scopo del programma, che necessita comunque di restare + all'interno di una directory.} Infine (\texttt{\small 28--30}) si installano +i gestori per i vari segnali di terminazione che, avendo a che fare con un +programma che deve essere eseguito come server, sono il solo strumento +disponibile per concluderne l'esecuzione. + +Il passo successivo (\texttt{\small 31--44}) è quello di creare gli oggetti di +intercomunicazione necessari. Si inizia costruendo (\texttt{\small 31}) la +chiave da usare come riferimento con il nome del programma,\footnote{si è + usato un riferimento relativo alla home dell'utente, supposto che i sorgenti + di GaPiL siano stati installati direttamente in essa. Qualora si effettui + una installazione diversa si dovrà correggere il programma.} dopo di che si +richiede (\texttt{\small 32}) la creazione di un segmento di memoria condivisa +con \func{shmget} (una pagina di memoria è sufficiente per i dati che +useremo), uscendo (\texttt{\small 33--36}) qualora la creazione non abbia +successo. + +Una volta ottenutone l'identificatore in \var{shmid}, si può agganciare +(\texttt{\small 37--40}) il segmento al processo con \func{shmat} anche in +questo caso si esce qualora la funzione non abbia successo. Con l'indirizzo +\var{shmptr} così ottenuto potremo poi accedere alla memoria condivisa, che, +per come abbiamo lo abbiamo definito, sarà vista nella forma data da +\struct{DirProp}. Infine (\texttt{\small 41--44}) utilizzando sempre la stessa +chiave, si crea, tramite le funzioni di interfaccia già descritte in +\secref{sec:ipc_sysv_sem}, anche un mutex, che utilizzeremo per regolare +l'accesso alla memoria condivisa. Una volta completata l'inizializzazione e la creazione degli oggetti di -intercomunicazione il programma eseguirà indefinitamente (\texttt{\small - 46--53}) il ciclo principale: si inizia bloccando il mutex (\texttt{\small - 48}) per poter accedere alla memoria condivisa (la funzione si bloccherà -automaticamente se qualche client sta leggendo), poi si cancellano -(\texttt{\small 49}) i valori precedentemente memorizzati, e si esegue -(\texttt{\small 50}) un nuovo calcolo degli stessi; infine si sblocca il mutex -(\texttt{\small 51}), e si attende (\texttt{\small 52}) per il periodo di -tempo specificato a riga di comando con l'opzione \code{-p}. +intercomunicazione il programma entra nel ciclo principale (\texttt{\small + 45--54}) dove vengono eseguitw indefinitamente le attività di monitoraggio. +Il primo passo (\texttt{\small 46}) è esguire \func{daemon} per proseguire con +l'esecuzione in background come si conviene ad un programma demone; si noti +che si è mantenuta, usando un valore non nullo del primo argomento, la +directory di lavoro corrente. + +Una volta che il programma è andato in background l'esecuzione prosegue +(\texttt{\small 47--53}) all'interno di un ciclo infinito: si inizia +(\texttt{\small 48}) bloccando il mutex con \func{MutexLock} per poter +accedere alla memoria condivisa (la funzione si bloccherà automaticamente se +qualche client sta leggendo), poi (\texttt{\small 49}) si cancellano i valori +precedentemente immagazzinati nella memoria condivisa con \func{memset}, e si +esegue (\texttt{\small 50}) un nuovo calcolo degli stessi utilizzando la +funzione \func{DirScan}; infine (\texttt{\small 51}) si sblocca il mutex con +\func{MutexUnlock}, e si attende (\texttt{\small 52}) per il periodo di tempo +specificato a riga di comando con l'opzione \code{-p} con una \func{sleep}. \begin{figure}[!htb] \footnotesize \centering \begin{minipage}[c]{15cm} \begin{lstlisting}{} -... -/* - * Routine to compute directory properties inside DirScan - */ +/* Routine to compute directory properties inside DirScan */ int ComputeValues(struct dirent * direntry) { struct stat data; @@ -3086,15 +3204,13 @@ int ComputeValues(struct dirent * direntry) if (S_ISREG(data.st_mode)) shmptr->tot_regular++; if (S_ISFIFO(data.st_mode)) shmptr->tot_fifo++; if (S_ISLNK(data.st_mode)) shmptr->tot_link++; - if (S_ISDIR(data.st_mode)) shmptr->tot_dir; - if (S_ISBLK(data.st_mode)) shmptr->tot_block; - if (S_ISCHR(data.st_mode)) shmptr->tot_char; - if (S_ISSOCK(data.st_mode)) shmptr->tot_sock; + if (S_ISDIR(data.st_mode)) shmptr->tot_dir++; + if (S_ISBLK(data.st_mode)) shmptr->tot_block++; + if (S_ISCHR(data.st_mode)) shmptr->tot_char++; + if (S_ISSOCK(data.st_mode)) shmptr->tot_sock++; return 0; } -/* - * Signal Handler to manage termination - */ +/* Signal Handler to manage termination */ void HandSIGTERM(int signo) { MutexLock(mutex); if (shmdt(shmptr)) { @@ -3122,56 +3238,43 @@ effettuare la scansione delle voci della directory, chiamando per ciascuna di esse la funzione \func{ComputeValues}, che esegue tutti i calcoli necessari. Il codice di quest'ultima è riportato in \figref{fig:ipc_dirmonitor_sub}. Come -si vede la funzione (\texttt{\small 5--19}) è molto semplice e si limita a -chiamare (\texttt{\small 8}) la funzione \func{stat} sul file indicato da +si vede la funzione (\texttt{\small 2--16}) è molto semplice e si limita a +chiamare (\texttt{\small 5}) la funzione \func{stat} sul file indicato da ciascuna voce, per ottenerne i dati, che poi utilizza per incrementare i vari -contatori. +contatori nella memoria condivisa, cui accede grazie alla variabile globale +\var{shmptr}. Dato che la funzione è chiamata da \func{DirScan}, si è all'interno del ciclo principale del programma, con un mutex acquisito, perciò non è necessario effettuare nessun controllo e si può accedere direttamente alla memoria -condivisa usando \var{shmptr} riempiendo i campi della struttura -\struct{DirProp}; così prima (\texttt{\small 9--10}) si sommano le dimensioni -dei file ed il loro numero, poi utilizzando le macro di -\tabref{tab:file_type_macro}, si contano (\texttt{\small 11--17}) quanti ce ne +condivisa usando \var{shmptr} per riempire i campi della struttura +\struct{DirProp}; così prima (\texttt{\small 6--7}) si sommano le dimensioni +dei file ed il loro numero, poi, utilizzando le macro di +\tabref{tab:file_type_macro}, si contano (\texttt{\small 8--14}) quanti ce ne sono per ciascun tipo. -In \figref{fig:ipc_dirmonitor_sub} è riportato anche (\texttt{\small 23--35}) -il codice del gestore di segnali di terminazione usato per chiudere il -programma, che oltre a provocare l'uscita del programma si incarica anche di -cancellare tutti gli oggetti di intercomunicazione non più necessari. Nel -caso anzitutto (\texttt{\small 24}) si acquisisce il mutex per evitare di -operare mentre un client sta ancora leggendo i dati, dopo di che prima si -distacca (\texttt{\small 25--28}) il segmento e poi lo si cancella -(\texttt{\small 29--32}). Infine (\texttt{\small 33}) si rimuove il mutex e si -esce. - -Il codice del client che permette di leggere le informazioni mantenute nella -memoria condivisa è riportato in \figref{fig:ipc_dirmonitor_client}, al solito -si è omessa la sezione di gestione delle opzioni e la funzione che stampa a -video le istruzioni; il codice completo è nei sorgenti allegati, nel file -\file{ReadMonitor.c}. +In \figref{fig:ipc_dirmonitor_sub} è riportato anche (\texttt{\small 17--30}) +il codice del gestore dei segnali di terminazione, usato per chiudere il +programma. Esso, oltre a provocare l'uscita del programma, si incarica anche +di cancellare tutti gli oggetti di intercomunicazione non più necessari. Per +questo anzitutto (\texttt{\small 19}) acquisisce il mutex con +\func{MutexLock}, per evitare di operare mentre un client sta ancora leggendo +i dati, dopo di che (\texttt{\small 20--23}) prima distacca il segmento di +memoria condivisa con \func{shmad} e poi (\texttt{\small 24--27}) lo cancella +con \func{shctl}. Infine (\texttt{\small 28}) rimuove il mutex con +\func{MutexRemove} ed esce. \begin{figure}[!htb] \footnotesize \centering \begin{minipage}[c]{15cm} \begin{lstlisting}{} -#include -#include -#include /* directory */ -#include /* C standard library */ -#include - -#include "Gapil.h" -#include "macros.h" - int main(int argc, char *argv[]) { int i; key_t key; - .. - /* find needed IPC objects */ - key = ftok("./DirMonitor.c", 1); /* define a key */ + ... + /* create needed IPC objects */ + key = ftok("~/gapil/sources/DirMonitor.c", 1); /* define a key */ shmid = shmget(key, 4096, 0); /* get the shared memory ID */ if (shmid < 0) { perror("Cannot find shared memory"); @@ -3187,8 +3290,6 @@ int main(int argc, char *argv[]) } /* main loop */ MutexLock(mutex); /* lock shared memory */ - printf("File presenti %d file, per un totale di %d byte\n", - shmptr->tot_files, shmptr->tot_size); printf("Ci sono %d file dati\n", shmptr->tot_regular); printf("Ci sono %d directory\n", shmptr->tot_dir); printf("Ci sono %d link\n", shmptr->tot_link); @@ -3196,30 +3297,118 @@ int main(int argc, char *argv[]) printf("Ci sono %d socket\n", shmptr->tot_sock); printf("Ci sono %d device a caratteri\n", shmptr->tot_char); printf("Ci sono %d device a blocchi\n", shmptr->tot_block); + printf("Totale %d file, per %d byte\n", + shmptr->tot_files, shmptr->tot_size); MutexUnlock(mutex); /* unlock shared memory */ } \end{lstlisting} \end{minipage} \normalsize - \caption{Codice del programma client \file{ReadMonitor.c}.} + \caption{Codice del programma client del monitori di directory, + \file{ReadMonitor.c}.} \label{fig:ipc_dirmonitor_client} \end{figure} +Il codice del client, che permette di leggere le informazioni mantenute nella +memoria condivisa, è riportato in \figref{fig:ipc_dirmonitor_client}. Al +solito si è omessa la sezione di gestione delle opzioni e la funzione che +stampa a video le istruzioni; il codice completo è nei sorgenti allegati, nel +file \file{ReadMonitor.c}. + Una volta completata la gestione delle opzioni a riga di comando il programma -rigenera (\texttt{\small 16}) la chiave usata per identificare memoria -condivisa e mutex, richiede (\texttt{\small 17}) l'identificatore della -memoria condivisa (che in questo caso deve già esistere), uscendo in caso di -errore (\texttt{\small 18--21}). Una volta ottenuto l'identificatore si può -(\texttt{\small 22--25}) agganciare il segmento al processo (anche in questo -caso uscendo se qualcosa non funziona). Infine (\texttt{\small 26--29}) si -richiede pure l'identificatore del mutex. +rigenera (\texttt{\small 7}) con \func{ftok} la stessa chiave usata dal server +per identificare il segmento di memoria condivisa ed il mutex, poi +(\texttt{\small 8}) si richiede con \func{semget} l'identificatore della +memoria condivisa, ma in questo caso si vuole che esso esista di già; al +solito (\texttt{\small 9--12}) si esce in caso di errore. Una volta ottenuto +l'identificatore in \var{shmid} si può (\texttt{\small 13--16}) agganciare il +segmento al processo all'indirizzo \func{shmptr}; anche in questo caso si +chiude immediatamente il programma se qualcosa non funziona. Infine +(\texttt{\small 17--20}) con \func{MutexFind} si richiede l'identificatore del +mutex. Una volta completata l'inizializzazione ed ottenuti i riferimenti agli oggetti di intercomunicazione necessari viene eseguito il corpo principale del -programma (\texttt{\small 30--41}); si acquisisce (\texttt{\small 31}) il -mutex (qui avviene il blocco se la memoria condivisa non è disponibile), e poi -(\texttt{\small 32--40}) si stampano i vari valori in essa contenuti. Infine -(\texttt{\small 41}) si rilascia il mutex. +programma (\texttt{\small 21--33}); si comincia (\texttt{\small 22}) +acquisendo il mutex con \func{MutexLock}; qui avviene il blocco del processo +se la memoria condivisa non è disponibile. Poi (\texttt{\small 23--31}) si +stampano i vari valori mantenuti nella memoria condivisa attraverso l'uso di +\var{shmptr}. Infine (\texttt{\small 41}) con \func{MutexUnlock} si rilascia +il mutex, prima di uscire. + +Verifichiamo allora il funzionamento dei nostri programmi; al solito, usando +le funzioni di libreira occorre definire opportunamente +\code{LD\_LIBRARY\_PATH}; poi si potrà lanciare il server con: +\begin{verbatim} +[piccardi@gont sources]$ ./dirmonitor ./ +\end{verbatim}%$ +ed avendo usato \func{daemon} il comando ritornerà immediatamente. Una volta +che il server è in esecuzione, possiamo passare ad invocare il client per +verificarne i risultati, in tal caso otterremo: +\begin{verbatim} +[piccardi@gont sources]$ ./readmon +Ci sono 68 file dati +Ci sono 3 directory +Ci sono 0 link +Ci sono 0 fifo +Ci sono 0 socket +Ci sono 0 device a caratteri +Ci sono 0 device a blocchi +Totale 71 file, per 489831 byte +\end{verbatim}%$ +ed un rapido calcolo (ad esempio con \code{ls -a | wc} per contare i file) ci +permette di verificare che il totale dei file è giusto. Un controllo con +\cmd{ipcs} ci permette inoltre di verificare la presenza di un segmento di +memoria condivisa e di un semaforo: +\begin{verbatim} +[piccardi@gont sources]$ ipcs +------ Shared Memory Segments -------- +key shmid owner perms bytes nattch status +0xffffffff 54067205 piccardi 666 4096 1 + +------ Semaphore Arrays -------- +key semid owner perms nsems +0xffffffff 229376 piccardi 666 1 + +------ Message Queues -------- +key msqid owner perms used-bytes messages +\end{verbatim}%$ + +Se a questo punto aggiungiamo un file, ad esempio con \code{touch prova}, +potremo verificare (passati nel peggiore dei casi almeno 10 secondi, cioè +l'intervallo scelto per la rilettura dei dati), che: +\begin{verbatim} +[piccardi@gont sources]$ ./readmon +Ci sono 69 file dati +Ci sono 3 directory +Ci sono 0 link +Ci sono 0 fifo +Ci sono 0 socket +Ci sono 0 device a caratteri +Ci sono 0 device a blocchi +Totale 72 file, per 489887 byte +\end{verbatim}%$ + +Infine potremo terminare il server con il comando \code{killall dirmonitor}, +nel qual caso, ripetendo la lettura otterremo che: +\begin{verbatim} +[piccardi@gont sources]$ ./readmon +Cannot find shared memory: No such file or directory +\end{verbatim}%$ +e potremo verificare che anche gli oggetti di intercomunicazion e sono stati +cancellati: +\begin{verbatim} +[piccardi@gont sources]$ ipcs +------ Shared Memory Segments -------- +key shmid owner perms bytes nattch status + +------ Semaphore Arrays -------- +key semid owner perms nsems + +------ Message Queues -------- +key msqid owner perms used-bytes messages +\end{verbatim}%$ + %% Per capire meglio il funzionamento delle funzioni facciamo ancora una volta @@ -3268,8 +3457,6 @@ diversa con un uso combinato della memoria condivisa e dei meccanismi di sincronizzazione, per cui alla fine l'uso delle code di messaggi classiche è relativamente poco diffuso. - - \subsection{I \textsl{file di lock}} \label{sec:ipc_file_lock} @@ -3349,13 +3536,13 @@ solo se si opera all'interno di uno stesso filesystem. Un generale comunque l'uso di un \textsl{file di lock} presenta parecchi problemi, che non lo rendono una alternativa praticabile per la -sincronizzazione: anzitutto anche in questo caso, in caso di terminazione -imprevista del processo, si lascia allocata la risorsa (il \textsl{file di - lock}) e questa deve essere sempre cancellata esplicitamente. Inoltre il -controllo della disponibilità può essere eseguito solo con una tecnica di -\textit{polling}\index{polling}, ed è quindi molto inefficiente. +sincronizzazione: anzitutto in caso di terminazione imprevista del processo, +si lascia allocata la risorsa (il \textsl{file di lock}) e questa deve essere +sempre cancellata esplicitamente. Inoltre il controllo della disponibilità +può essere eseguito solo con una tecnica di \textit{polling}\index{polling}, +ed è quindi molto inefficiente. -La tecnica dei file di lock non di meno ha una sua utilità, e può essere usata +La tecnica dei file di lock ha comunque una sua utilità, e può essere 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 @@ -3367,104 +3554,161 @@ disponibile.\index{file!di lock|)} \subsection{La sincronizzazione con il \textit{file locking}} \label{sec:ipc_lock_file} -Dato che i file di lock presentano gli inconvenienti illustrati in precedenza, -la tecnica alternativa più comune è quella di fare ricorso al \textit{file - locking}\index{file!locking} (trattato in \secref{sec:file_locking}) usando -\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. +Dato che i file di lock\index{file!di lock} presentano gli inconvenienti +illustrati in precedenza, la tecnica alternativa di sincronizzazione più +comune è quella di fare ricorso al \textit{file locking}\index{file!locking} +(trattato in \secref{sec:file_locking}) usando \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, inoltre -non consuma risorse permanentemente allocate nel sistema, lo svantaggio è che -dovendo fare ricorso a delle operazioni sul filesystem esso è in genere +chiusura dei relativi file) e non ci si deve preoccupare di niente; inoltre +non consuma risorse permanentemente allocate nel sistema. Lo svantaggio è che, +dovendo fare ricorso a delle operazioni sul filesystem, esso è in genere leggermente più lento. \begin{figure}[!htb] \footnotesize \centering \begin{minipage}[c]{15cm} \begin{lstlisting}{} -/* - * Function LockMutex: lock a file (creating it if not existent). - */ -int LockMutex(const char *path_name) +/* Function CreateMutex: Create a mutex using file locking. */ +int CreateMutex(const char *path_name) +{ + return open(path_name, O_EXCL|O_CREAT); +} +/* Function UnlockMutex: unlock a file. */ +int FindMutex(const char *path_name) +{ + return open(path_name, O_RDWR); +} +/* Function LockMutex: lock mutex using file locking. */ +int LockMutex(int fd) { - int fd, res; struct flock lock; /* file lock structure */ /* first open the file (creating it if not existent) */ - if ( (fd = open(path_name, O_EXCL|O_CREAT)) < 0) { /* first open file */ - return fd; - } /* set flock structure */ lock.l_type = F_WRLCK; /* set type: read or write */ lock.l_whence = SEEK_SET; /* start from the beginning of the file */ lock.l_start = 0; /* set the start of the locked region */ lock.l_len = 0; /* set the length of the locked region */ /* do locking */ - if ( (res = fcntl(fd, F_SETLKW, &lock)) < 0 ) { - return res; - } - return 0; + return fcntl(fd, F_SETLKW, &lock); } -/* - * Function UnlockMutex: unlock a file. - */ -int UnlockMutex(const char *path_name) +/* Function UnlockMutex: unlock a file. */ +int UnlockMutex(int fd) { - int fd, res; struct flock lock; /* file lock structure */ - /* first open the file */ - if ( (fd = open(path_name, O_RDWR)) < 0) { /* first open file */ - return fd; - } /* set flock structure */ lock.l_type = F_UNLCK; /* set type: unlock */ lock.l_whence = SEEK_SET; /* start from the beginning of the file */ lock.l_start = 0; /* set the start of the locked region */ lock.l_len = 0; /* set the length of the locked region */ /* do locking */ - if ( (res = fcntl(fd, F_SETLK, &lock)) < 0 ) { + return fcntl(fd, F_SETLK, &lock); +} +/* Function RemoveMutex: remove a mutex (unlinking the lock file). */ +int RemoveMutex(const char *path_name) +{ + return unlink(path_name); +} +/* Function ReadMutex: read a mutex status. */ +int ReadMutex(int fd) +{ + int res; + struct flock lock; /* file lock structure */ + /* set flock structure */ + lock.l_type = F_WRLCK; /* set type: unlock */ + lock.l_whence = SEEK_SET; /* start from the beginning of the file */ + lock.l_start = 0; /* set the start of the locked region */ + lock.l_len = 0; /* set the length of the locked region */ + /* do locking */ + if ( (res = fcntl(fd, F_GETLK, &lock)) ) { return res; } - return 0; + return lock.l_type; } \end{lstlisting} \end{minipage} \normalsize - \caption{Il codice delle funzioni che permettono di creare un - \textit{mutex} utilizzando il file locking\index{file!locking}.} + \caption{Il codice delle funzioni che permettono per la gestione dei + \textit{mutex} con il file locking\index{file!locking}.} \label{fig:ipc_flock_mutex} \end{figure} -Il codice per implementare un mutex utilizzando il file -locking\index{file!locking} è riportato in \figref{fig:ipc_flock_mutex}; a -differenza del precedente caso in cui si sono usati i semafori le funzioni -questa volta sono sufficienti due funzioni, \func{LockMutex} e -\func{UnlockMutex}, usate rispettivamente per acquisire e rilasciare il mutex. - -La prima funzione (\texttt{\small 1--22}) serve per acquisire il mutex. -Anzitutto si apre (\texttt{\small 9--11}), creandolo se non esiste, il file -specificato dall'argomento \param{pathname}. In caso di errore si ritorna -immediatamente, altrimenti si prosegue impostando (\texttt{\small 12--16}) la -struttura \var{lock} in modo da poter acquisire un write lock sul file. -Infine si richiede (\texttt{\small 17--20}) il file lock (restituendo il -codice di ritorno di \func{fcntl} caso di errore). Se il file è libero il lock -è acquisito e la funzione ritorna immediatamente; altrimenti \func{fcntl} si -bloccherà (si noti che la si è chiamata con \func{F\_SETLKW}) fino al rilascio -del lock. - -La seconda funzione (\texttt{\small 23--44}) serve a rilasciare il mutex. Di -nuovo si apre (\texttt{\small 30--33}) il file specificato dall'argomento -\param{pathname} (che stavolta deve esistere), ritornando immediatamente in -caso di errore. Poi si passa ad inizializzare (\texttt{\small 34--38}) la -struttura \var{lock} per il rilascio del lock, che viene effettuato -(\texttt{\small 39--42}) subito dopo. +Il codice delle varie funzioni usate per implementare un mutex utilizzando il +file locking\index{file!locking} è riportato in \figref{fig:ipc_flock_mutex}; +si è mantenuta volutamente una struttura analoga alle precedenti funzioni che +usano i semafori, anche se le due interfacce non possono essere completamente +equivalenti, specie per quanto riguarda la rimozione del mutex. + +La prima funzione (\texttt{\small 1--5}) è \func{CreateMutex}, e serve a +creare il mutex; la funzione è estremamente semplice, e si limita +(\texttt{\small 4}) a creare, con una opportuna chiamata ad \func{open}, il +file che sarà usato per il successivo file locking, assicurandosi che non +esista già (nel qual caso segnala un errore); poi restituisce il file +descriptor che sarà usato dalle altre funzioni per acquisire e rilasciare il +mutex. + +La seconda funzione (\texttt{\small 6--10}) è \func{FindMutex}, che, come la +precedente, è stata definita per mantenere una analogia con la corrispondente +funzione basata sui semafori. Anch'essa si limita (\texttt{\small 9}) ad +aprire il file da usare per il file locking, solo che in questo caso le +opzioni di \func{open} sono tali che il file in questione deve esistere di +già. + +La terza funzione (\texttt{\small 11--23}) è \func{LockMutex} e serve per +acquisire il mutex. La funzione definisce (\texttt{\small 14}) e inizializza +(\texttt{\small 17--20}) la struttura \var{lock} da usare per acquisire un +write lock sul file, che poi (\texttt{\small 21}) viene richiesto con +\func{fcntl}, restituendo il valore di ritorno di quest'ultima. Se il file è +libero il lock viene acquisito e la funzione ritorna immediatamente; +altrimenti \func{fcntl} si bloccherà (si noti che la si è chiamata con +\func{F\_SETLKW}) fino al rilascio del lock. + +La quarta funzione (\texttt{\small 24--35}) è \func{UnlockMutex} e serve a +rilasciare il mutex. La funzione è analoga alla precedente, solo che in questo +caso si inizializza (\texttt{\small 29--32}) la struttura \var{lock} per il +rilascio del lock, che viene effettuato (\texttt{\small 34}) con la opportuna +chiamata a \func{fcntl}. Avendo usato il file locking in semantica POSIX (si +riveda quanto detto \secref{sec:file_posix_lock}) solo il processo che ha +precedentemente eseguito il lock può sbloccare il mutex. + +La quinta funzione (\texttt{\small 36--40}) è \func{RemoveMutex} e serve a +cancellare il mutex. Anche questa funzione è stata definita per mantenere una +analogia con le funzioni basate sui semafori, e si limita a cancellare +(\texttt{\small 39}) il file con una chiamata ad \func{unlink}. Si noti che in +questo caso la funzione non ha effetto sui mutex già ottenuti con precedenti +chiamate a \func{FindMutex} o \func{CreateMutex}, che continueranno ad essere +disponibili fintanto che i relativi file descriptor restano aperti. Pertanto +per rilasciare un mutex occorrerà prima chiamare \func{UnlockMutex} oppure +chiudere il file usato per il lock. + +La sesta funzione (\texttt{\small 41--56}) è \func{ReadMutex} e serve a +leggere lo stato del mutex. In questo caso si prepara (\texttt{\small 47--50}) +la solita struttura \var{lock} come l'acquisizione del lock, ma si effettua +(\texttt{\small 52}) la chiamata a \func{fcntl} usando il comando +\const{F\_GETLK} per ottenere lo stato del lock, e si restituisce +(\texttt{\small 53}) il valore di ritorno in caso di errore, ed il valore del +campo \var{l\_type} (che descrive lo stato del lock) altrimenti. Per questo +motivo la funzione restituirà -1 in caso di errore e uno dei due valori +\const{F\_UNLCK} o \const{F\_WRLCK}\footnote{non si dovrebbe mai avere il + terzo valore possibile, \const{F\_RDLCK}, dato che la nostra interfaccia usa + solo i write lock. Però è sempre possibile che siano richiesti altri lock + sul file al di fuori dell'interfaccia, nel qual caso si potranno avere, + ovviamente, interferenze indesiderate.} in caso di successo, ad indicare che +il mutex è, rispettivamente, libero o occupato. + +Basandosi sulla semantica dei file lock POSIX valgono tutte le considerazioni +relative al comportamento di questi ultimi fatte in +\secref{sec:file_posix_lock}; questo significa ad esempio che, al contrario di +quanto avveniva con l'interfaccia basata sui semafori, chiamate multiple a +\func{UnlockMutex} o \func{LockMutex} non hanno nessun inconveniente. \subsection{Il \textit{memory mapping} anonimo}