Spiegazione sulla implementazione dei semafori e correzioni su alcuni
[gapil.git] / ipc.tex
diff --git a/ipc.tex b/ipc.tex
index 4db9ea2db3dbb3bdf45d6668be6ff306c8156a54..da8d85a09c1e7eff365c4d02aa305fef2da2ed53 100644 (file)
--- a/ipc.tex
+++ b/ipc.tex
@@ -1718,55 +1718,58 @@ void HandSIGTERM(int signo) {
 In \figref{fig:ipc_mq_fortune_server} si è riportato un estratto delle parti
 primcipali del codice del nuovo server (il codice completo è nel file
 \file{MQFortuneServer.c} nei sorgenti allegati). Il programma è basato su un
-uso accorto dei tipi di messaggi per permettere una comunicazione indipendente
-fra il server ed i vari client, usando il \acr{pid} di questi ultimi come
-identificativo. Questo è possibile in quanto, al contrario di una fifo, la
-lettura di una coda di messaggi può non essere sequanziale, proprio grazie
-alla classificazione dei messaggi sulla base del loro tipo.  
-
-
-Oltre alle solite variabili per il nome del file delle fifo e per il vettore
-di stringhe che contiene le frasi, il programma utilizza due strutture per la
-comunicazione; con \var{msgbuf\_read} (\texttt{\small 8--11}) vengono passate
-le richieste mentre con \var{msgbuf\_write} (\texttt{\small 12--15}) vengono
-restituite le frasi.
-
-La gestione delle opzioni si è al solito omessa, essa si curerà di restituire
-in \var{n} il numero di file da leggere ed in \var{fortunefilename} il file da
-cui leggerle; dopo aver installato (\texttt{\small 19--21}) dei manipolatori
-per gestire l'uscita prima viene controllato (\texttt{\small 22}) che si siano
-richiesti un numero positivo di messaggi, che poi (\texttt{\small 23}) vengono
-letti con la stessa funzione \code{FortuneParse()} usata anche per il server
-basato sulle fifo.
+uso accorto della caratteristica di poter associate un ``tipo'' ai messaggi
+per permettere una comunicazione indipendente fra il server ed i vari client,
+usando il \acr{pid} di questi ultimi come identificativo. Questo è possibile
+in quanto, al contrario di una fifo, la lettura di una coda di messaggi può
+non essere sequanziale, proprio grazie alla classificazione dei messaggi sulla
+base del loro tipo.
+
+Il programma, oltre alle solite variabili per il nome del file da cui leggere
+le \textit{fortunes} e per il vettore di stringhe che contiene le frasi,
+definisce due strutture appositamente per la comunicazione; con
+\var{msgbuf\_read} (\texttt{\small 8--11}) vengono passate le richieste mentre
+con \var{msgbuf\_write} (\texttt{\small 12--15}) vengono restituite le frasi.
+
+La gestione delle opzioni si è al solito omessa, essa si curerà di impostare
+in \var{n} il numero di frasi da leggere specificato a linea di comando ed in
+\var{fortunefilename} il file da cui leggerle; dopo aver installato
+(\texttt{\small 19--21}) dei manipolatori per gestire l'uscita dal server,
+viene prima controllato (\texttt{\small 22}) il numero di frasi richieste
+abbia senso (cioè sia maggiore di zero), le quali poi (\texttt{\small 23})
+vengono lette nel vettore in memoria con la stessa funzione
+\code{FortuneParse()} usata anche per il server basato sulle fifo.
 
 Una volta inizializzato il vettore di stringhe coi messaggi presi dal file
-delle fortunes si procede (\texttt{\small 25}) con la generazione di una
-chiave (si usa il nome del file dei sorgenti del server) con la quale poi si
-esegue (\texttt{\small 26}) la creazione della nostra coda di messaggi (si
-noti come si sia chiamata \func{msgget} con un valore opportuno per il flag),
-avendo cura di abortire il programma (\texttt{\small 27--29}) in caso di
-errore.
+delle \textit{fortune} si procede (\texttt{\small 25}) con la generazione di
+una chiave per identificare la coda di messaggi (si usa il nome del file dei
+sorgenti del server) con la quale poi si esegue (\texttt{\small 26}) la
+creazione della stessa (si noti come si sia chiamata \func{msgget} con un
+valore opportuno per l'argomento \param{flag}), avendo cura di abortire il
+programma (\texttt{\small 27--29}) in caso di errore.
 
 Finita la fase di inizializzazione il server esegue in permanenza il ciclo
 principale (\texttt{\small 32--41}). Questo inizia (\texttt{\small 33}) con il
 porsi in attesa di un messaggio di richiesta da parte di un client; si noti
-infatti come \func{msgrcv} richieda un messaggio con \var{mtype} uguale a 1,
-che è il valore usato per le richieste dato che corriponde al \acr{pid} di
+infatti come \func{msgrcv} richieda un messaggio con \var{mtype} uguale a 1:
+questo è il valore usato per le richieste dato che corriponde al \acr{pid} di
 \cmd{init}, che non può essere un client. L'uso del flag \macro{MSG\_NOERROR}
 è solo per sicurezza, dato che i messaggi di richiesta sono di dimensione
-fissa.
+fissa (e contengono solo il \acr{pid} del client).
 
-Se non sono presenti messaggi di richiesta \func{msgrcv} si bloccherà;
-all'arrivo sulla coda di un messaggio di richiesta da parte di un client la
-funzione ritorna, ed 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.
+Se non sono presenti messaggi di richiesta \func{msgrcv} si bloccherà,
+ritornando soltanto in corrispondenza dell'arrivo sulla coda di un messaggio
+di richiesta da parte di un client, in tal caso il ciclo prosegue
+(\texttt{\small 34}) selezionando una frase a caso, copiandola (\texttt{\small
+  35}) nella struttura \var{msgbuf\_write} usata per la risposta e
+calcolandone (\texttt{\small 36}) la dimensione.
 
 Per poter permettere a ciascun client di ricevere solo la risposta indirizzata
 a lui il tipo del messaggio in uscita viene inizializzato (\texttt{\small 37})
-al valore ricevuto nel messaggio di richiesta.  L'ultimo passo del ciclo
-(\texttt{\small 38}) è inviare sulla coda il messaggio di risposta.
+al valore del \acr{pid} del client ricevuto nel messaggio di richiesta.
+L'ultimo passo del ciclo (\texttt{\small 38}) è inviare sulla coda il
+messaggio di risposta. Si tenga conto che se la coda è piena anche questa
+funzione potrà bloccarsi fintanto che non venga liberato dello spazio.
 
 Si noti che il programma può terminare solo grazie ad una interruzione da
 parte di un segnale; in tal caso verrà eseguito il manipolatore
@@ -1803,27 +1806,44 @@ int main(int argc, char *argv[])
 \end{figure}
 
 In \figref{fig:ipc_mq_fortune_client} si è riportato un estratto il codice del
-programma client, al solito il codice completo è con i sorgenti allegati, nel
+programma client.  Al solito il codice completo è con i sorgenti allegati, nel
 file \file{MQFortuneClient.c}.  Come sempre si sono rimosse le parti relative
 alla gestione delle opzioni, ed in questo caso, anche la dichiarazione delle
-variabili.
+variabili, che, per la parte relative alle strutture usate per la
+comunicazione tramite le code, sono le stesse viste in
+\figref{fig:ipc_mq_fortune_server}.
 
 Il client in questo caso è molto semplice; la prima parte del programma
 (\texttt{\small 4--9}) si occupa di accedere alla coda di messaggi, ed è
 identica a quanto visto per il server, solo che in questo caso \func{msgget}
 non viene chiamata con il flag di creazione in quanto la coda deve essere
-preesistente.
+preesistente. In caso di errore (ad esempio se il server non è stato avviato)
+il programma termina immediatamente. 
 
 Una volta acquistito l'identificatore della coda il client compone il
 messaggio di richiesta (\texttt{\small 12--13}) in \var{msg\_read}, usando 1
 per il tipo ed inserendo il proprio \acr{pid} come dato da passare al server.
 Calcolata (\texttt{\small 14}) la dimensione, provvede (\texttt{\small 15}) ad
-immettere la richiesta sulla coda. A questo punto non resta che
-(\texttt{\small 16}) rileggere dalla coda la risposta del server richiedendo a
-\func{msgrcv} di selezionare i messaggi di tipo corrispondente al valore del
-\acr{pid} inviato nella richiesta. L'ultimo passo (\texttt{\small 17}) prima
-di uscire è quello di stampare a video il messaggio ricevuto.
+immettere la richiesta sulla coda. 
+
+A questo punto non resta che (\texttt{\small 16}) rileggere dalla coda la
+risposta del server richiedendo a \func{msgrcv} di selezionare i messaggi di
+tipo corrispondente al valore del \acr{pid} inviato nella richiesta. L'ultimo
+passo (\texttt{\small 17}) prima di uscire è quello di stampare a video il
+messaggio ricevuto.
  
+Benché funzionante questa archietettura risente dello stesso inconveniente
+visto anche nel caso del precedente server basato sulle fifo; se il client
+viene interrotto dopo l'invio del messaggio di richiesta e prima della lettura
+della risposta, quest'ultima resta nella coda (così come per le fifo si aveva
+il problema delle fifo che restavano nel filesystem). In questo caso però il
+problemi sono maggiori, sia perché è molto più facile esaurire la memoria
+dedicata ad una coda di messaggi che gli inode di un filesystem, sia perché,
+con il riutilizzo dei \acr{pid} da parte dei processi, un client eseguito in
+un momento successivo potrebbe ricevere un messaggio non indirizzato a
+lui.
+
+
 
 \subsection{Semafori}
 \label{sec:ipc_sysv_sem}
@@ -1913,6 +1933,7 @@ del sistema. Come vedremo esistono delle modalit
 diventa necessario indicare esplicitamente che si vuole il ripristino del
 semaforo all'uscita del processo.
 
+
 \begin{figure}[!htb]
   \footnotesize \centering
   \begin{minipage}[c]{15cm}
@@ -1933,12 +1954,14 @@ struct semid_ds
 \end{figure}
 
 A ciascun insieme di semafori è associata una struttura \var{semid\_ds},
-riportata in \figref{fig:ipc_semid_ds}. Come nel caso delle code di messaggi
-quando si crea un nuovo insieme di semafori con \func{semget} questa struttura
-viene inizializzata, in particolare il campo \var{sem\_perm} viene
-inizializzato come illustrato in \secref{sec:ipc_sysv_access_control} (si
-ricordi che in questo caso il permesso di scrittura è in realtà permesso di
-alterare il semaforo), per quanto riguarda gli altri campi invece:
+riportata in \figref{fig:ipc_semid_ds}.\footnote{non si sono riportati i campi
+  ad uso interno del kernel, che vedremo in \figref{fig:ipc_sem_schema}, che
+  dipendono dall'implementazione.} Come nel caso delle code di messaggi quando
+si crea un nuovo insieme di semafori con \func{semget} questa struttura viene
+inizializzata, in particolare il campo \var{sem\_perm} viene inizializzato
+come illustrato in \secref{sec:ipc_sysv_access_control} (si ricordi che in
+questo caso il permesso di scrittura è in realtà permesso di alterare il
+semaforo), per quanto riguarda gli altri campi invece:
 \begin{itemize*}
 \item il campo \var{sem\_nsems}, che esprime il numero di semafori
   nell'insieme, viene inizializzato al valore di \param{nsems}.
@@ -1948,6 +1971,7 @@ alterare il semaforo), per quanto riguarda gli altri campi invece:
   effettuata, viene inizializzato a zero.
 \end{itemize*}
 
+
 Ciascun semaforo dell'insieme è realizzato come una struttura di tipo
 \var{sem} che ne contiene i dati essenziali, la sua definizione\footnote{si è
   riportata la definizione originaria del kernel 1.0, che contiene la prima
@@ -1955,13 +1979,10 @@ Ciascun semaforo dell'insieme 
   ridotta ai soli due primi membri, e gli altri vengono calcolati
   dinamicamente. La si è utilizzata a scopo di esempio, perché indica tutti i
   valori associati ad un semaforo, restituiti dalle funzioni di controllo, e
-  citati dalla pagine di manuale.} è riportata in \figref{fig:ipc_sem}. Di
-norma questa struttura non è accessibile in user space, ma lo sono, in maniera
-indiretta, tramite l'uso delle funzioni di controllo, i valori in essa
-specificati, che indicano rispettivamente: il valore del semaforo, il
-\acr{pid} dell'ultimo processo che ha eseguito una operazione, il numero di
-processi in attesa che esso venga incrementato ed il numero di processi in
-attesa che esso si annulli.
+  citati dalle pagine di manuale.} è riportata in \figref{fig:ipc_sem}. Questa
+struttura, come le altre, non è accessibile in user space, ma i valori in essa
+specificati possono essere letti in maniera indiretta, attraverso l'uso delle
+funzioni di controllo. 
 
 \begin{figure}[!htb]
   \footnotesize \centering
