Tipi di Segnali e Azioni di Default in Linux

Nella lezione precedente abbiamo visto cosa sono i segnali in Linux e qual è il loro funzionamento a linee generali.

In questa lezione vedremo quali sono i Tipi di Segnali Standard supportati dal sistema operativo Linux e le Azioni di Default ad essi associate.

Tipi di Segnali

Nella lezione precedente abbiamo detto che i segnali standard sono identificati da un intero che va da 1 a 31 e sono associati a delle costanti simboliche definite in signal.h. Queste costanti simboliche non sono altro che macro semplici definite all'interno del file header.

Ad ogni valore numerico, per motivi di compatibilità, possono essere associati più nomi simbolici.

Di seguito è riportata la lista dei segnali standard che il sistema operativo Linux supporta:

  • SIGABRT: Segnale di Abort.

    Un processo riceve questo segnale quando invoca la funzione abort() definita in stdlib.h. Si tratta di un segnale auto-generato al momento dell'invocazione di abort. Il comportamento di default è quello di terminare il processo e generare un core dump.

  • SIGALRM: Segnale di Alarm.

    Il kernel invia questo segnale quando scatta un timer real-time impostato tramite le funzioni alarm() o setitimer(). In questo caso, quando si parla di real-time si intende che il timer è impostato per scattare ad un orario specifico.

  • SIGBUS: Segnale di Bus Error.

    Questo segnale viene inviato quando si verifica un errore di accesso a certe aree di memoria. Non indica un accesso illegale alla memoria, bensì un errore di accesso ad un'area di memoria mappata attraverso la funzione mmap(). Vedremo in dettaglio la funzione mmap() nelle prossime lezioni.

  • SIGCHLD: Segnale di Child Status Changed.

    Questo segnale viene inviato al processo padre quando uno dei suoi figli termina l'esecuzione, oppure quando l'esecuzione di uno dei suoi figli viene sospesa o ripresa.

    Un sinonimo di questo segnale è SIGCLD.

  • SIGCONT: Segnale di Continue.

    Questo segnale viene inviato ad un processo in pausa per farlo riprendere. Se il processo che riceve il segnale non era in pausa, il segnale viene ignorato. In generale un processo può gestire questo segnale per effettuare le azioni necessarie per riprendere l'esecuzione.

  • SIGFPE: Segnale di Floating Point Exception.

    Questo segnale viene inviato quando si verifica un'errore aritmetico in un calcolo in virgola mobile. Ad esempio, SIGFPE viene inviato nel caso in cui si tenti di dividere per zero. In generale, questo segnale dipende molto dall'architettura CPU su cui il programma è in esecuzione. Su di un processore x86, ad esempio, questo segnale viene sempre inviato quando si tenta di dividere per zero, a meno che non si sia impostato un flag specifico.

    Su alcuni sistemi, inoltre, viene inviato anche quando si verifica un errore aritmetico tra numeri interi, di tipo int, a dispetto del nome. Infatti, se proviamo a dividere un numero intero per zero, il kernel invia comunque questo segnale.

  • SIGHUP: Segnale di Hangup.

    Questo segnale viene inviato ad un processo quando il terminale a cui è associato viene chiuso. In generale, questo segnale viene inviato a processi che sono associati ad un terminale, come ad esempio i processi lanciati da un utente da shell.

    In generale, questo segnale viene gestito dai demoni (daemons) di sistema che devono girare in background e che non devono essere legati ad un terminale. Nelle prossime lezioni vedremo come creare un demone di sistema linux.

  • SIGILL: Segnale di Illegal Instruction.

    Questo segnale viene inviato quando un processo tenta di eseguire un'istruzione non valida. Ad esempio, questo segnale viene inviato quando si tenta di eseguire un'istruzione non valida per l'architettura CPU su cui il programma è in esecuzione.

  • SIGINT: Segnale di Interrupt.

    Questo segnale viene inviato ad un processo quando l'utente preme CTRL+C sulla tastiera. In tal caso, il kernel invia questo segnale ai processi in foreground associati al terminale.

  • SIGKILL: Segnale di Kill.

    Questo segnale viene inviato ad un processo per terminarlo immediatamente. Non può essere bloccato, ignorato o gestito con un handler. Il processo che riceve questo segnale viene terminato immediatamente.

  • SIGPIPE: Segnale di Broken Pipe.

    Questo segnale viene inviato ad un processo quando tenta di scrivere su un pipe che è stato chiuso dal processo che lo legge. In tal caso, pipe indica un generico meccanismo di comunicazione interprocesso: può rappresentare, infatti, una socket, un file, una pipe o una FIFO.

  • SIGQUIT: Segnale di Quit.

    Questo segnale viene inviato ad un processo quando l'utente preme CTRL+\ sulla tastiera. In generale, questo segnale viene inviato ad un processo per terminarlo e generare un core dump. Il core dump può essere, poi, analizzato per capire il motivo per cui il processo è terminato utilizzando un debugger come gdb.

  • SIGSEGV: Segnale di Segmentation Violation o Segmentation Fault.

    Uno dei segnali più comuni che un programmatore può incontrare. Questo segnale viene inviato quando un processo tenta di accedere ad un'area di memoria non permessa. Ad esempio, questo segnale viene inviato quando si tenta di accedere ad un'area di memoria non allocata o ad un'area di memoria che è protetta in scrittura. Tipicamente, quando si programma in C, questo segnale viene ricevuto quando si de-referenzia un puntatore non inizializzato.

  • SIGSTOP: Segnale di Stop.

    Questo segnale viene inviato ad un processo per fermarlo. A differenza di SIGKILL, il processo non viene terminato, ma messo in pausa. Il processo può essere ripreso inviando un segnale SIGCONT. Analogamente a SIGKILL, non può essere bloccato, ignorato o gestito con un handler.

  • SIGTERM: Segnale di Terminate.

    Questo è il segnale standard utilizzato per terminare un processo. Infatti, si tratta del segnale di default inviato quando si utilizza il comando kill PID senza specificare un segnale. A differenza di SIGKILL, SIGTERM può essere bloccato, ignorato o gestito con un handler.

    In generale, un'applicazione ben progettata dovrebbe gestire questo segnale per terminare in modo pulito, ad esempio salvando i dati su disco o chiudendo le connessioni di rete.

    Per tal motivo, di solito è un errore chiudere un processo esplicitando SIGKILL senza aver prima inviato SIGTERM. Molti utenti, infatti, adoperano il comando kill -KILL PID o kill -9 PID per terminare un processo, ma questo non permette al processo di terminare in modo pulito. SIGKILL deve rappresentare sempre l'ultima risorsa nel caso in cui un processo non risponda a SIGTERM.

  • SIGTSTP: Segnale di Terminal Stop.

    Questo segnale viene inviato ad un processo quando l'utente preme CTRL+Z sulla tastiera. In tal caso, il processo viene messo in pausa e può essere ripreso inviando un segnale SIGCONT.

    Vedremo nelle prossime lezioni come e quando gestire questo segnale.

  • SIGTTIN: Segnale di Terminal Input for Background Process.

    Questo segnale viene inviato ad un processo in background che tenta di leggere da un terminale. In generale, un processo in background non può leggere da un terminale. Questo segnale viene inviato per notificare al processo che non può leggere da un terminale. Il comportamento di default è quello di mettere in pausa il processo.

  • SIGTTOU: Segnale di Terminal Output for Background Process.

    Analogo a SIGTTIN, ma inviato quando un processo in background tenta di scrivere su un terminale. Anche in questo caso, il comportamento di default è quello di mettere in pausa il processo.

  • SIGUSR1 e SIGUSR2: Segnali User Defined.

    Questi due segnali sono destinati ad essere utilizzati a discrezione del programmatore. Non vengono mai generati dal kernel, ma possono essere inviati ad un processo tramite la funzione kill(). Possono essere usati per sincronizzare processi o notificare eventi definiti dall'utente.

    Originariamente, nelle versioni vecchie di UNIX, il programmatore aveva a disposizione solo questi due segnali per definire comportamenti personalizzati. Successivamente, con l'introduzione dei segnali real-time, sono stati introdotti altri segnali per permettere una maggiore flessibilità.

    Inoltre, per lungo periodo, sulle vecchie versioni di Linux, questi due segnali erano usati per gestire i thread Posix, o pthreads. Per questo, in realtà, un programmatore che voleva adoperare i thread Posix non poteva usare questi due segnali.

  • SIGPOLL o SIGIO: Segnale di I/O Event.

    Questo segnale viene inviato ad un processo quando si verifica un evento di I/O su un file descriptor. Vedremo come utilizzare questo segnale nelle lezioni sull'I/O non bloccante o I/O Asincrono.

  • SIGPROF: Segnale di Profiling Timer.

    Questo segnale viene inviato quando scatta un timer profiling impostato tramite la funzione setitimer(). Questo segnale viene utilizzato per profilare l'esecuzione di un programma. Inoltre, un timer di profiling tiene conto del tempo di utilizzo della CPU da parte di un programma sia in modalità utente che in modalità kernel.

  • SIGSYS: Segnale di Bad System Call.

    Questo segnale viene inviato quando un processo tenta di eseguire una system call non valida. Ad esempio, questo segnale viene inviato quando si tenta di eseguire una system call non supportata dal kernel.

  • SIGTRAP: Segnale di Trace/Breakpoint Trap.

    Questo segnale viene inviato quando un processo tenta di eseguire un'istruzione di trace o breakpoint. Questo segnale viene utilizzato specialmente dai debugger, come gdb, oppure dai comandi come strace o ptrace.

  • SIGURG: Segnale di Urgent Condition on Socket.

    Questo segnale viene inviato ad un processo quando si verifica una condizione urgente su una socket. Questo segnale viene inviato quando si ricevono dati fuori banda (out-of-band data) su una socket.

  • SIGVTALRM: Segnale di Virtual Timer.

    Questo segnale viene inviato quando scatta un timer virtuale impostato tramite la funzione setitimer(). Un timer virtuale è molto simile ad un timer di profilazione, che usa SIGPROF, ma tiene conto solo del tempo di utilizzo della CPU in modalità utente.

  • SIGXCPU: Segnale di CPU Time Limit Exceeded.

    Questo segnale viene inviato quando un processo supera il limite di tempo di utilizzo della CPU. Questo segnale viene inviato quando un processo supera il tempo di utilizzo della CPU impostato tramite la funzione setrlimit().

  • SIGXFSZ: Segnale di File Size Limit Exceeded.

    Questo segnale viene inviato quando un processo supera il limite di dimensione di un file. Questo segnale viene inviato quando un processo supera la dimensione massima di un file impostata tramite la funzione setrlimit().

  • SIGWINCH: Segnale di Window Size Change.

    Questo segnale viene inviato ad un processo quando la dimensione della finestra del terminale cambia. Ad esempio, negli ambienti desktop come Gnome o KDE, questo segnale viene inviato ad un processo quando si ridimensiona la finestra del terminale. Esso viene adoperato, ad esempio, da programmi come emacs o vim per conoscere la dimensione della finestra del terminale e adattare di conseguenza l'interfaccia grafica.

  • SIGPWR: Segnale di Power Failure.

    Questo segnale viene inviato ad un processo quando si verifica un'interruzione di corrente. Su molti sistemi che hanno un UPS (Uninterruptible Power Supply) oppure sistemi laptop su cui la batteria sta per esaurirsi, è configurato un demone di sistema che invia questo segnale ai processi per permettere loro di salvare i dati su disco prima che il sistema venga spento.

    Ad esempio, se questo segnale viene inviato al processo init, il sistema operativo inizia la procedura di spegnimento.

  • SIGSTKFLT: Segnale di Stack Fault on Coprocessor.

    Sebbene definito, questo segnale non viene utilizzato su Linux.

    La sua esistenza deriva dal fatto che un tempo i processori non avevano a bordo unità aritmetiche in virgola mobile (FPU), ma utilizzavano coprocessori esterni. Questo segnale veniva inviato quando si verificava un errore sul coprocessore.

    Oggigiorno, la stragrande maggioranza dei processori ha a bordo un'unità aritmetica in virgola mobile, quindi questo segnale non viene più utilizzato.

  • SIGEMT: Segnale di EMT Instruction.

    Questo segnale non è mai usato su Linux.

    Originariamente, indicava un errore hardware dipendente dall'architettura del processore. Il suo nome, infatti, deriva da Emulator Trap, un tipo di eccezione generata dai processori Digital PDP-11. Attualmente non è più utilizzato a meno dei kernel Linux che girano per architetture Sun SPARC.

  • SIGINFO: Segnale di Information Request.

    Su Linux questo segnale è sinonimo di SIGPWR.

    Su altre implementazioni UNIX, come i kernel derivati BSD (come FreeBSD), questo segnale viene inviato ad un processo per richiedere informazioni sul suo stato. Ad esempio, quando si preme CTRL+T su una shell, viene inviato questo segnale al processo in foreground per richiedere informazioni.

  • SIGLOST: Segnale di Resource Lost.

    Questo segnale non è mai usato su Linux.

    Originariamente, indicava la perdita di un'unità di risorsa. Ad esempio, su vecchi sistemi UNIX, quando si utilizzava il file system di rete NFS e si perdeva la connessione con il server NFS, veniva inviato questo segnale ai processi che avevano accesso ai file remoti.

  • SIGIOT: Segnale di I/O Trap Instruction.

    Su Linux questo segnale è sinonimo di SIGABRT.

    Originariamente, veniva usato per indicare un errore dipendente dall'hardware. Sul PDP-11, infatti, veniva inviato quando si verificava un errore di I/O. Attualmente non è più utilizzato.

  • SIGUNUSED: Segnale non utilizzato.

    Come indica il nome, questo segnale non è utilizzato su Linux.

    Tuttavia, a partire dal kernel Linux 2.4 e successivi, questo segnale è sinonimo di SIGSYS.

