In UNIX, i segnali sono un meccanismo di comunicazione tra processi utilizzato per notificare a un processo lโoccorrenza di un evento specifico, come un errore, unโinterruzione dellโutente o unโoperazione speciale.
1 - Nomi simbolici dei segnali
Ogni segnale รจ associato univocamente a:
Un intero, a partire dallโ1.
Un nome simbolico, della forma SIG***.
Poichรฉ gli effettivi interi assegnati a ogni segnale variano a seconda delle implementazioni nei vari sistemi operativi, allโinterno dei programmi รจ meglio utilizzare direttamente i nomi simbolici.
Consiglio
ร possibile verificare la numerazione dei segnali nel proprio sistema operativo eseguendo il comando:
kill -l
2 - Tipi di segnali in UNIX
In UNIX, i segnali si dividono nelle seguenti categorie:
Trap: segnali generati da un processo e inviati al processo stesso.
Interrupt: segnali generati da un agente esterno (es. utente, altro processo) a un processo.
2.1 - Trap
In UNIX, le trap sono segnali generati da eventi prodotti da un processo e inviati al processo stesso. Alcune trap sono causate da comportamenti errati del processo stesso, e immediatamente inviate al processo che normalmente reagisce terminando.
Sono esempi di trap i tentativi di divisione per zero (SIGFPE), indirizzamento errato degli array (SIGSEGV), tentativo di eseguire istruzioni privilegiate (SIGILL), ecc.
2.2 - Interrupt
In UNIX, gli interrupt sono segnali inviati ad un processo da un agente esterno (come lโutente o un altro processo).
Esempi di interrupt inviati dallโutente sono la pressione delle combinazione di tasti Ctrl + C (SIGINT) o Ctrl + Z (SIGSTOP) durante lโesecuzione di un processo, mentre un interrupt inviato da un altro processo puรฒ essere la funzione kill().
3 - Ciclo di vita dei segnali in UNIX
In UNIX, i segnali hanno il seguente ciclo di vita:
graph LR
A[Generazione] --> B[Blocco] --> C[Consegna] --> D[Gestione] --> E[Conclusione]
B --> B
Generazione del segnale: il segnale viene creato come risultato di un evento specifico e viene posto in una associata al processo destinatario.
Blocco del segnale: dopo la sua generazione, si puรฒ impedire temporaneamente che un segnale venga consegnato a un processo. Invece di essere immediatamente gestito, il segnale rimane in uno stato di pendenza finchรฉ non viene sbloccato.
Consegna del segnale: quando il processo destinatario รจ attivo, il kernel controlla se ci sono segnali pendenti per quel processo. Se il segnale non รจ bloccato, allora viene recapitato, altrimenti rimane in stato pendente fino a quando non puรฒ essere gestito. Alcuni segnali, come SIGKILL, vengono consegnati immediatamente e non possono essere bloccati o ignorati.
Gestione del segnale: una volta consegnato, il segnale viene gestito dal processo a cui รจ stato recapitato.
Conclusione del segnale: una volta gestito, il segnale esce dalla coda dei segnali pendenti. Se il segnale ha attivato unโazione (ad esempio, la terminazione del processo), il ciclo di vita del segnale termina con lโesecuzione dellโazione predefinita o personalizzata.
4 - Generazione di un segnale
Durante la generazione del segnale, il segnale viene creato come risultato di un evento specifico e viene posto in una coda dei segnali pendenti associata al processo destinatario.
4.1 - Fonti di generazione di un segnale
La generazione di un segnale puรฒ essere causato da diversi eventi o sorgenti:
Evento hardware: lโhardware ha verificato una condizione di errore che รจ stata notificata al kernel, il quale a propria volta ha inviato un segnale corrispondente al processo in questione. Per esempio, lโesecuzione di istruzioni di linguaggio macchina malformate (SIGILL), divisioni per 0 (SIGFPE), o riferimenti a parti di memoria inaccessibili (SIGSEGV).
Evento software: eventi che non derivano direttamente dallโhardware ma sono causati da azioni compiute da processi, dal kernel o da altre operazioni software. Per esempio, lโinput รจ divenuto disponibile su un descrittore di file (SIGIO), un timer รจ arrivato a 0 (SIGALRM), il tempo di processore per il processo รจ stato superato (SIGXCPU) o un figlio del processo รจ terminato (SIGCHLD).
Azione dellโutente: lโutente ha digitato sul terminale combinazioni di tasti che generano i segnali, per esempio Ctrl + C (SIGINT) o Ctrl + Z (SIGTSTP).
4.2 - Generazione di un segnale tramite eventi software
La generazione di un segnale tramite eventi software avviene tramite azioni compiute da processi, dal kernel o da altre operazioni software. In particolare, si puรฒ ottenere usando le seguenti chiamate di sistema:
kill(): invia un segnale a un processo o a un gruppo di processi.
raise(): invia un segnale allo stesso processo chiamante.
alarm(): imposta un timer di sistema al termine del quale invia il segnale SIGALRM.
4.2.1 - Invio di un segnale tramite kill()
La funzione kill() รจ una chiamata di sistema utilizzata per inviare segnali a un processo o gruppo di processi.
Il suo prototipo รจ il seguente:
#include <signal.h>int kill(pid_t pid, int sig);
dove:
pid: processo o gruppo di processi a cui inviare il segnale, in particolare:
pid > 0: invia il segnale al processo specifico con PID uguale a pid.
pid == 0: invia il segnale a tutti i processi nel gruppo di processi del chiamante.
pid == -1 (detto broadcast signal): invia il segnale a tutti i processi che lโutente puรฒ controllare (tranne i processi di sistema e il processo chiamante stesso).
pid < -1: invia il segnale a tutti i processi nel gruppo di processi specificato dal valore assoluto pid (cioรจ -pid).
sig: segnale da inviare.
valore di ritorno int: puรฒ assumere i seguenti valori:
0: la funzione ha eseguito correttamente lโoperazione.
-1: cโรจ stato un errore durante lโesecuzione della funzione ed errno viene impostato.
Attenzione!
La funzione kill()non sempre termina un processo, ma semplicemente invia un segnale che attiverร lโazione predefinita di terminazione o, se possibile e se specificato, il relativo signal handler che compirร unโazione diversa.
4.3.1 - Invio di un segnale allo stesso processo chiamante tramite raise()
La funzione raise() รจ una chiamata di sistema utilizzata per inviare un segnale allo stesso processo chiamante. ร una forma semplificata per generare segnali allโinterno di un programma, senza dover usare kill() o identificare esplicitamente il processo.
Il suo prototipo รจ il seguente:
#include <signal.h>int raise(int sig);
dove:
sig: segnale da inviare.
valore di ritorno int:
0: la funzione ha eseguito correttamente lโoperazione.
-1: cโรจ stato un errore durante lโesecuzione della funzione ed errno viene impostato.
4.3.2 - Impostazione di un timer di sistema tramite alarm()
La funzione alarm() รจ una chiamata di sistema usata per impostare un timer di sistema al termine del quale viene inviato il segnale SIGALRM. ร utile per gestire eventi basati sul tempo, come timeout per operazioni o esecuzioni periodiche.
Il suo prototipo รจ il seguente:
#include <unistd.h>unsigned int alarm(unsigned int seconds);
dove:
seconds: numero di secondi dopo il quale il segnale SIGALRM sarร inviato. Se seconds viene impostato a 0, lโallarme giร esistente (se presente) viene annullato.
unsigned int restituito: numero di secondi rimanenti del precedente timer impostato, se esisteva. Altrimenti, restituisce 0 se non era impostato alcun timer.
4.4 - Segnali SIGUSR1 e SIGUSR2
I segnali SIGUSR1 e SIGUSR2 sono segnali definiti dallโutente. Sono segnali generici che non hanno unโazione predefinita specifica (a parte la terminazione del processo, se non gestiti) e vengono solitamente utilizzati per notifiche o comunicazioni personalizzate tra processi.
I numeri assegnati a SIGUSR1 e SIGUSR2 possono variare tra i sistemi, ma tipicamente sono rispettivamente 10 e 12.
4.4.1 - Uso tipico
Notifiche personalizzate: segnalare eventi specifici tra processi.
Debugging: utilizzati per notificare lo stato interno di unโapplicazione durante il debug.
5 - Blocco del segnale
Il blocco di un segnale รจ una tecnica usata per impedire temporaneamente che un segnale venga consegnato a un processo, memorizzandolo allโinterno della coda dei segnali pendenti.
5.1 - Uso tipico
Protezione delle sezioni critiche: durante lโesecuzione di una sezione di codice critico, un segnale puรฒ causare interruzioni indesiderate che portano a comportamenti errati, come la generazione di dati incoerenti, la corruzione di strutture dati condivise od operazioni incomplete. Bloccando i segnali, si garantisce che il codice critico venga eseguito senza interferenze.
Coordinamento in applicazioni multithreading: in programmi multithreading, รจ importante evitare che piรน thread gestiscano lo stesso segnale contemporaneamente. Bloccando i segnali, si puรฒ sincronizzare la gestione a livello di thread o di processo.
Posticipare la gestione di segnali non prioritari: un segnale potrebbe essere generato in un momento inopportuno, ad esempio mentre il processo sta elaborando unโaltra richiesta importante. Bloccarlo consente di gestirlo successivamente, quando il processo รจ pronto.
Debug e testing: durante il debugging o il testing di applicazioni, รจ utile bloccare alcuni segnali per evitare che interferiscano con lโesecuzione o per analizzare il loro comportamento una volta sbloccati.
5.2 - Maschera dei segnali
La maschera dei segnali รจ un meccanismo che consente di specificare quali segnali un processo desidera bloccare temporaneamente. ร una struttura cruciale per la gestione dei segnali, utilizzata per impedire che determinati segnali vengano consegnati al processo finchรฉ la maschera non viene modificata.
Viene rappresentata da un oggetto di tipo sigset_t, che รจ una struttura dati per definire un set di segnali. Quando un segnale รจ โmascheratoโ (cioรจ bloccato), non viene consegnato immediatamente al processo, ma viene messo nella coda dei segnali pendenti.
Le funzioni tipicamente usate per gestire una maschera dei segnali sono:
sigemptyset(): inizializza una nuova maschera vuota.
sigfillset(): inizializza una maschera contenente tutti i segnali dichiarati dal sistema.
sigismember(): verifica se un determinato segnale รจ presente nella maschera.
sigaddset(): aggiunge un determinato segnale alla maschera.
sigdelset(): rimuove un determinato segnale dalla maschera.
sigprocmask(): associa una maschera a un determinato processo.
5.2.1 - Inizializzazione di una maschera vuota tramite sigemptyset()
La funzione sigemptyset() permette di inizializzare una maschera vuota, rappresentata da un set di segnali. ร spesso usata come primo passo per configurare una maschera di segnali o per costruire un set di segnali specifici.
set: puntatore a una variabile di tipo sigset_t (struttura dati che rappresenta un insieme di segnali). Questa variabile viene inizializzata come vuota dalla funzione.
valore di ritorno int:
0: la funzione ha eseguito correttamente lโoperazione.
-1: cโรจ stato un errore durante lโesecuzione della funzione ed errno viene impostato (anche se nella pratica questa funzione non fallisce mai).
Esempio
#include <stdio.h>#include <signal.h>int main() { sigset_t set; // Inizializzazione del set vuoto if (sigemptyset(&set) == -1) { perror("Errore nell'inizializzazione del set"); return 1; } // Uso della maschera return 0;}
5.2.2 - Inizializzazione di una maschera piena tramite sigfillset()
La funzione sigfillset() permette di inizializzare una maschera contenente tutti i segnali definiti dal sistema. ร lโopposto di sigemptyset(), che invece crea un set vuoto.
Il suo prototipo รจ il seguente:
#include <signal.h>int sigfillset(sigset_t *set);
dove:
set: puntatore a una variabile di tipo sigset_t (struttura dati che rappresenta un insieme di segnali). La funzione riempie questa struttura con tutti i segnali supportati dal sistema.
valore di ritorno int:
0: la funzione ha eseguito correttamente lโoperazione.
-1: cโรจ stato un errore durante lโesecuzione della funzione ed errno viene impostato (anche se nella pratica questa funzione non fallisce mai).
Esempio
#include <stdio.h>#include <signal.h>int main() { sigset_t set; // Inizializzazione del set con tutti i segnali if (sigfillset(&set) == -1) { perror("Errore nell'inizializzazione del set"); return 1; } // Uso della maschera return 0;}
5.2.3 - Verifica di un segnale nella maschera tramite sigismember()
La funzione sigismember() verifica se un segnale specifico รจ presente in un set di segnali (di tipo sigset_t). ร comunemente utilizzata per controllare la composizione di un set prima di applicare una maschera di segnali o eseguire altre operazioni.
Il suo prototipo รจ il seguente:
#include <signal.h>int sigismember(const sigset_t *set, int signum);
dove:
set: puntatore a un oggetto di tipo sigset_t che rappresenta un set di segnali.
signum: intero che rappresenta il numero del segnale da aggiungere al set (es. SIGINT, SIGTERM).
valore di ritorno int:
1: il segnale รจ presente nel set.
0: il segnale non รจ presente nel set.
-1: cโรจ stato un errore durante lโesecuzione della funzione ed errno viene impostato con codice di errore EINVAL (che indica un argomento non valido).
Esempio
#include <stdio.h>#include <signal.h>int main() { sigset_t set; // Inizializzazione di un set vuoto if (sigemptyset(&set) == -1) { perror("Errore nell'inizializzazione del set"); return 1; } // Aggiunta di SIGINT al set if (sigaddset(&set, SIGINT) == -1) { perror("Errore nell'aggiunta di SIGINT"); return 1; } // Verifica di SIGINT nel set if (sigismember(&set, SIGINT)) { printf("SIGINT รจ presente nel set.\n"); } else { printf("SIGINT non รจ presente nel set.\n"); } // Verifica di SIGTERM nel set if (sigismember(&set, SIGTERM)) { printf("SIGTERM รจ presente nel set.\n"); } else { printf("SIGTERM non รจ presente nel set.\n"); } return 0;}
5.2.4 - Aggiunta di un segnale tramite sigaddset()
La funzione sigaddset() permette di aggiungere un segnale specifico a un set di segnali (di tipo sigset_t). Il suo prototipo รจ il seguente:
#include <signal.h>int sigaddset(sigset_t *set, int signum);
dove:
set: puntatore a un oggetto di tipo sigset_t che rappresenta un set di segnali.
signum: intero che rappresenta il numero del segnale da aggiungere al set (es. SIGINT, SIGTERM).
valore di ritorno int:
0: la funzione ha eseguito correttamente lโoperazione.
-1: cโรจ stato un errore durante lโesecuzione della funzione ed errno viene impostato con codice di errore EINVAL (che indica un argomento non valido).
Esempio
#include <stdio.h>#include <signal.h>int main() { sigset_t set; // Inizializzazione del set vuoto if (sigemptyset(&set) == -1) { perror("Errore nell'inizializzazione del set"); return 1; } // Aggiunta di SIGINT al set if (sigaddset(&set, SIGINT) == -1) { perror("Errore nell'aggiunta di SIGINT"); return 1; } // Verifica di SIGINT nel set if (sigismember(&set, SIGINT)) { printf("SIGINT รจ stato aggiunto al set.\n"); } else { printf("SIGINT non รจ nel set.\n"); } return 0;}
5.2.5 - Rimozione di un segnale tramite sigdelset()
La funzione sigdelset() permette di rimuovere un segnale specifico da un set di segnali (di tipo sigset_t). Il suo prototipo รจ il seguente:
#include <signal.h>int sigdelset(sigset_t *set, int signum);
dove:
set: puntatore a un oggetto di tipo sigset_t che rappresenta un set di segnali.
signum: intero che rappresenta il numero del segnale da rimuovere dal set.
valore di ritorno int:
0: la funzione ha eseguito correttamente lโoperazione.
-1: cโรจ stato un errore durante lโesecuzione della funzione ed errno viene impostato con codice di errore EINVAL (che indica un argomento non valido).
Esempio
#include <stdio.h>#include <signal.h>int main() { sigset_t set; // Inizializzazione del set con tutti i segnali if (sigfillset(&set) == -1) { perror("Errore nell'inizializzazione del set"); return 1; } // Rimozione di SIGINT dal set if (sigdelset(&set, SIGINT) == -1) { perror("Errore nella rimozione di SIGINT"); return 1; } // Verifica di SIGINT nel set if (sigismember(&set, SIGINT)) { printf("SIGINT รจ ancora nel set.\n"); } else { printf("SIGINT รจ stato rimosso dal set.\n"); } return 0;}
5.2.6 - Applicazione di una maschera al processo tramite sigprocmask()
La funzione sigprocmask() viene utilizzata per modificare la maschera associata a un processo.
how: intero che specifica come modificare la maschera esistente (quella correntemente applicata al processo) e puรฒ assumere i seguenti valori:
SIG_BLOCK: aggiunge i segnali specificati alla maschera giร esistente.
SIG_UNBLOCK: rimuove i segnali specificati dalla maschera esistente.
SIG_SETMASK: sostituisce la maschera esistente con quella specificata.
set: puntatore alla nuova maschera da applicare.
oldset (opzionale): puntatore alla maschera precedente (utile per poterla salvare prima di sostituirla con la nuova).
valore di ritorno int:
0: la funzione ha eseguito correttamente lโoperazione.
-1: cโรจ stato un errore durante lโesecuzione della funzione ed errno viene impostato.
Esempio
#include <stdio.h>#include <signal.h>#include <unistd.h>int main() { sigset_t set, oldset; // Inizializzazione di una maschera vuota sigemptyset(&set); sigaddset(&set, SIGINT); // Aggiunta di SIGINT alla maschera // Blocco di SIGINT if (sigprocmask(SIG_BLOCK, &set, &oldset) == -1) { perror("Errore nel blocco di SIGINT"); return 1; } printf("SIGINT bloccato. Genera segnali con Ctrl + C.\n"); sleep(10); // Ripristino della maschera originale if (sigprocmask(SIG_SETMASK, &oldset, NULL) == -1) { perror("Errore nel ripristino della maschera"); return 1; } printf("SIGINT sbloccato.\n"); sleep(10); return 0;}
5.3 - Segnali non bloccabili (SIGKILL e SIGSTOP)
Nei sistemi UNIX/Linux, esistono segnali non bloccabili, ignorabili o gestibili da un processo tramite un signal handler. Questi segnali hanno comportamenti predefiniti che il sistema operativo applica sempre, indipendentemente dalle richieste del processo. Gli unici due segnali non bloccabili sono:
SIGKILL: termina immediatamente un processo, viene usato dal sistema operativo o dagli utenti per forzare la terminazione di un processo che non risponde a segnali ordinari.
SIGSTOP: sospende immediatamente lโesecuzione di un process, viene utilizzato per mettere in pausa un processo (ad esempio con Ctrl + Z o comandi come kill -STOP).
Rendere questi segnali non bloccabili assicura che il sistema operativo mantenga un controllo minimo indispensabile sui processi, evitando situazioni in cui un processo malintenzionato o malfunzionante possa eludere la gestione dei segnali.
6 - Coda dei segnali pendenti
La coda dei segnali pendenti รจ una struttura utilizzata dal kernel per mantenere traccia dei segnali che sono stati generati ma non ancora consegnati a un processo. Questo avviene quando:
Un segnale รจ bloccato, cioรจ il processo ha indicato di non volerlo gestire immediatamente).
Il processo รจ temporaneamente incapace di riceverlo (ad esempio, รจ in uno stato di pausa o attesa).
6.1 - Caratteristiche principali
Un segnale per tipo: la coda non accumula piรน istanze dello stesso segnale. Se un segnale รจ giร pendente, ulteriori generazioni dello stesso segnale vengono ignorate.
Gestione dei segnali bloccati: i segnali bloccati vengono aggiunti alla coda invece di essere immediatamente consegnati. Quando un segnale viene sbloccato (es. tramite sigprocmask()), esso viene prelevato dalla coda e consegnato.
Prioritร dei segnali: la prioritร dei segnali non viene determinata in base al tempo di generazione, ma dipende dal tipo del segnale, assegnando ad alcuni segnali una prioritร piรน alta se necessario.
Segnali non bloccabili: alcuni segnali non possono essere messi nella coda dei segnali pendenti, come SIGKILL e SIGSTOP, che vengono gestiti immediatamente dal kernel.
Compatibilitร con i thread: nei programmi multithreading, ogni thread ha la propria coda dei segnali pendenti, ma alcuni segnali (es. segnali diretti al processo) possono essere condivisi e devono essere gestiti con coordinamento.
Non persistenza: i segnali pendenti non vengono conservati se il processo a cui si riferiscono termina prima di riceverli.
Sicurezza nelle sezioni critiche: permette di proteggere sezioni critiche di codice bloccando temporaneamente i segnali, senza rischiare di perdere notifiche importanti.
6.2 - Verifica dei segnali pendenti tramite sigpending()
La funzione sigpending() permette di controllare quali segnali sono attualmente pendenti per il processo.
Il suo prototipo รจ il seguente:
#include <signal.h>int sigpending(sigset_t *set);
dove:
set: puntatore a un oggetto sigset_t che verrร popolato con lโelenco dei segnali pendenti.
valore di ritorno int:
0: la funzione ha eseguito correttamente lโoperazione.
-1: cโรจ stato un errore durante lโesecuzione della funzione ed errno viene impostato con codice di errore EINVAL (che indica un argomento non valido).
Esempio
#include <stdio.h>#include <stdlib.h>#include <signal.h>int main() { sigset_t set; // Controlla i segnali pendenti if (sigpending(&set) == -1) { perror("Errore nel recupero dei segnali pendenti"); exit(EXIT_FAILURE); } // Verifica se SIGINT รจ pendente if (sigismember(&set, SIGINT)) { printf("SIGINT รจ pendente.\n"); } else { printf("SIGINT non รจ pendente.\n"); } return 0;}
7 - Gestione del segnale
Al momento della ricezione di un segnale, il processo puรฒ decidere come gestirlo in uno dei seguenti modi:
Eseguire lโazione predefinita: se il segnale non รจ stato configurato diversamente, il processo esegue lโazione predefinita associata al segnale.
Assegnare un signal handler: il processo puรฒ assegnare una funzione (detta signal handler) per gestire un segnale specifico tramite le funzioni signal() e sigaction() anzichรฉ eseguire lโazione predefinita.
Alla ricezione del segnale, se non รจ stato configurato diversamente, il processo esegue lโazione predefinita associata al segnale. Ogni segnale ha un proprio comportamento preimpostato, che varia in base alla sua natura e scopo. Le principali azioni predefinite sono:
Terminazione del processo: il processo termina immediatamente e il sistema libera le risorse allocate. Esempi di segnali con questa azione includono SIGTERM (terminazione ordinata) e SIGINT (interruzione da tastiera, azionabile con la combinazione di tasti Ctrl + C).
Terminazione con generazione di un core dump: il processo termina e il sistema crea un file di core dump, utile per analizzare lo stato del programma al momento del crash. Esempi di segnali con questa azione sono SIGSEGV (violazione di segmentazione) e SIGABRT (interruzione forzata).
Ignorazione del segnale: il processo non intraprende alcuna azione e continua la sua esecuzione. Esempi di segnali ignorati per impostazione predefinita includono SIGCHLD, che notifica la terminazione di un processo figlio.
Sospensione del processo: il processo viene messo in stato di stop e rimane sospeso fino a quando non riceve un segnale per riprendere. Un esempio di segnali con questa azione รจ SIGSTOP (sospensione forzata, azionabile con la combinazione di tasti Ctrl + Z), che sospende il processo senza possibilitร di blocco o gestione finchรฉ non riceve il segnale di ripresa SIGCONT.
Ripresa del processo: il processo sospeso riprende la sua esecuzione. Un esempio รจ SIGCONT, utilizzato per continuare un processo precedentemente sospeso con SIGSTOP.
7.2 - Il signal handler
Il signal handler รจ una funzione specificata dallโutente che dice a un programma come gestire la ricezione di un segnale e come agire di conseguenza. Si puรฒ impostare tramite le chiamate di sistema signal() e sigaction().
7.2.1 - Assegnazione di un signal handler tramite signal()
La funzione signal() รจ una chiamata di sistema e rappresenta la maniera piรน semplice per associare un signal handler a un segnale.
Vi sono differenze nel comportamento della funzione signal() fra le varie implementazioni di UNIX; se possibile, quindi, va evitato il suo uso in programmi complessi.
7.2.2 - Assegnazione di un signal handler tramite sigaction()
La funzione sigaction() รจ una chiamata di sistema e rappresenta una versione piรน potente e sicura per gestire i segnali. Permette un controllo piรน fine, come il blocco temporaneo dei segnali durante lโesecuzione del gestore.
act: puntatore a una struttura sigaction che descrive come deve essere gestito il segnale.
oldact: puntatore a una struttura sigaction in cui verrร memorizzato il gestore precedente (puรฒ essere NULL).
valore di ritorno int:
0: la funzione ha eseguito correttamente lโoperazione.
-1: cโรจ stato un errore durante lโesecuzione della funzione ed errno viene impostato con codice di errore EINVAL (che indica un argomento non valido).
sa_handler: funzione che definisce il comportamento da adottare allโattivazione del segnale (signal handler).
sa_mask: maschera dei segnali temporanea applicata durante lโesecuzione dellโhandler che sostituisce quella legata al processo in esecuzione. Insieme ai segnali specificati in questa maschera, viene automaticamente bloccato anche il segnale che ha attivato lโhandler.
sa_flags: flag per personalizzare il comportamento dellโhandler.
sa_restorer (obsoleto): puntatore a una funzione che il kernel potrebbe utilizzare per ripristinare il contesto del programma dopo lโesecuzione di un signal handler. Era usato in implementazioni storiche per specificare una routine di โpuliziaโ che riportava il processo al suo stato normale dopo che il signal handler terminava.
Esempio
#include <stdio.h>#include <signal.h>void handler(int sig) { printf("Ricevuto segnale %d\n", sig);}int main() { struct sigaction sa; sa.sa_handler = handler; // Imposta il gestore sigemptyset(&sa.sa_mask); // Nessun segnale รจ bloccato durante l'esecuzione sa.sa_flags = 0; // Nessun flag particolare sigaction(SIGINT, &sa, NULL); // Associa il gestore a SIGINT return 0;}
7.2.3 - Meglio usare signal() o sigaction()?
Solitamente, sigaction() รจ preferita rispetto a signal() per i seguenti motivi:
Flessibilitร : sigaction() permette di bloccare temporaneamente altri segnali durante lโesecuzione del gestore e offre maggiore controllo sui segnali.
Compatibilitร : signal() puรฒ avere un comportamento imprevisto su alcune piattaforme, mentre sigaction() รจ piรน robusta e portabile.
Controllo avanzato: con sigaction() รจ possibile specificare una maschera di segnali da bloccare durante lโesecuzione del gestore e altre opzioni, mentre signal() รจ piรน semplice ma meno potente.
7.2.4 - Interruzione del signal handler
Il signal handler, durante la sua esecuzione, puรฒ comunque essere interrotto da altri segnali (o dallo stesso segnale, nel caso la flag SA_NODEFER sia settata nella struttura sigaction in modo da evitare il blocco automatico del segnale che attiva lโhandler).
Quando lโhandler termina, la maschera dei segnali viene reimpostata al valore precedente la sua esecuzione, indipendentemente dalle possibili manipolazioni dei segnali bloccati eventualmente presenti nellโhandler.
7.2.5 - Ereditarietร del signal handler e funzioni AS-Safe
In seguito a unโoperazione di fork(), il signal handler viene ereditato dal processo figlio e le variabili globali definite nel programma sono visibili sia dalle funzioni handler che dal resto del programma. Questo puรฒ essere utile per modificare il valore di una variabile globale simulando un cambio di stato del programma che verrร poi utilizzato durante lโesecuzione โnormaleโ del codice.
Ma cosa succede quando un handler modifica il valore di una variabile globale il cui valore non dovrebbe essere modificato?
Alcune chiamate di sistema utilizzano strutture dati globali e quindi il loro utilizzo allโinterno di un handler puรฒ generare problemi. Per esempio, cosa succede se un segnale interrompe lโesecuzione di una printf(), che poi viene anche utilizzata allโinterno dellโhandler?
8 - Lista dei segnali piรน comuni
Segnale
Numero
Quando viene generato
Azione predefinita
SIGHUP (hang up)
1
Disconnessione del terminale o chiusura della shell.
Termina o ricarica il processo, spesso usato per configurazioni.
SIGINT (interrupt)
2
Interruzione da tastiera (Ctrl + C).
Termina il processo.
SIGQUIT (quit)
3
Interruzione da tastiera con dump (Ctrl+\).
Termina il processo e genera un core dump.
SIGILL (illegal instruction)
4
Istruzione illegale eseguita dal processo.
Termina il processo segnalando un errore critico.
SIGTRAP (trap)
5
Breakpoint impostato da un debugger (es. GDB) o sollevata unโeccezione.
Termina il processo con generazione di un core dump.
SIGABRT (abort)
6
Invocazione della funzione abort().
Termina il processo e genera un core dump.
SIGBUS (bus error)
7
Accesso a memoria non allineato o errore su memoria mappata.
Termina il processo con generazione di un core dump.
SIGFPE (floating point exception)
8
Errore aritmetico (es. divisione per zero).
Termina il processo e segnala un errore matematico.
Processo in background tenta di leggere dallโinput.
Sospende il processo fino a che non รจ in primo piano.
SIGTTOU (terminal output)
22
Processo in background tenta di scrivere sullโoutput.
Sospende il processo fino a che non รจ in primo piano.
SIGPOLL (pollable event) o SIGIO (input/output)
29
Evento su un descrittore di file monitorato.
Notifica un input/output disponibile (POSIX).
Fonti
๐ซ Lezioni e slide del Prof. Schifanella Claudio del corso di Laboratorio di Sistemi Operativi (canale B, turno T4), Corso di Laurea in Informatica presso lโUniversitร di Torino, A.A. 2024-25: