From: Simone Piccardi Date: Thu, 5 Dec 2002 23:38:22 +0000 (+0000) Subject: Shared memory, esempio di LockFile, di Mutex con il file locking, X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=commitdiff_plain;h=bff17765a4aa5e979e0a72b70917c70254e97c98;ds=sidebyside Shared memory, esempio di LockFile, di Mutex con il file locking, spiegazioni sul memory mapping anonimo e aggiunta delle relative funzioni al codice. --- diff --git a/ipc.tex b/ipc.tex index 2975185..ca655f9 100644 --- a/ipc.tex +++ b/ipc.tex @@ -2951,11 +2951,12 @@ viene tolta dallo spazio di indirizzi del processo. \section{Tecniche alternative} \label{sec:ipc_alternatives} -Come abbiamo visto in \secref{sec:ipc_sysv_generic} il \textit{SysV IPC} +Come abbiamo detto in \secref{sec:ipc_sysv_generic}, e ripreso nella +descrizione dei signoli oggetti che ne fan parte, il \textit{SysV IPC} presenta numerosi problemi; in \cite{APUE}\footnote{in particolare nel - capitolo 14.} Stevens effettua una accurata analisi (alcuni dei concetti -sono già stati accennati in precedenza) ed elenca alcune possibili -alternative, che vogliamo riprendere in questa sezione. + capitolo 14.} Stevens ne eeffettua una accurata analisi (alcuni dei +concetti sono già stati accennati in precedenza) ed elenca alcune possibili +tecniche alternative, che vogliamo riprendere in questa sezione. \subsection{Alternative alle code di messaggi} @@ -2978,7 +2979,7 @@ cui alla fine l'uso delle code di messaggi classiche -\subsection{La sincronizzazione con il \textit{file locking}} +\subsection{I \textsl{file di lock}} \label{sec:ipc_file_lock} Come illustrato in \secref{sec:ipc_sysv_sem} i semafori del \textit{SysV IPC} @@ -2998,36 +2999,79 @@ usa la caratteristica della funzione \func{open} (illustrata in tecnica possa non funzionare; in particolare per Linux, nel caso di NFS, si è comunque soggetti alla possibilità di una race condition.} che essa ritorni un errore quando usata con i flag di \macro{O\_CREAT} e -\macro{O\_EXCL}. In tal modo la creazione di un file di lock può essere -eseguita atomicamente, il processo che crea il file con successo si può +\macro{O\_EXCL}. In tal modo la creazione di un \textsl{file di lock} può +essere eseguita atomicamente, il processo che crea il file con successo si può considerare come titolare del lock (e della risorsa ad esso associata) mentre -il rilascio si può eseguire con una chiamata ad -\func{unlink}.\footnote{abbiamo già accennato in \secref{sec:file_open} che - questa tecnica può non funzionare se il filesystem su cui si va ad operare è - su NFS; in tal caso si può adottare una tecnica alternativa che prevede - l'uso di \func{link} per creare come file di lock un hard link ad un file - esistente; se il link esiste già e la funzione fallisce, significa che la - risorsa è bloccata e potrà essere sbloccata solo con un \func{unlink}, - altrimenti il link è creato ed il lock acquisito; il controllo e l'eventuale - acquisizione sono atomici; il difetto di questa soluzione è che funziona - solo se si opera all'interno di uno stesso filesystem.} - -L'uso di un file di lock presenta però parecchi problemi, che non lo rendono -una alternativa praticabile per la sincronizzazione: anzitutto anche in questo -caso in caso di terminazione imprevista del processo lascia allocata la -risorsa (il file di lock) e questa deve essere sempre cancellata -esplicitamente. Inoltre il controllo della disponibilità può essere fatto -solo con una tecnica di \textit{polling}\index{polling}, che è molto -inefficiente. - -La tecnica può comunque 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 più programmi: qualora si trovi un -file di lock il programma che cerca di accedere alla seriale si limita a -segnalare che la risorsa non è disponibile; in \file{LockFile.c} (un'altro dei -sorgenti allegati alla guida) si sono predisposte due funzioni, -\func{LockFile} e \func{UnlockFile}, da utilizzare allo scopo. +il rilascio si può eseguire con una chiamata ad \func{unlink}. + +Un esempio dell'uso di questa funzione è mostrato dalle funzioni +\func{LockFile} ed \func{UnlockFile} riportate in \figref{fig:ipc_file_lock} +(sono contenute in \file{LockFile.c}, un'altro dei sorgenti allegati alla +guida) che permettono rispettivamente di creare e rimuovere un \textsl{file di + lock}. Come si può notare entrambe le funzioni sono elementari; la prima +(\texttt{\small 4--10}) si limita ad aprire il file di lock (\texttt{\small + 9}) nella modalità descritta, mentre la seconda (\texttt{\small 11--17}) lo +cancella con \func{unlink}. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \begin{lstlisting}{} +#include +#include +#include /* unix standard functions */ +/* + * Function LockFile: + */ +int LockFile(const char* path_name) +{ + return open(path_name, O_EXCL|O_CREAT); +} +/* + * Function UnlockFile: + */ +int UnlockFile(const char* path_name) +{ + return unlink(path_name); +} + + \end{lstlisting} + \end{minipage} + \normalsize + \caption{Il codice delle funzioni \func{LockFile} e \func{UnlockFile} che + permettono di creare e rimuovere un \textsl{file di lock}.} + \label{fig:ipc_file_lock} +\end{figure} + +Uno dei limiti di questa tecnica è che, come abbiamo già accennato in +\secref{sec:file_open}, questo comportamento di \func{open} può non funzionare +(la funzione viene eseguita, ma non è garantita l'atomicità dell'operazione) +se il filesystem su cui si va ad operare è su NFS; in tal caso si può adottare +una tecnica alternativa che prevede l'uso della \func{link} per creare come +file di lock un hard link ad un file esistente; se il link esiste già e la +funzione fallisce, significa che la risorsa è bloccata e potrà essere +sbloccata solo con un \func{unlink}, altrimenti il link è creato ed il lock +acquisito; il controllo e l'eventuale acquisizione sono atomici; la soluzione +funziona anche su NFS, ma ha un'altro difetto è che è quello di poterla usare +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 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 +con successo quando l'esigenza è solo quella di segnalare l'occupazione di una +risorsa, senza necessità di attendere che questa si liberi; ad esempio la si +usa spesso per evitare interferenze sull'uso delle porte seriali da parte di +più programmi: qualora si trovi un file di lock il programma che cerca di +accedere alla seriale si limita a segnalare che la risorsa non è disponibile. + +\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 @@ -3047,11 +3091,54 @@ non consuma risorse permanentemente allocate nel sistema, lo svantaggio dovendo fare ricorso a delle operazioni sul filesystem esso è in genere leggermente più lento. -\begin{figure}[!bht] +\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) +{ + 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; +} +/* + * Function UnLockMutex: unlock a file. + */ +int UnlockMutex(const char *path_name) +{ + 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 res; + } + return 0; +} \end{lstlisting} \end{minipage} \normalsize @@ -3061,21 +3148,60 @@ leggermente pi \end{figure} Il codice per implementare un mutex utilizzando il file locking è riportato in -\figref{fig:ipc_flock_mutex}; come nel precedente caso dei mutex implementato -con i semafori le funzioni sono tre - - - - -\subsection{Il \textit{memory mapping} anonimo} +\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. + + \subsection{Il \textit{memory mapping} anonimo} \label{sec:ipc_mmap_anonymous} -Abbiamo visto in \secref{sec:file_memory_map} come sia possibile mappare il -contenuto di un file nella memoria di un processo. Una della opzioni possibili -utilizzabili con Linux è quella del \textit{memory mapping} -anonimo\footnote{in altri sistemi una funzionalità simile a questa viene - implementata mappando il file speciale \file{/dev/zero}.}, in tal caso -infatti +Abbiamo già visto che quando i processi sono \textsl{correlati}\footnote{se + cioè hanno almeno un progenitore comune.} l'uso delle pipe può costituire +una valida alternativa alle code di messaggi; nella stessa situazione si può +evitare l'uso di una memoria condivisa facendo ricorso al cosiddetto +\textit{memory mapping} anonimo. + +Abbiamo visto in \secref{sec:file_memory_map} che è possibile mappare il +contenuto di un file nella memoria di un processo, e che, quando viene usato +il flag \macro{MAP\_SHARED}, le modifiche effettuate al contenuto del file +vengono viste da tutti i processi che lo hanno mappato. Utilizzare questa +tecnica per creare una memoria condivisa fra processi diversi è estremamente +inefficiente, in quanto occorre passare attraverso il disco. Però abbiamo +visto anche che se si esegue la mappatura con il flag \macro{MAP\_ANONYMOUS} +la regione mappata non viene associata a nessun file, anche se quanto scritto +rimane in memoria e può essere riletto; allora, dato che un processo figlio +mantiene nel suo spazio degli indirizzi anche le regioni mappate, esso sarà +anche in grado di accedere a quanto in esse è contenuto. + +In questo modo diventa possibile creare una memoria condivisa fra processi +diversi, purché questi abbiano almeno un progenitore comune che ha effettuato +il \textit{memory mapping} anonimo.\footnote{nei sistemi derivati da SysV una + funzionalità simile a questa viene implementata mappando il file speciale + \file{/dev/zero}. In tal caso i valori scritti nella regione mappata non + vengono ignorati (come accade qualora si scriva direttamente sul file), ma + restano in memoria e possono essere riletti secondo le stesse modalità usate + nele \textit{memory mapping} anonimo.} Un esempio di utilizzo di questa +tecnica è mostrato in + \section{La comunicazione fra processi di POSIX} @@ -3094,9 +3220,9 @@ una interfaccia completamente nuova, che tratteremo in questa sezione. Il Linux non tutti gli oggetti del POSIX IPC sono supportati nel kernel ufficiale; solo la memoria condivisa è presente, ma solo a partire dal kernel -2.4.x, per gli altri oggetti esistono patch e librerie non -ufficiali. Nonostante questo è importante esaminare questa interfaccia per la -sua netta superiorità nei confronti di quella del \textit{SysV IPC}. +2.4.x, per gli altri oggetti esistono patch e librerie non ufficiali. +Nonostante questo è importante esaminare questa interfaccia per la sua netta +superiorità nei confronti di quella del \textit{SysV IPC}. \subsection{Code di messaggi} diff --git a/sources/Gapil.h b/sources/Gapil.h index 541cd49..67dabab 100644 --- a/sources/Gapil.h +++ b/sources/Gapil.h @@ -23,7 +23,7 @@ * * Author: S. Piccardi * - * $Id: Gapil.h,v 1.2 2002/12/03 22:30:11 piccardi Exp $ + * $Id: Gapil.h,v 1.3 2002/12/05 23:38:22 piccardi Exp $ * *****************************************************************************/ #include /* IPC semaphore declarations */ @@ -63,10 +63,15 @@ inline int MutexRead(int sem_id); inline int MutexLock(int sem_id); /* Function MutexUnlock: to unlock a mutex/semaphore. See Mutex.c */ inline int MutexUnlock(int sem_id); +/* Function LockMutex: acquire a mutex (using file locking). See Mutex.c */ +inline int LockFile(const char* path_name); +/* Function UnlockMutex: release a mutex (using file locking). See Mutex.c */ +inline int UnlockFile(const char* path_name); + -/* Function LockFile: create a lock file. See FileLock.c*/ +/* Function LockFile: create a lock file. See FileLock.c */ inline int LockFile(const char* path_name); -/* Function UnLockFile: remove a lock file. See FileLock.c*/ +/* Function UnlockFile: remove a lock file. See FileLock.c */ inline int UnlockFile(const char* path_name); /* * Signal Handling Functions diff --git a/sources/Mutex.c b/sources/Mutex.c index ddbb9b1..d009998 100644 --- a/sources/Mutex.c +++ b/sources/Mutex.c @@ -22,7 +22,7 @@ * * Author: S. Piccardi Dec. 2002 * - * $Id: Mutex.c,v 1.2 2002/12/03 22:30:11 piccardi Exp $ + * $Id: Mutex.c,v 1.3 2002/12/05 23:38:22 piccardi Exp $ * *****************************************************************************/ #include /* IPC semaphore declarations */ @@ -109,3 +109,63 @@ int MutexUnlock(int sem_id) { return semop(sem_id, &sem_ulock, 1); } +/***************************************************************************** + * + * File locking mutex + * + * Create a mutex usinf file locking. Use file locking to lock a file + * as a mutex request, and unlock it as a mutex release. + * + * Author: S. Piccardi Dec. 2002 + * + *****************************************************************************/ +/* + * Function LockMutex: lock a file (creating it if not existent). + * + * Input: a filename + * Output: a return code (0 OK, -1 KO) + */ +int LockMutex(const char *path_name) +{ + 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; +} +/* + * Function UnlockMutex: unlock a file. + * + * Input: a filename + * Output: a return code (0 OK, -1 KO) + */ +int UnlockMutex(const char *path_name) +{ + 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 res; + } + return 0; +}