Tabella riassuntiva e Azioni di Default

Nella tabella che segue sono riportati tutti i segnali standard visti sopra e le azioni di default associate.

Le disposizioni di default, quando un processo riceve un segnale, sono cinque:

  1. Core Dump: Il processo termina e viene generato un core dump. Un core dump è un file che contiene lo stato della memoria del processo al momento della terminazione. Questo file può essere analizzato con un debugger come gdb per capire il motivo per cui il processo è terminato.
  2. Terminazione: Il processo termina immediatamente.
  3. Ignorato: Il segnale viene ignorato e il processo continua la sua esecuzione.
  4. Stop: Il processo viene messo in pausa e rimane in attesa di un segnale SIGCONT per riprendere l'esecuzione.
  5. Continuazione: Il processo in pausa viene ripreso.
Nome Segnale Descrizione Standardizzato Azione di Default
SIGABRT Abort del processo Core Dump
SIGALRM Real-Time Timer Terminazione
SIGBUS Bus Error Core Dump
SIGCHLD Child Status Ignorato
SIGCONT Continue Continuazione
SIGEMT Errore Hardware No Terminazione
SIGFPE Errore Floating Point Core Dump
SIGHUP Hangup Terminazione
SIGILL Istruzione Illegale Core Dump
SIGINT Interrupt da Terminale Terminazione
SIGIO Evento di I/O Terminazione
SIGKILL Terminazione Forzata Terminazione
SIGPIPE Broken Pipe Terminazione
SIGPROF Profiling Timer Terminazione
SIGPWR Power Failure No Terminazione
SIGQUIT Quit Core Dump
SIGSEGV Segmentation Fault Core Dump
SIGSTKFLT Coprocessor Stack Fault No Terminazione
SIGSTOP Stop Stop
SIGSYS Bad System Call Core Dump
SIGTERM Terminate Terminazione
SIGTRAP Trace/Breakpoint Core Dump
SIGTSTP Terminal Stop Stop
SIGTTIN Terminal Input Stop
SIGTTOU Terminal Output Stop
SIGURG Urgent Socket Condition Ignorato
SIGUSR1 User Defined 1 Terminazione
SIGUSR2 User Defined 2 Terminazione
SIGVTALRM Virtual Timer Terminazione
SIGWINCH Window Size Change No Ignorato
SIGXCPU CPU Time Limit Terminazione
SIGXFSZ File Size Limit Terminazione
Tabella 1: Segnali Standard in Linux e Azioni di Default

