X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=ipc.tex;h=da8d85a09c1e7eff365c4d02aa305fef2da2ed53;hp=4db9ea2db3dbb3bdf45d6668be6ff306c8156a54;hb=b7b0345f61ab7d26af9a165bc2d3e55cb9a3331a;hpb=ee350fdc6822ea17657d0e1598faa3e56c8d6e9b diff --git a/ipc.tex b/ipc.tex index 4db9ea2..da8d85a 100644 --- 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}