Gestione dei segnali
•I segnali sono interrupt software.I segnali
– interrompono i processi qualunque cosa stiano
essi facendo al momento della generazione
dell’evento.
– forniscono un modo per notificare e gestire
eventi asincroni che si verificano in maniera
imprevedibile.
– evitano di dover fare polling per verificare la
generazione di un evento.
– sono generati per (o spediti verso) un processo
•I processi possono specificare quali azioni
intraprendere per gestire il segnale
Gestione dei segnali
•Ogni segnale ha un nome: tale nome inizia con i tre
caratteri SIG: ad esempio SIGABRT è il segnale generato
quando un processo chiama la funzione abort. A tali nomi
sono associati numeri positivi interi definiti nell’header
signal.h.
•Si possono elencare tutti i segnali supportati dal sistema
con il comando kill -l.
•La pressione dei tasti Ctrl-C causa l’invio di un segnale
SIGINT al processo in esecuzione e ne causa l’immediata
terminazione.
•La pressione dei tasti Ctrl-Z causa l’invio di un segnale
SIGTSTP al processo in esecuzione e ne causa la
sospensione.
Condizioni di generazione dei
segnali
• I segnali generati da terminale si verificano quando
l’utente preme certi tasti . Es. il tasto canc causa la
generazione del segnale di interruzione (SIGINT).
• Possono generare segnali eccezioni hardware, come ad
esempio una divisione per 0 o riferimenti non validi di
memoria. Tali situazioni sono controllate dell’hardware e
notificate al kernel che genera il segnale opportuno. Es.
SIGSEGV, che viene generato quando un processo, fa un
riferimento non valido in memoria.
• La funzione kill consente ad un processo di inviare
segnali ad un altro processo.
• Il comando kill consente agli utenti di inviare un segnale
ai processi.
• Il segnale SIGALRM è generato quando scade un
intervallo fissato da un processo.
Comandi per la generazione dei
segnali
•Il comando kill accetta due parametri: il nome ( o
numero ) di un segnale e l’ID di un processo.
kill -<signal> <PID>
Per inviare il segnale INT al processo con PID 6543 si
deve eseguire
kill -INT 6543
che ha lo stesso effetto che premere Ctrl-C mentre il
processo è in esecuzione.
•Il comando fg fa riprendere l’esecuzione ad un
processo sospeso inviando il segnale SIGCONT.
Come gestire i segnali
Ci sono tre differenti comportamenti che possono essere
adottati per gestire i segnali:
•Ignorare il segnale: questo può andar bene per la
maggior parte dei segnali, ma due di questi non possono
essere ignorati: SIGKILL e SIGSTOP, che consentono al
superuser di aver un modo sicuro di uccidere o fermare
un processo. Inoltre, ignorare alcuni tipi di segnali( ad
esempio una divisione per 0) può portare il processo in
uno stato indefinito.
•Gestire il segnale, cioè specificare una funzione da
chiamare nel caso che un determinato segnale si
verifichi. Ad esempio chiamare la funzione waitpid
quando si riceve il segnale SIGCHLD di terminazione di
un figlio.
•Applicare l’azione di default associata al segnale (
che porta a volte alla generazione del file core).
I segnali SIGKILL e SIGSTOP
• Il SIGKILL viene generato da consolle con il
comando kill -9 per terminare un processo
• Per eseguire lo shutdown del sistema, tipicamente
– viene inviato a tutti i processi prima un segnale
SIGTERM che avvisa tutti i processi che devono
terminare le operazioni prima possibile. Questo
segnale non forza la chiusura dei processi.
– dopo aver aspettato un attimo la conclusione
delle operazioni di terminazione, il sistema forza
la terminazione dei processi rimasti inviando un
segnale SIGKILL.
• Il SIGSTOP forza la sospensione di un processo. Si
può utilizzare il segnale SIGCONT per indicare al
processo di riprendere la sua esecuzione.
Esempi di segnali
• SIGABRT: generato chiamando la funzione abort. Il
processo termina in maniera anomala. L’handler di default
per questo segnale fa il dump dell’immagine della
memoria del processo nella directory in un file chiamato
“core” e poi esce.
• SIGALRM: generato quando scade un intervallo di
tempo fissato.
• SIGCHLD: quando un processo termina questo segnale
viene spedito al padre del processo. Per default questo
segnale è ignorato.
• SIGKILL viene utilizzato per terminare senza problemi
un processo.
• SIGQUIT è generato quando si digitano i tasti di uscita.
La funzione signal
La funzione signal fornisce un’interfaccia per definire le
funzioni da utilizzare per gestire i segnali.
void (*signal(int signo, void (*func)(int)))(int)
•L’argomento signo è il nome del segnale da gestire.
•Il valore di func può essere
–la costante SIG_IGN ( per ignorare)
–la costante SIG_DFL ( per eseguire l’azione di default)
–l’indirizzo della funzione per la gestione del segnale
La funzione signal
• La funzione richiede due argomenti e ritorna il
puntatore a una funzione che restituisce un tipo void.
• Il primo argomento è un intero.
• Il secondo argomento è il puntatore ad una funzione
che prende come argomento un intero a non ritorna
niente.
• Il valore di ritorno della funzione è un puntatore alla
funzione precedentemente definita per gestire il segnale.
• In alcuni sistemi, quando viene terminata la gestione di
un segnale, il sistema automaticamente ridefinisce il
gestore per il segnale appena intercettato a quello di
default.
Esempio sulla funzione signal
#include
<signal.h>
static void sig_usr(int);
/* un gestore per entrambi i
segnali */
int main(void){
if (signal(SIGUSR1, sig_usr) == SIG_ERR)
err_sys("can't catch SIGUSR1");
if (signal(SIGUSR2, sig_usr) == SIG_ERR)
err_sys("can't catch SIGUSR2");
for ( ; ; )pause(); }
static void sig_usr(int signo) {
if (signo == SIGUSR1) printf("received SIGUSR1\n");
else if (signo == SIGUSR2)printf("received SIGUSR2\n");
else err_dump("received signal %d\n", signo);
return;}
Output dell’esempio precedente
$ a.out &
[1] 4720
$kill -USR1 4720
received SIGUSR1
$kill -USR2 4720
received SIGUSR2
$kill 4720
[1] + Terminated
•Il processo termina quando si invia il segnale SIGTERM
che causa di default la terminazione del processo.
I segnali e le funzioni fork e
exec
• Quando un programma è eseguito con una exec lo stato
di tutti i segnali è quello di default o di ignore
(normalmente di default).
• I segnali gestiti dal processo che ha chiamato la exec
non sono più gestiti dal nuovo programma a meno che
questo non ridefinisca dei suoi nuovi gestori dei segnali.
• Quando un processo esegue una fork il figlio eredita
tutta la gestione dei segnali dal padre.
• Una shell automaticamente considera i segnali di
interruzione e uscita come da ignorare per tutti i processi
che sono eseguiti in background.
• Nota: la funzione signal non può informare della
gestione corrente di un segnale senza cambiarla.
Blocco dei segnali
• Un segnale è inviato ad un processo per segnale un
evento. Durante l’intervallo fra la generazione del segnale
e i suo invio il segnale si dice pendente.
• Un processo può bloccare la comunicazione del segnale.
• Se un segnale generato da un processo è bloccato, e
l’azione associata ad esso è quella di default o di essere
gestito da una funzione, il segnale rimane pendente per il
processo finchè
– il processo sblocca il segnale
– il processo decide di ignorare il segnale
• La funzione sigpending può essere chiamata da un
processo per determinare quali segnali sono bloccati o
pendenti.
Blocco dei segnali
• Cosa succede se un segnale bloccato viene
generato più volte? Questo può dipendere
dall’implementazione del meccanismo, in generale i
segnali non vengono accodati ma generati al
massimo una volta.
• Ogni processo ha una maschera che specifica quali
segnali devono essere bloccati e non inviati
• I processi possono leggere e modificare questa
maschera con la funzione sigprocmask
Invio del segnali: le funzioni kill
e raise
•La funzione kill spedisce un segnale ad un processo
o ad un gruppo di processi.
•La funzione raise consente ad un processo di
spedire un segnale a se stesso
int kill(pid_t pid, int signo)
int raise(int signo)
Ci sono 3 condizioni associate al parametro pid della
funzione kill
•pid>0: il segnale e spedito al processo il cui ID è
PID
•pid==0: il segnale è spedito a tutti i processi il cui
group ID è uguale al group ID del mittente
•pid<0 : il segnale è spedito ai processi che hanno
group ID uguale al valore assoluto uguale a pid
Invio di segnali
• Un processo deve avere dei permessi per poter
inviare dei segnali ad un altro processo. In generale
per poter inviare un segnale lo user ID del processo
che invia il segnale deve essere uguale allo user ID
del processo che lo riceve.
• Il superuser può inviare segnali a ogni processo.
• Se la funzione kill invia un messaggio al processo
che l’ha eseguita, e il segnale non è bloccato, il
segnale viene recapitato al processo prima che la
funzione kill ritorni.
Le funzioni alarm e pause
•La funzione alarm consente di fissare un timer per
misurare intervalli di tempo e segnalarne la scadenza.
•Quando scade un intervallo viene generato il segnale
SIGALRM.
•Se questo segnale viene ignorato o non gestito, l’azione di
default prevede la terminazione del processo
unsigned int alarm(unsigned int seconds)
il parametro di questa funzione specifica il numero di
secondi dopo il quale viene generato il segnale. In realtà, a
causa dei tempi necessari per le operazioni di scheduling, il
gestore del segnale può essere attivato con un po’ di
ritardo
•Per ogni processo può essere fissato al più un intervallo di
tempo.
Le funzioni alarm e pause
• Successive chiamata alla funzione alarm dopo la
registrazione di un intervallo non scaduto, sovrascrivono il
vecchio intervallo. In questo caso la funzione alarm
restituisce il numero di secondi mancanti alla scadenza
precedentemente fissata.
• Se il parametro passato è 0, viene annullata la
scadenza fissata, se esiste.
-------------------------------------------------------------• La funzione pause sospende il processo che la invoca
fino a quando non viene ricevuto un segnale.
int pause(void)
• La funzione pause ritorna solo se viene eseguito un
gestore di segnale e il gestore ritorna senza fare exit. Il
valore ritornato in questo caso è -1
Esempio uso di alarm
• Un uso comune della alarm è quello di fissare un limite
di tempo nell’esecuzione di operazioni bloccanti. Ad
esempio, in fase di lettura da particolari device che
possono bloccarsi, si vuole interrompere l’esecuzione della
read dopo un certo periodo di tempo
• Nell’esempio seguente si vedrà una prima
implementazione di un programma che inizia una lettura
da device e fissa un timeout su questa operazione
utilizzando la funzione alarm. Alla scadenza del timeout
l’operazione di lettura, se non terminata, deve essere
interrotta e il programma finire.
Esempio uso di alarm
#include
#include
static void
<signal.h>
"ourhdr.h"
sig_alrm(int);
int main(void){
int n; char line[MAXLINE];
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
err_sys("signal(SIGALRM) error");
alarm(10);
if ( (n = read(STDIN_FILENO, line, MAXLINE)) < 0)
err_sys("read error");
alarm(0);
write(STDOUT_FILENO, line, n);
exit(0);
}
static void sig_alrm(int signo){
return; /* semplicemente ritorna per interrompere la read
*/}