La colonna Standardizzato indica se il segnale è standardizzato nello standard POSIX SUSv3 (Single Unix Specification). I segnali standardizzati sono quelli per cui è garantita la presenza su tutti i sistemi UNIX e UNIX-like.

Mostrare la Descrizione di un Segnale

Ciascun segnale ha, associato ad esso, una descrizione stampabile. Per ottenere tali descrizioni possiamo usare tre funzioni definite nell'header string.h:

#define _POSIX_C_SOURCE 200809L
#include <string.h>

char *strsignal(int signum);

La funzione strsignal() restituisce una stringa che descrive il segnale signum. Questa stringa è statica e non deve essere liberata. Inoltre, sarà diversa a seconda delle impostazioni di localizzazione del sistema.

La definizione della macro _POSIX_C_SOURCE è necessaria per rendere visibile il prototipo della funzione strsignal().

Oltre a strsignal esistono altre due funzioni che, tuttavia, sono estensioni GNU: sigdescr_np() e sigabbrev_np().

#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <string.h>

const char *sigdescr_np(int signum);
const char *sigabbrev_np(int signum);

La funzione sigdescr_np() è simile a strsignal(), ma restituisce una stringa non localizzata. Questa stringa è statica e non deve essere liberata.

La funzione sigabbrev_np() restituisce l'abbreviazione del segnale signum. Anche in questo caso, la stringa restituita è statica e non deve essere liberata.

