C/Appendice/Librerie standard/signal.h

Indice del libro

Il file signal.h della libreria standard definisce principalmente delle funzioni per la gestione dei segnali che riguardano il programma. Assieme alle funzioni definisce anche delle macro-variabili per classificare i segnali e per fare riferimento a delle funzioni predefinite, destinate astrattamente al trattamento dei segnali.

Dichiarazione modifica

Per la gestione dei segnali ci sono due funzioni che vengono dichiarate nel file signal.h: signal() e raise(). La funzione raise() serve ad azionare un segnale specificato, come dire che serve ad attivare manualmente un allarme interno al programma, specificato da un numero particolare che ne definisce il tipo. Il programma contiene sempre una procedura predefinita che stabilisce ciò che deve essere fatto in presenza di un certo allarme, ma il programmatore può ridefinire la procedura attraverso l'uso della funzione signal(), con la quale si associa l'avvio di una funzione particolare in presenza di un certo segnale. Il modello sintattico seguente rappresenta, in modo estremamente semplificato, l'uso della funzione signal():

signal (n_segnale, funzione_da_associare)

Logicamente la funzione che si associa a un certo numero di segnale viene indicata negli argomenti della chiamata come puntatore a funzione. La funzione che viene passata come argomento è un gestore di segnale e deve avere una certa forma:

void gestore (n_segnale)

In pratica, quando viene creata l'associazione tra segnale e funzione che deve gestirlo, la funzione in questione deve avere un parametro tale da poter rappresentare il numero del segnale che la riguarda e non restituisce alcun valore (pertanto è di tipo void).

Avendo determinato questo, il modello della funzione signal() può essere precisato un po' di più:

signal (n_segnale, void (*gestore)(int))

Ciò significa che il secondo argomento della funzione signal() è un puntatore a una funzione (gestore()) con un parametro di tipo int, la quale non restituisce alcunché (void).

Ma non è ancora stato specificato cosa deve restituire la funzione signal(): un puntatore a una funzione che ha un parametro di tipo int e che a sua volta non restituisce alcunché. In pratica, signal() deve restituire il puntatore a una funzione che ha le stesse caratteristiche di quella del proprio secondo parametro. A questo punto, si arriva al prototipo completo, ma molto difficile da interpretare a prima vista:

void (*signal(int n_segnale, void gestore (int)))(int)

Per ovviare a questo problema di comprensibilità, anche se lo standard non lo prescrive, di norma, nel file signal.h si dichiara un tipo speciale, in qualità di puntatore a funzione con le caratteristiche del gestore di segnale:

typedef void (*SIGPTR) (int);

Così facendo, la funzione signal() può essere dichiarata in modo più gradevole:

SIGPTR signal (n_segnale, SIGPTR gestore);

Tipo sig_atomic_t modifica

