X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=signal.tex;h=ec3bf5547afcab767eddadcd7d14a91cb477bb67;hp=d819acd2083ca2a0cf1b1f1c8bc9360e4ef30af2;hb=056bbc90c8a0710b57fa7b13f5f0dfdad1b3ff3f;hpb=7968f2b0830a873f1bbfeedd0d863ea84a00dc6d diff --git a/signal.tex b/signal.tex index d819acd..ec3bf55 100644 --- a/signal.tex +++ b/signal.tex @@ -1,5 +1,5 @@ \chapter{I segnali} -\label{sec:signals} +\label{cha:signals} I segnali sono il primo e più semplice meccanismo di comunicazione nei confronti dei processi. Non portano con se nessuna informazione che non sia il @@ -28,57 +28,137 @@ segnale sono vari; un breve elenco di possibile cause essere eseguita. \item una richiesta dell'utente di terminare o fermare il programma. In genere si realizza attraverso un segnale mandato dalla shell in corrispondenza - della pressione di tasti come 'ctrl-c' o 'ctrl-z'. + della pressione di tasti del terminale come 'ctrl-c' o 'ctrl-z'. \item l'esecuzione di una \texttt{kill} o di una \texttt{raise} da parte del processo stesso o di un'altro (solo nel caso della \texttt{kill}). \end{itemize} Ciascuno di questi eventi (tranne gli ultimi due che sono controllati -dall'utente) comporta da parte del kernel la generazione un particolare tipo -di segnale. +dall'utente) comporta l'intervento diretto da parte del kernel che causa la +generazione un particolare tipo di segnale. \subsection{Le modalità di funzionamento} \label{sec:sig_semantics} -Quando un processo riceve un segnale il kernel esegue una apposita routine di -gestione (il cosiddetto \textit{signal handler}) che può essere specificata -dall'utente. Negli anni il comportamento del sistema in risposta ai segnali è -stato modificato in vari modi nelle differenti implementazioni di unix. -Attualmente si possono individuare due tipologie fondamentali di comportamento -dei segnali (dette semantiche) che vengono chiamate rispettivamente -\textit{reliable} e \textit{unreliable}. - -Nella semantica \textit{unreliable} la routine di gestione del segnale -specificata dall'utente non resta installata una volta chiamata; è perciò a -carico dell'utente stesso ripetere l'installazione all'interno della routine -di gestione stessa in tutti i casi in cui si vuole che il signal handler -esterno resti attivo. - -Per questo motivo è possibile una race-condition in cui un secondo segnale -arriva prima che il manipolatore abbia eseguito la re-installazione di se -stesso, nel qual caso il segnale può essere perso o causare il comportamento -originale assegnato al segnale (in genere la terminazione del processo). -Questa è la ragione per cui detti segnali sono chiamati \textit{inaffidabili}, -in quanto la ricezione del segnale e la reinstallazione del suo manipolatore -non sono operazioni atomiche. - -Nel caso di implementazione inaffidabile le chiamate di sistema non -sono fatte ripartire automaticamente quando sono interrotte da un segnale, per -questo il programma deve controllare lo stato di uscita della chiamata al -sistema e riperterla nel caso l'errore riportato da \texttt{errno} sia -\texttt{EINTR}. - -Inoltre in questo caso non esiste una modalità semplice per ottenere una -operazione di pausa atomica (cioè mandare in sleep un processo fino all'arrivo -di un segnale), dato che ci sono casi in cui un segnale può arrivare quando il -programma non è in grado di accorgersene. - -In caso di segnali \textit{reliable} invece il signal handler resta installato -quando viene chiamato e i problemi precedenti sono evitati. Inoltre alcune -chiamate di sistema possono essere fatte ripartire automaticamente e si può -ottenere un'operazione di pausa atomica (usando la funzione POSIX -\texttt{sigsuspend}). +Quando un processo riceve un segnale il kernel esegue una azione di default o +una apposita routine di gestione (il cosiddetto \textit{signal handler} o +\textsl{manipolatore}) che può essere specificata dall'utente (nel qual caso +si dice che si \textsl{intercetta} il segnale). Negli anni il comportamento +del sistema in risposta ai segnali è stato modificato in vari modi nelle +differenti implementazioni di unix. Si possono individuare due tipologie +fondamentali di comportamento dei segnali (dette semantiche) che vengono +chiamate rispettivamente \textit{reliable} e \textit{unreliable}. + +Nella semantica \textit{unreliable} (quella implementata dalle prime versioni +di unix) la routine di gestione del segnale specificata dall'utente non resta +installata una volta chiamata; è perciò a carico dell'utente stesso ripetere +l'installazione all'interno della routine di gestione stessa in tutti i casi +in cui si vuole che il signal handler esterno resti attivo. + +In questo caso è possibile una situazione in cui i segnali possono essere +perduti; si consideri il seguente segmento di codice in cui la prima +operazione del manipolatore è quella di reinstallare se stesso: +\begin{lstlisting}{showlines=false} + + int sig_handler(); /* handler function */ + ... + signal(SIGINT, sig_handler); /* establish handler */ + ... + +int sig_handler() +{ + signal(SIGINT, sig_handler); /* restablish handler */ + ... /* process signal */ +} +\end{lstlisting} +se un secondo segnale arriva prima che il manipolatore invocato dal primo +abbia eseguito la re-installazione di se stesso il segnale può essere perso o +causare il comportamento originale assegnato al segnale (in genere la +terminazione del processo). + +Questa è la ragione per cui l'implementazione dei segnali secondo questa +semantica viene chiamata \textit{inaffidabile}, in quanto la ricezione del +segnale e la reinstallazione del suo manipolatore non sono operazioni +atomiche. + +Un'altro problema è che in questa semantica è che non esiste un modo per +bloccare i segnali quando non si vuole che arrivino; i processi possono si +ignorare il segnale, ma non è possibile istruire il sistema a non fare nulla +in occasione di un segnale, pur mantenendo memoria del fatto che è avvenuto. + +Un caso classico, riportato da Stevens, in cui si incontra questo problema, è +quello in cui si usa il manipolatore per settare un flag che riporta al +processo l'occorrenza del segnale. Si consideri il seguente segmento di +codice il cui scopo sarebbe quello di fermare il processo fino all'occorrenza +di un opportuno segnale: +\begin{lstlisting}{} +int signal_flag = 0; +main () +{ + int sig_handler(); /* handler function */ + ... + signal(SIGINT, sig_handler); /* establish handler */ + ... + while(signal_flag == 0) { /* while flag is zero */ + pause(); /* go to sleep */ + } + ... +} +int sig_handler() +{ + signal(SIGINT, sig_handler); /* restablish handler */ + signal_flag = 1; /* set flag */ +} +\end{lstlisting} +l'idea è che quando il processo trova il flag a zero viene messo in sleep e +verrà risvegliato solo dalla ricezione di un segnale. Il manipolatore si +limita in questo caso a settare il flag a uno; all'uscita dal manipolatore la +chiamata a \func{pause} è interrotta ed il processo viene risvegliato e +riprende l'esecuzione all'istruzione successiva, ma essendo cambiato il flag +la condizione non è più soddisfatta e il programma prosegue. + +Il problema con l'implementazione inaffidabile è che niente ci garantisce che +il segnale arrivi fra la valutazione della condizione del \func{while} e la +chiamata a \func{pause}, nel qual caso, se il segnale non viene più generato, +il processo resterà in sleep permanentemente. + +% Un'altra caratteristica della implementazione inaffidabile è che le chiamate +% di sistema non sono fatte ripartire automaticamente quando sono interrotte da +% un segnale, per questo un programma deve controllare lo stato di uscita della +% chiamata al sistema e riperterla nel caso l'errore riportato da \texttt{errno} +% sia \texttt{EINTR}. + +Questo ci mostra ad esempio come con la semantica inaffidabile non esista una +modalità semplice per ottenere una operazione di pausa atomica (cioè mandare +in sleep un processo fino all'arrivo di un segnale). + +Nella semantica \textit{reliable} (quella utilizzata da Linux e da ogni Unix +moderno) invece il signal handler una volta installato resta attivo e non si +hanno tutti i problemi precedenti. In questa semantica i segnali vengono +\textsl{generati} dal kernel per un processo all'occorrenza dell'evento che +causa il segnale. In genere questo viene fatto dal kernel settanto un flag +nella process table del processo. + +Si dice che il segnale viene \textsl{consegnato} al processo (dall'inglese +\textit{delivered}) quando viene eseguita l'azione per esso prevista, mentre +per tutto il tempo che passa fra la generazione del segnale e la sua consegna +esso è detto \textsl{pendente}. In genere questa procedura viene effettuata +dal kernel quando, riprendendo l'esecuzione del processo in questione, verifica +la presenza del flag del segnale nella process table. + +In questa semantica un processo ha la possibilità di bloccare la consegna dei +segnali, in questo caso se l'azione per il suddetto segnale non è quella di +ignorarlo, il segnale resta \textsl{pendente} fintanto che il processo non lo +sblocca (nel qual caso viene consegnato) o setta l'azione di default per +ignorarlo. + +Si tenga presente kernel stabilisce cosa fare con un segnale che è stato +bloccato al momento della consegna, non quando viene generato; questo consente +di cambiare l'azione per il segnale prima che esso venga consegnato, e si può +usare la funzione \func{sigpending} (vedi \secref{sec:sig_sigpending}) per +determinare quali segnali sono bloccati e quali sono pendenti. + \subsubsection{Tipi di segnali} @@ -198,42 +278,42 @@ che i numeri dei segnali sono allocati progressivamente, essa corrisponde anche al successivo del valore numerico assegnato all'ultimo segnale definito. In \ntab\ si è riportato l'elenco completo dei segnali definiti in Linux (estratto dalle man page), comparati con quelli definiti in vari standard. - \begin{table}[htb] \centering - \begin{tabular}[c]{|l|c|c|c||c|l|} + \begin{tabular}[c]{|l|c|c|c||c|p{8cm}|} \hline - Segnale & POSIX.1 & SUSv2 & Linux &Azione & Descrizione \\ + Segnale & POSIX.1 & SUSv2 & Linux &Azione & Descrizione \\ \hline \hline - SIGHUP &$\bullet$&&$\bullet$& A & Hangup \\ - SIGINT &$\bullet$&&$\bullet$& A & Interrupt from keyboard \\ - SIGQUIT &$\bullet$&&$\bullet$& C & Quit from keyboard \\ - SIGILL &$\bullet$&&$\bullet$& C & Illegal Instruction \\ - SIGABRT &$\bullet$&&$\bullet$& C & Abort signal from abort(3) \\ - SIGFPE &$\bullet$&&$\bullet$& C & Floating point exception \\ - SIGKILL &$\bullet$&&$\bullet$& AEF & Kill signal \\ - SIGSEGV &$\bullet$&&$\bullet$& C & Invalid memory reference \\ - SIGPIPE &$\bullet$&&$\bullet$& A & Broken pipe \\ - SIGALRM &$\bullet$&&$\bullet$& A & Timer signal from alarm(2) \\ - SIGTERM &$\bullet$&&$\bullet$& A & Termination signal \\ - SIGUSR1 &$\bullet$&&$\bullet$& A & User-defined signal 1 \\ - SIGUSR2 &$\bullet$&&$\bullet$& A & User-defined signal 2 \\ - SIGCHLD &$\bullet$&&$\bullet$& B & Child stopped or terminated \\ - SIGCONT &$\bullet$&&$\bullet$& & Continue if stopped \\ - SIGSTOP &$\bullet$&&$\bullet$& DEF & Stop process \\ - SIGTSTP &$\bullet$&&$\bullet$& D & Stop typed at tty \\ - SIGTTIN &$\bullet$&&$\bullet$& D & tty input for background process \\ - SIGTTOU &$\bullet$&&$\bullet$& D & tty output for background process \\ - SIGBUS &&$\bullet$&$\bullet$& C & Bus error (bad memory access) \\ + SIGHUP &$\bullet$&&$\bullet$& A & Hangup sul terminale o + morte del processo di controllo \\ + SIGINT &$\bullet$&&$\bullet$& A & Interrupt da tastiera (\cmd{C-c})\\ + SIGQUIT &$\bullet$&&$\bullet$& C & Quit da tastiera (\cmd{C-y}) \\ + SIGILL &$\bullet$&&$\bullet$& C & Istruzione illegale\\ + SIGABRT &$\bullet$&&$\bullet$& C & Segnale di Abort da \func{abort} \\ + SIGFPE &$\bullet$&&$\bullet$& C & Errore aritmetico\\ + SIGKILL &$\bullet$&&$\bullet$& AEF & Segnale di terminazione forzata \\ + SIGSEGV &$\bullet$&&$\bullet$& C & Errore di accesso in memoria\\ + SIGPIPE &$\bullet$&&$\bullet$& A & Pipe spezzata\\ + SIGALRM &$\bullet$&&$\bullet$& A & Segnale del timer da \func{alarm} \\ + SIGTERM &$\bullet$&&$\bullet$& A & Segnale di terminazione \verb|C-\|\\ + SIGUSR1 &$\bullet$&&$\bullet$& A & User-defined signal 1\\ + SIGUSR2 &$\bullet$&&$\bullet$& A & User-defined signal 2\\ + SIGCHLD &$\bullet$&&$\bullet$& B & Child stopped or terminated\\ + SIGCONT &$\bullet$&&$\bullet$& & Continue if stopped\\ + SIGSTOP &$\bullet$&&$\bullet$& DEF & Stop process\\ + SIGTSTP &$\bullet$&&$\bullet$& D & Stop typed at tty \\ + SIGTTIN &$\bullet$&&$\bullet$& D & tty input for background process \\ + SIGTTOU &$\bullet$&&$\bullet$& D & tty output for background process \\ + SIGBUS &&$\bullet$&$\bullet$& C & Bus error (bad memory access) \\ SIGPOLL &&$\bullet$&$\bullet$& A & Pollable event (Sys V). Synonym of SIGIO\\ - SIGPROF &&$\bullet$&$\bullet$& A & Profiling timer expired \\ - SIGSYS &&$\bullet$&$\bullet$& C & Bad argument to routine (SVID) \\ - SIGTRAP &&$\bullet$&$\bullet$& C & Trace/breakpoint trap \\ - SIGURG &&$\bullet$&$\bullet$& B & Urgent condition on socket (4.2 BSD) \\ - SIGVTALRM &&$\bullet$&$\bullet$& A & Virtual alarm clock (4.2 BSD) \\ - SIGXCPU &&$\bullet$&$\bullet$& C & CPU time limit exceeded (4.2 BSD) \\ - SIGXFSZ &&$\bullet$&$\bullet$& C & File size limit exceeded (4.2 BSD) \\ + SIGPROF &&$\bullet$&$\bullet$& A & Profiling timer expired \\ + SIGSYS &&$\bullet$&$\bullet$& C & Bad argument to routine (SVID)\\ + SIGTRAP &&$\bullet$&$\bullet$& C & Trace/breakpoint trap \\ + SIGURG &&$\bullet$&$\bullet$& B & Urgent condition on socket (4.2 BSD)\\ + SIGVTALRM &&$\bullet$&$\bullet$& A & Virtual alarm clock (4.2 BSD) \\ + SIGXCPU &&$\bullet$&$\bullet$& C & CPU time limit exceeded (4.2 BSD) \\ + SIGXFSZ &&$\bullet$&$\bullet$& C & File size limit exceeded (4.2 BSD)\\ SIGIOT &&&$\bullet$& C & IOT trap. A synonym for SIGABRT \\ SIGEMT &&&$\bullet$& & \\ SIGSTKFLT &&&$\bullet$& A & Stack fault on coprocessor \\ @@ -249,8 +329,17 @@ In \ntab\ si \caption{Lista dei segnali in Linux} \label{tab:sig_signal_list} \end{table} -in \curtab\ si sono riportate anche le caratteristiche di ciascun segnale, -indicate con una lettera nel campo azione, la cui legenda è: +in \curtab\ si sono riportate le azioni di default di ciascun segnale +(riassunte con delle lettere, la cui legenda completa è in \ntab), quando +nessun manipolatore è installato un segnale può essere ignorato o causare la +terminazione del processo. + +In alcuni casi alla terminazione del processo è associata la creazione di un +file (posto nella directory corrente del processo e chiamato \file{core}) su +cui viene salvata un'immagine della memoria del processo (il cosiddetto +\textit{core dump}), che può essere usata da un debugger per esaminare lo +stato dello stack e delle variabili al momento della ricezione del segnale. + \begin{table}[htb] \centering \begin{tabular}[c]{c p{6cm}} @@ -267,9 +356,7 @@ indicate con una lettera nel campo azione, la cui legenda \label{tab:sig_action_leg} \end{table} la descrizione dettagliata del significato dei vari segnali, raggruppati per -tipologia, è a seguire; una descrizione dettagliata del significato delle -varie azioni si trova invece in \secred{sec:sig_handlers}. - +tipologia, è a seguire. \subsubsection{Segnali di errore di programma} \label{sec:sig_prog_error} @@ -334,7 +421,9 @@ Questi segnali sono: \item \texttt{SIGABRT} Il segnale indica che il programma stesso ha rilevato un errore che viene riportato chiamando la funzione \texttt{abort} che genera questo segnale. -\item \texttt{SIGTRAP} +\item \texttt{SIGTRAP} È il segnale generato da un'istruzione di breakpoint o + dall'attivazione del tracciamento per il processo. È usato dai programmi per + il debugging e se un programma normale non dovrebbe ricevere questo segnale. \item \texttt{SIGSYS} Sta ad indicare che si è eseguta una istruzione che richiede l'esecuzione di una system call, ma si è fornito un codice sbagliato per quest'ultima. @@ -358,11 +447,16 @@ periferica). L'azione di default di questi segnali è di terminare il processo, questi segnali sono: \begin{description} -\item \texttt{SIGTERM} -\item \texttt{SIGINT} -\item \texttt{SIGQUIT} -\item \texttt{SIGKILL} -\item \texttt{SIGHUP} +\item \macro{SIGTERM} Questo è un segnale generico usato per causare la + conclusione di un programma. Al contrario di \macro{SIGKILL} può essere + intercettato, ignorato, bloccato. In genere lo si usa per chiedere in + maniera ``educata'' ad un processo di concludersi. +\item \macro{SIGINT} E il segnale di interruzione per il programma. È quello + che viene generato di default dal comando \cmd{kill} o dall'invio sul + terminale del carattere di interrupt (generato dalla sequenza \macro{C-\\}). +\item \macro{SIGQUIT} +\item \macro{SIGKILL} +\item \macro{SIGHUP} \end{description} \subsection{I segnali di allarme} @@ -370,9 +464,8 @@ segnali sono: Questi segnali sono generati dalla scadenza di un temporizzatore. Il loro comportamento di default è quello di causare la terminazione del programma, ma -nessun default ha una utlità avrebbe una utilità particolare, in quanto l'uso -di questi segnali presuppone quasi sempre la necessità di un -manipolatore. Questi segnali sono: +con questi segnali la scelta di default è irrilevante, in quanto il loro uso +presuppone sempre la necessità di un manipolatore. Questi segnali sono: \begin{description} \item \texttt{SIGALRM} \item \texttt{SIGVTALRM} @@ -461,14 +554,56 @@ essere controllata all'interno del flusso di esecuzione di quest'ultimo, ma tutto quello che si potrà fare è di specificare (al kernel, che li genera) quale azione andrà intrapresa quando essi si verificano. -In questa sezione vedremo allora come si gestiscono i segnali, partendo dalla -descrizione di cosa fanno le azioni di default citate in precedenza, per poi -esaminare le funzioni usate per personalizzare la gestione dei segnali, -analizzando tutte le problematiche relative alla gestione di eventi asincroni -di questo tipo. - - - -\subsection{Le azioni di default} -\label{sec:sig_default_acttion} +In questa sezione vedremo allora come si gestiscono i segnali, esaminando le +funzioni che si usano per effettuare la gestione dei segnali ed analizzando le +problematiche relative alla gestione di eventi asincroni di questo tipo. + + +\subsection{La funzione \func{signal}} +\label{sec:sig_signal} + +L'interfaccia più semplice alla manipolazione dei segnali è costituita dalla +funzione \func{signal}; questa funzione è definita fin dallo standard ANSI C +che però non considera sistemi multitasking, per cui la sua definizione in +tale standard è tanto vaga da essere del tutto inutile in un sistema unix, per +questo ogni implementazione successiva ne ha modificato e ridefinito il +comportamento, pur mantenendone immutato il prototipo\footnote{in realtà + alcune vecchie implementazioni (SVR4 e 4.3+BSD) usano parametri aggiuntivi + per definire il comportamento della funzione} che è: +\begin{prototype}{signal.h} + {sighandler\_t signal(int signum, sighandler\_t handler)} + + Installa una nuova funzione di gestione (manipolatore) per il segnale + \param{signum}, usando il manipolatore \param{handler}. + + La funzione ritorna il precedente manipolatore in caso di successo o + \macro{SIG\_ERR} in caso di errore. +\end{prototype} + +In questa definizione si è usato il tipo \type{sighandler\_t} che è una +estensione GNU definita in Linux che permette di riscrivere il prototipo in +forma più leggibile dell'originario \func{void (*signal(int signum, void + (*handler)(int)))int)}, e che è sostanzialmente equivalente alla +definizione: +\begin{verbatim} +typedef void (* sighandler_t)(int) +\end{verbatim} +cioè un puntatore ad una funzione di tipo \type{void} con un parametro di tipo +\type{int}\footnote{si devono usare le parentesi intorno al nome della + funzione per via delle precedenze degli operatori del C, senza di esse si + sarebbe definita una funzione che ritorna un puntatarore a \type{void} e non + un puntatore ad una funzione \type{void}}. + +Il numero di segnale passato in \param{signum} segnale può essere indicato +direttamente con una delle costanti definite in \secref{sec:sig_standard}, il +manipolatore \param{handler} invece, oltre all'indirizzo della funzione da +chiamare all'occorrenza del segnale, può assumere anche i valori costanti +\macro{SIG\_IGN} con cui si dice ignorare il segnale e \macro{SIG\_DFL} per +installare l'azione di di default (si ricordi però che i due segnali +\macro{SIGKILL} e \macro{SIGSTOP} non possono essere ignorati né +intercettati). + + +\subsection{Funzioni rientranti e default dei segnali} +\label{sec:sig_reentrant}