Ad esempio, volendo scrivere un semplice programma che stampa a schermo la descrizione di un segnale passato da riga di comando, possiamo scrivere il seguente codice:

/* descrizione_segnali.c */

#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Uso: %s <numero_segnale>\n", argv[0]);
        return 1;
    }

    int signum = atoi(argv[1]);

    if (signum < 1 || signum > 31) {
        fprintf(stderr, "Il segnale deve essere compreso tra 1 e 31\n");
        return 1;
    }

    printf("Descrizione del segnale %d: [SIG%s] %s\n",
            signum,
            sigabbrev_np(signum),
            strsignal(signum));

    return 0;
}

Compilando ed eseguendo il programma di sopra, otteniamo un output simile al seguente:

$ ./descrizione_segnali 3
Descrizione del segnale 3: [SIGQUIT] Quit
$ ./descrizione_segnali 9
Descrizione del segnale 9: [SIGKILL] Kill
$ ./descrizione_segnali 11
Descrizione del segnale 11: [SIGSEGV] Segmentation fault
$ ./descrizione_segnali 19
Descrizione del segnale 19: [SIGSTOP] Stop

In Sintesi

In questa lezione abbiamo visto i segnali standard supportati dal sistema operativo Linux e le azioni di default associate.

I segnali standard sono identificati da un intero che va da 1 a 31 e sono associati a delle costanti simboliche definite in signal.h.

Ogni segnale identifica un evento che può verificarsi durante l'esecuzione di un processo. Alcuni di essi sono specificati nello standard POSIX, altri sono specifici del sistema operativo Linux.

Ogni segnale ha un comportamento di default associato, tale comportamento prende il nome di gestione del segnale. Nella prossima lezione vedremo come inviare un segnale ad un processo attraverso la funzione kill().