il file signal.h definisce il tipo sig_atomic_t, il cui uso non viene precisato dai documenti ufficiali. Si chiarisce solo che deve trattarsi di un valore intero, possibilmente di tipo volatile, a cui si possa accedere attraverso una sola istruzione elementare del linguaggio macchina (in modo tale che la lettura o la modifica del suo contenuto non possa essere sospesa a metà da un'interruzione di qualunque genere).

typedef int sig_atomic_t;

Nell'esempio, il tipo sig_atomic_t viene dichiarato come equivalente al tipo int, supponendo che l'accesso alla memoria per un tipo intero normale corrisponda a un'operazione «atomica» nel linguaggio macchina. A ogni modo, il tipo a cui corrisponde sig_atomic_t può dipendere da altri fattori, mentre l'unico vincolo nel rango è quello di poter contenere i valori rappresentati dalle macro-variabili SIG..., che individuano mnemonicamente i segnali.

Il programmatore che deve memorizzare un segnale in una variabile, potrebbe usare per questo il tipo sig_atomic_t.

Denominazione dei segnali modifica

Un gruppo di macro-variabili definisce l'elenco dei segnali gestibili. Lo standard del linguaggio ne prescrive solo una quantità minima, mentre il sistema operativo può richiederne degli altri. Teoricamente l'associazione del numero al nome simbolico del segnale è libera, ma in pratica la concordanza con altri standard prescrive il rispetto di un minimo di uniformità.

Denominazione dei segnali indispensabili al linguaggio
Denominazione Significato mnemonico Descrizione
SIGABRT abort Deriva da una terminazione anomala che può essere causata espressamente dall'uso della funzione abort().
SIGFPE floating point exception Viene provocato da un'operazione aritmetica errata, come la divisione per zero o uno straripamento del risultato.
SIGILL illegal Istruzione «illegale».
SIGINT interrupt Deriva dalla ricezione di una richiesta interattiva di attenzione, quale può essere quella di un'interruzione.
SIGSEGV segmentation violation Deriva da un accesso alla memoria non valido, per esempio oltre i limiti fissati.
SIGTERM termination Indica la ricezione di una richiesta di terminazione del funzionamento del programma.

Macro-variabili per la gestione predefinita dei segnali. modifica

Denominazione Significato mnemonico Descrizione
SIG_DFL default Indica simbolicamente che l'azione da compiere alla ricezione del segnale deve essere quella predefinita.
SIG_IGN ignore Indica simbolicamente che alla ricezione del segnale si procede come se nulla fosse accaduto.
SIG_ERR error Rappresenta un risultato errato nell'uso della funzione signal().

Uso delle funzioni modifica

La funzione signal() viene usata per associare un «gestore di segnale», costituito dal puntatore a una funzione, a un certo segnale; tutto questo allo scopo di attivare automaticamente quella tale funzione al verificarsi di un certo evento che si manifesta tramite un certo segnale.

La funzione signal() restituisce un puntatore alla funzione che precedentemente si doveva occupare di quel segnale. Se invece l'operazione fallisce, signal() esprime questo errore restituendo il valore SIG_ERR, spiegando così il motivo per cui questo debba avere l'apparenza di un puntatore a funzione.

Per la stessa ragione per cui esiste SIG_ERR, le macro-variabili SIG_DFL e SIG_IGN vanno usate come gestori di segnali, rispettivamente, per ottenere il comportamento predefinito o per far sì che i segnali siano ignorati semplicemente.

In linea di principio si può ritenere che nel proprio programma esista una serie iniziale di dichiarazioni implicite per cui si associano tutti i segnali gestibili a SIG_DFL:

...
signal (segnale, SIG_DFL);
...

L'altra funzione da considerare è raise(), con la quale si attiva volontariamente un segnale, dal quale poi dovrebbero o potrebbero sortire delle conseguenze, come stabilito in una fase precedente attraverso signal(). La funzione raise() è molto semplice:

int raise (int sig);

La funzione richiede come argomento il numero del segnale da attivare e restituisce un valore pari a zero in caso di successo, altrimenti restituisce un valore diverso da zero. Naturalmente, a seconda dell'azione che viene intrapresa all'interno del programma, a seguito della ricezione del segnale, può darsi che dopo questa funzione non venga eseguito altro, pertanto non è detto che possa essere letto il valore che la funzione potrebbe restituire.

Esempio modifica

Esempio

Viene proposto un esempio che serve a dimostrare il meccanismo di provocazione e intercettazione dei segnali:

#include <stdio.h>
#include <signal.h>

void sig_generic_handler (int sig)
{
    printf ("Ho intercettato il segnale n. %d.\n", sig);
}

void sigfpe_handler (int sig)
{
    printf ("Attenzione: ho intercettato il segnale SIGFPE (%d)\n", sig);
    printf ("            e devo concludere il funzionamento!\n", sig);
    exit (sig);
}

void sigterm_handler (int sig)
{
    printf ("Attenzione: ho intercettato il segnale SIGTERM (%d),\n", sig);
    printf ("            però non intendo rispettarlo.\n");
}

void sigint_handler (int sig)
{
    printf ("Attenzione: ho intercettato il segnale SIGINT (%d),\n", sig);
    printf ("            però non intendo rispettarlo.\n");
}

int main (void)
{
    signal (SIGFPE,  sigfpe_handler);
    signal (SIGTERM, sigterm_handler);
    signal (SIGINT,  sigint_handler);
    signal (SIGILL,  sig_generic_handler);
    signal (SIGSEGV, sig_generic_handler);

    int c;
    int x;

    printf ("[0][Invio] divisione per zero\n");
    printf ("[c][Invio] provoca un segnale SIGINT\n");
    printf ("[t][Invio] provoca un segnale SIGTERM\n");
    printf ("[q][Invio] conclude il funzionamento\n");
    while (1)
      {
        c = getchar();
        if (c == '0')
          {
            printf ("Sto per eseguire una divisione per zero:\n");
            x = x / 0;
          }
        else if (c == 'c')
          {
            raise (SIGINT);
          }
        else if (c == 't')
          {
            raise (SIGTERM);
          }
        else if (c == 'q')
          {
            return 0;
          }
      }
    return 0;
}

All'inizio del programma vengono definite delle funzioni per il trattamento delle situazioni che hanno provocato un certo segnale. Nella funzione main(), prima di ogni altra cosa, si associano tali funzioni ai segnali principali, quindi si passa a un ciclo senza fine, nel quale possono essere provocati dei segnali premendo un certo tasto, come suggerito da un breve menù. Per esempio è possibile provocare la condizione che si verifica tentando di dividere un numero per zero.

La divisione per zero fa scattare il segnale SIGFPE che viene intercettato dalla funzione sigfpe_handler(), la quale però non può far molto e così conclude anche il funzionamento del programma.