X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=signal.tex;h=be01dc4ec8304c0b992281a202f3c5aa056a6633;hp=fec20a72c0d0465508faae3bda50031b4a894bc8;hb=f6c4cb8cabe3c33f7057e937d66c8dad6d017086;hpb=4135c36d064ec8e3f15d6ab97b8ea580fad82e87 diff --git a/signal.tex b/signal.tex index fec20a7..be01dc4 100644 --- a/signal.tex +++ b/signal.tex @@ -19,6 +19,7 @@ di generazione fino ad esaminare in dettaglio funzioni e le metodologie di gestione. + \section{Introduzione} \label{sec:sig_intro} @@ -1442,42 +1443,42 @@ void Hand_CHLD(int sig) \end{figure} Il codice del manipolatore è di lettura immediata; come buona norma di -programmazione (si ricordi quanto accennato \secref{sec:sys_errno}) si comincia -(\texttt{\small 13}) con il salvare lo stato corrente di \var{errno}, in modo -da poterla ripristinare prima del ritorno del manipolatore (\texttt{\small - 23}). In questo modo si preserva il valore della variabile visto dal corso -di esecuzione principale del processo, che sarebbe altrimenti sarebbe -sovrascritto dal valore restituito nella successiva chiamata di \func{wait}. +programmazione (si ricordi quanto accennato \secref{sec:sys_errno}) si +comincia (\texttt{\small 12-13}) con il salvare lo stato corrente di +\var{errno}, in modo da poterlo ripristinare prima del ritorno del +manipolatore (\texttt{\small 22-23}). In questo modo si preserva il valore +della variabile visto dal corso di esecuzione principale del processo, che +sarebbe altrimenti sarebbe sovrascritto dal valore restituito nella successiva +chiamata di \func{wait}. Il compito principale del manipolatore è quello di ricevere lo stato di terminazione del processo, cosa che viene eseguita nel ciclo in -(\texttt{\small 15--21}). Il ciclo è necessario a causa di una caratteristica +(\texttt{\small 15-21}). Il ciclo è necessario a causa di una caratteristica fondamentale della gestione dei segnali: abbiamo già accennato come fra la generazione di un segnale e l'esecuzione del manipolatore possa passare un -certo lasso di tempo; dato che questo lasso di tempo può dipendere da parecchi -fattori esterni, niente ci assicura che il manipolatore venga eseguito prima -della generazione di altri segnali dello stesso tipo. In questo caso -normalmente i segnali vengono ``fusi'' insieme ed al processo ne viene -recapitato soltanto uno. - -Questo può essere un caso comune proprio con \texttt{SIGCHLD}, quando molti -processi figli terminano in rapida successione. Esso comunque si presenta -tutte le volte che un segnale viene bloccato: per quanti siano i segnali -emessi durante il periodo di blocco, una volta che esso viene rimosso ne sarà -recapitato uno solo. - -Nel caso della terminazione dei processi figli, se si chiamasse \func{waitpid} -una sola volta, essa leggerebbe un solo stato di teminazione, anche se i -processi terminati sono più di uno, con relativa possibilità di avere zombie -che non vengono eliminati. - -Per questo si esegue un ciclo (\texttt{\small 15--21}) in cui ripete la -lettura fintanto che essa non restituisce un valore nullo (si veda -\secref{sec:proc_wait} per la sintassi della funzione) segno che non resta -nessun processo di cui si debba ancora ricevere lo stato di terminazione. Si -noti come la funzione viene invocata con il parametro \macro{WNOHANG} che -permette di evitare che essa si blocchi quando tutti gli stati di terminazione -sono stati ricevuti. +certo lasso di tempo e niente ci assicura che il manipolatore venga eseguito +prima della generazione di ulteriori segnali dello stesso tipo. In questo caso +normalmente i segnali segnali successivi vengono ``fusi'' col primo ed al +processo ne viene recapitato soltanto uno. + +Questo può essere un caso comune proprio con \macro{SIGCHLD}, qualora capiti +che molti processi figli terminino in rapida successione. Esso inoltre si +presenta tutte le volte che un segnale viene bloccato: per quanti siano i +segnali emessi durante il periodo di blocco, una volta che quest'ultimo sarà +rimosso sarà recapitato un solo segnale. + +Allora nel caso della terminazione dei processi figli, se si chiamasse +\func{waitpid} una sola volta, essa leggerebbe lo stato di teminazione per un +solo processo, anche se i processi terminati sono più di uno, e gli altri +resterebbero in stato di zombie per un tempo indefinito. + +Per questo occorre ripetere la chiamata di \func{waitpid} fino a che essa non +ritorni un valore nullo, segno che non resta nessun processo di cui si debba +ancora ricevere lo stato di terminazione (si veda \secref{sec:proc_wait} per +la sintassi della funzione). Si noti anche come la funzione venga invocata con +il parametro \macro{WNOHANG} che permette di evitare il suo blocco quando +tutti gli stati di terminazione sono stati ricevuti. + \section{Gestione avanzata} @@ -1490,7 +1491,9 @@ che i segnali possono generare e alla natura asincrona degli stessi. Affronteremo queste problematiche in questa sezione, partendo da un esempio che le evidenzi, per poi prendere in esame le varie funzioni che permettono di -risolvere i problemi più complessi connessi alla programmazione con i segnali. +risolvere i problemi più complessi connessi alla programmazione con i segnali, +fino a trattare le caratteristiche generali della gestione dei medesimi nella +casistica ordinaria. \subsection{Un esempio di problema} @@ -1499,8 +1502,20 @@ risolvere i problemi pi Come accennato in \secref{sec:sig_pause_sleep} è possibile implementare \func{sleep} a partire da dall'uso di \func{pause} e \func{alarm}. A prima vista questo può sembrare di implementazione immediata; ad esempio una -semplice versione di \func{sleep} potrebbe essere la seguente quella -illustrata in \ref{fig:sig_sleep_wrong}. +semplice versione di \func{sleep} potrebbe essere quella illustrata in +\figref{fig:sig_sleep_wrong}. + + +Dato che è nostra intenzione utilizzare \macro{SIGALARM} il primo passo della +nostra implementazione di sarà quello di installare il relativo manipolatore +salvando il precedente (\texttt{\small 4-7}). Si effettuerà poi una chiamata +ad \func{alarm} per specificare il tempo d'attesa per l'invio del segnale a +cui segue la chiamata a \func{pause} per fermare il programma (\texttt{\small + 8-9}) fino alla sua ricezione. Al ritorno di \func{pause}, causato dal +ritorno del manipolatore (\texttt{\small 15-23}), si ripristina il +manipolatore originario (\texttt{\small 10-11}) restituendo l'eventuale tempo +rimanente (\texttt{\small 12-13}) che potrà essere diverso da zero qualora +l'interruzione di \func{pause} venisse causata da un altro segnale. \begin{figure}[!htb] \footnotesize \centering @@ -1532,21 +1547,77 @@ void alarm_hand(int sig) { \end{lstlisting} \end{minipage} \normalsize - \caption{Una implementazione sbagliata di \func{sleep}.} + \caption{Una implementazione pericolosa di \func{sleep}.} \label{fig:sig_sleep_wrong} \end{figure} -Ma questa funzione, a parte il non gestire il caso in cui si è avuta una -precedente chiamata a \func{alarm}, presenta una pericolosa race condition. -Infatti se il processo viene interrotto fra la chiamata di \func{alarm} e -\func{pause} può capitare (nel caso il sistema sia molto carico) che -quest'ultima possa essere eseguita dopo l'arrivo di \macro{SIGALRM}. In questo -caso ci si troverebbe di fronte ad un deadlock, in cui \func{pause} non -verrebbe mai interrotta (se non in caso di un altro segnale). +Questo codice però, a parte il non gestire il caso in cui si è avuta una +precedente chiamata a \func{alarm} (che si è tralasciato per brevità), +presenta una pericolosa race condition. Infatti se il processo viene +interrotto fra la chiamata di \func{alarm} e \func{pause} può capitare (ad +esempio se il sistema è molto carico) che il tempo di attesa scada prima +dell'esecuzione quest'ultima, cosicchè essa sarebbe eseguita dopo l'arrivo di +\macro{SIGALRM}. In questo caso ci si troverebbe di fronte ad un deadlock, in +quanto \func{pause} non verrebbe mai più interrotta (se non in caso di un +altro segnale). + +Questo problema può essere risolto (ed è la modalità con cui veniva fatto in +SVr2) usando la funzione \func{longjump} (vedi \secref{sec:proc_longjmp}) per +uscire dal manipolatore; in questo modo, con una condizione sullo stato di +uscita di quest'ultima, si può evitare la chiamata a \func{pause}, usando un +codice del tipo di quello riportato in \figref{fig:sig_sleep_incomplete}. -Come abbiamo accennato in \secref{sec:proc_atom_oper} quando si ha a che fare -con i segnali +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \begin{lstlisting}{} +static jmp_buff alarm_return; +unsigned int sleep(unsigned int seconds) +{ + signandler_t prev_handler; + if ((prev_handler = signal(SIGALRM, alarm_hand)) == SIG_ERR) { + printf("Cannot set handler for alarm\n"); + exit(1); + } + if (setjmp(alarm_return) == 0) { /* if not returning from handler */ + alarm(second); /* call alarm */ + pause(); /* then wait */ + } + /* restore previous signal handler */ + signal(SIGALRM, prev_handler); + /* remove alarm, return remaining time */ + return alarm(0); +} +void alarm_hand(int sig) { + /* check if the signal is the right one */ + if (sig != SIGALRM) { /* if not exit with error */ + printf("Something wrong, handler for SIGALRM\n"); + exit(1); + } else { /* return in main after the call to pause */ + longjump(alarm_return, 1); + } +} + \end{lstlisting} + \end{minipage} + \normalsize + \caption{Una implementazione ancora malfunzionante di \func{sleep}.} + \label{fig:sig_sleep_incomplete} +\end{figure} +In questo caso il manipolatore (\texttt{\small 18-26}) non ritorna come in +\figref{fig:sig_sleep_wrong}, ma usa \func{longjmp} (\texttt{\small 24}) per +rientrare nel corpo principale del programma; dato che in questo caso il +valore di uscita di \func{setjmp} è 1 grazie alla condizione in +(\texttt{\small 9-12}) si evita comunque che \func{pause} sia chiamata a +vuoto. + +Ma anche questa implementazione comporta dei problemi; in questo caso infatti +non viene gestita correttamente l'interazione con gli altri segnali; se +infatti il segnale di allarme interrompe un altro manipolatore, in questo caso +l'esecuzione non riprenderà nel manipolatore in questione, ma nel ciclo +principale, interrompendone inopportunamente l'esecuzione. È per questo +motivo che occorrono funzioni più sofisticate della semplice \func{signal} che +permettano di gestire in maniera più completa