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 - 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 static voidsig_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 -USR received SIGUSR1 $kill -USR 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"ourhdr.h" static voidsig_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 */}