@@ -1980,11 +2001,16 @@ struct sem {
   \label{fig:ipc_sem}
 \end{figure}
 
-Come per le code di messaggi anche per gli insiemi di semafori esistono una
-serie di limiti, i cui valori sono associati ad altrettante costanti, che si
-sono riportate in \tabref{tab:ipc_sem_limits}. Alcuni di questi limiti sono
-al solito accessibili e modificabili attraverso \func{sysctl} o scrivendo
-direttamente nel file \file{/proc/sys/kernel/sem}.
+I dati mentenuti nella struttura, ed elencati in \figref{fig:ipc_sem},
+indicano rispettivamente: 
+\begin{description*}
+\item[\var{semval}] il valore numerico del semaforo.
+\item[\var{sempid}] il \acr{pid} dell'ultimo processo che ha eseguito una
+  operazione sul semaforo
+\item[\var{semncnt}] il numero di processi in attesa che esso venga
+  incrementato.
+\item[\var{semzcnt}] il numero di processi in attesa che esso si annulli.
+\end{description*}
 
 \begin{table}[htb]
   \footnotesize
@@ -2012,6 +2038,14 @@ direttamente nel file \file{/proc/sys/kernel/sem}.
   \label{tab:ipc_sem_limits}
 \end{table}
 
+
+
+Come per le code di messaggi anche per gli insiemi di semafori esistono una
+serie di limiti, i cui valori sono associati ad altrettante costanti, che si
+sono riportate in \tabref{tab:ipc_sem_limits}. Alcuni di questi limiti sono al
+solito accessibili e modificabili attraverso \func{sysctl} o scrivendo
+direttamente nel file \file{/proc/sys/kernel/sem}.
+
 La funzione che permette di effettuare le varie operazioni di controllo sui
 semafori (fra le quali, come accennato, è impropriamente compresa anche la
 loro inizializzazione) è \func{semctl}; il suo prototipo è:
@@ -2311,16 +2345,71 @@ strutture non vengono ereditate attraverso una \func{fork} (altrimenti si
 avrebbe un doppio ripristino), mentre passano inalterate nell'esecuzione di
 una \func{exec} (altrimenti non si avrebbe ripristino).
 
-Resta comunque insoluto il problema di fondo di questo meccanismo, che non si
-adatta al concetto di operazioni atomiche su un semaforo. Infatti siccome le
-richieste di ripristino si accumulano attraverso diverse chiamate a
-\func{semop}, si pone il problema di cosa fare all'uscita del processo quando
-viene eseguito il ripristino.  Il punto è se si deve porre il processo in
-stato di \textit{sleep} se non si può accedere al semaforo o andare avanti
-come se fosse stato impostato \macro{IPC\_NOWAIT}. La scelta del kernel è
-quella di effettuare le operazioni che non prevedono un blocco del processo ed
-ignorare silenziosamente le altre.  Questo comporta che un comportamento senza
-problemi può essere garantito solo per i semafori privati.
+Tutto questo però ha un problema di fondo. Per capire di cosa si tratta
+occorre fare riferimento all'implementazione usata in Linux, che è riportata
+in maniera semplificata nello schema di \figref{fig:ipc_sem_schema}.  Si è
+presa come riferimento l'architettura usata fino al kernel 2.2.x che è più
+semplice (ed illustrata in dettaglio in \cite{tlk}); nel kernel 2.4.x la
+struttura del System V IPC è stata modificata, ma le definizioni relative a
+queste strutture restano per compatibilità.\footnote{in particolare con le
+  vecchie versioni delle librerie del C, come le libc5.}
+
+\begin{figure}[htb]
+  \centering \includegraphics[width=15cm]{img/semtruct}
+  \caption{Schema della struttura di un insieme di semafori.}
+  \label{fig:ipc_sem_schema}
+\end{figure}
+
+Alla creazione di un nuovo insieme viene allocata una nuova strutture
+\var{semid\_ds} ed il relativo vettore di strutture \var{sem}. Quando si
+richiede una operazione viene anzitutto verificato che tutte le operazioni
+possono avere successo; se una di esse comporta il blocco del processo il
+kernel creaa una struttura \var{sem\_queue} che viene aggiunta in fondo alla
+coda di attesa associata a ciascun insieme di semafori\footnote{che viene
+  referenziata tramite i campi \var{sem\_pending} e \var{sem\_pending\_last}
+  di \var{semid\_ds}.}. Nella struttura viene memorizzato il riferimento alle
+operazioni richieste (nel campo \var{sops}, che è un puntatore ad una
+struttura \var{sembuf}) e al processo corrente (nel campo \var{sleeper}) poi
+quest'ultimo viene messo stato di attesa e viene invocato lo scheduler per
+passare all'esecuzione di un altro processo.
+
+Se invece tutte le operazioni possono avere successo queste vengono eseguite
+immediatamente, dopo di che il kernel esegue una scansione della coda di
+attesa (a partire da \var{sem\_pending}) per verificare se qualcuna delle
+operazioni sospese in precedenza può essere eseguita, nel qual caso la
+struttura \var{sem\_queue} viene rimossa e lo stato del processo associato
+all'operazione (\var{sleeper}) viene riportato a \textit{running}; il tutto
+viene ripetuto fin quando non ci sono più operazioni eseguibili o si è
+svuotata la coda.
+
+Per gestire il meccanismo del ripristino tutte le volte che per un'operazione
+si è specificato il flag \macro{SEM\_UNDO} viene mantenuta per ciascun insieme
+di semafori una apposita struttura \var{sem\_undo} che contiene (nel vettore
+puntato dal campo \var{semadj}) un valore di aggiustamento per ogni semaforo
+cui viene sommato l'opposto del valore usato per l'operazione. 
+
+Queste strutture sono mantenute in due liste,\footnote{rispettivamente
+  attraverso i due campi \var{id\_next} e \var{proc\_next}.} una associata
+all'insieme di cui fa parte il semaforo, che viene usata per invalidare le
+strutture se questo viene cancellato o per azzerarle se si è eseguita una
+operazione con \func{semctl}; l'altra associata al processo che ha eseguito
+l'operazione;\footnote{attraverso il campo \var{semundo} di
+  \var{task\_struct}, come mostrato in \ref{fig:ipc_sem_schema}.} quando un
+processo termina, la lista ad esso associata viene scandita e le operazioni
+applicate al semaforo.
+
+Siccome le richieste di ripristino si accumulano attraverso diverse chiamate a
+\func{semop} per semafori diversi, si pone il problema di come viene eseguito
+il ripristino all'uscita del processo, in particolare se questo può essere
+fatto atomicamente. Il punto è cosa succede quando una delle operazioni
+previste per il ripristino non può essere eseguita immediatamente perché ad
+esempio il semaforo è occupato; in tal caso infatti, se si pone il processo in
+stato di \textit{sleep} aspettando la disponibilità del semaforo (come faceva
+l'implementazione originaria) si perde l'atomicità dell'operazione. La scelta
+fatta dal kernel è pertanto quella di effettuare subito le operazioni che non
+prevedono un blocco del processo e di ignorare silenziosamente le altre;
+questo però comporta il fatto che il ripristino non è comunque garantito in
+tutte le occasioni.
 
 
 \subsection{Memoria condivisa}