[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico] [home] [volume] [parte]


Capitolo 603.   C: «signal.h»

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.

Dal punto di vista del programmatore, l'uso delle funzioni di questo file di intestazione può essere abbastanza semplice, ma la comprensione di come siano organizzate nel file signal.h diventa abbastanza difficile.

In questo capitolo vengono proposti due modi alternativi di scrivere il file signal.h che dovrebbero essere disponibili presso <allegati/a2/c/include/signal.h> e <allegati/a2/c/include/signal-bis.h>.

603.1   Dichiarazione contorta

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 (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 (*sighandler_t) (int);
...

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

sighandler_t signal (n_segnale, sighandler_t gestore);

603.2   Tipo speciale

A parte il caso di sighandler_t che non fa parte dello standard del linguaggio, il file include.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.

603.3   Denominazione dei segnali

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à.

#define SIGINT           2
#define SIGILL           4
#define SIGABRT          6
#define SIGFPE           8
#define SIGSEGV         11
#define SIGTERM         15

Tabella 603.4. 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.

603.4   Gestori fittizi di segnali

Lo standard prescrive di definire tre macro-variabili che devono espandersi in un puntatore a quel tipo di funzione che deve essere in grado di gestire le azioni da compiere in relazione alla ricezione di un certo segnale. Tuttavia, questo puntatore non deve essere rivolto a una funzione vera, ma averne solo la forma. In pratica, si usano dei valori interi con un valore assoluto molto piccolo e si esegue un cast per trasformarli in puntatori a funzione, come già accennato.

Per ottenere questo risultato, si possono dichiarare le macro-variabili in due modi equivalenti, con la differenza che il secondo è probabilmente più difficile da interpretare:

typedef void (*sighandler_t) (int); // Il tipo «sighandler_t» è
                                    // un puntatore a funzione
                                    // per la gestione dei segnali
                                    // con parametro «int» che
                                    // restituisce «void».
//
// Funzioni non dichiarabili
//
#define SIG_ERR ((sighandler_t) -1) // Trasforma un numero intero 
#define SIG_DFL ((sighandler_t) 0)  // in un tipo «sighandler_t»,
#define SIG_IGN ((sighandler_t) 1)  // ovvero un puntatore a funzione
                                    // che però non esiste realmente.
//
// Funzioni non dichiarabili
//
#define SIG_ERR ((void (*) (int)) -1) // Trasforma un numero intero 
#define SIG_DFL ((void (*) (int)) 0)  // in un puntatore a una funzione
#define SIG_IGN ((void (*) (int)) 1)  // che ha un parametro «int» e
                                      // restituisce «void».

Lo standard sottolinea il fatto che il numero trasformato in puntatore non deve poter corrispondere all'indirizzo di alcuna funzione reale; pertanto i valori usati possono essere solo molto bassi (in termini di valore assoluto), contando sul fatto che a tali indirizzi non ci possano essere funzioni reali. In pratica, non deve succedere che venga dichiarata una funzione per la gestione di un segnale che finisca per avere proprio tali indirizzi, perché se così fosse, non verrebbe avviata, ma al suo posto verrebbe considerata l'azione che una di queste macro-variabili simboleggia.

Tabella 603.7. Macro-variabili per la gestione predefinita dei segnali.

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().

603.5   Funzioni

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);
...

In base al fatto che sia stata dichiarato o meno il tipo sighandler_t, la funzione potrebbe avere i prototipi seguenti:

sighandler_t signal (int sig, sighandler_t handler);
void (*signal (int sig, void (*handler) (int))) (int);

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.

603.6   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:

[0][Invio] divisione per zero
[c][Invio] provoca un segnale SIGINT
[t][Invio] provoca un segnale SIGTERM
[q][Invio] conclude il funzionamento

0[Invio]

Sto per eseguire una divisione per zero:
Attenzione: ho intercettato il segnale SIGFPE (8)
            e devo concludere il funzionamento!

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.

Attraverso il menù è possibile provocare anche un segnale SIGINT e un segnale SIGTERM, ma per questo è più interessante provare con i mezzi che dovrebbe offrire il sistema operativo:

[0][Invio] divisione per zero
[c][Invio] provoca un segnale SIGINT
[t][Invio] provoca un segnale SIGTERM
[q][Invio] conclude il funzionamento

[Ctrl c][Invio]

Attenzione: ho intercettato il segnale SIGINT (2),
            però non intendo rispettarlo.

Utilizzando un sistema operativo Unix o simile, da un altro terminale, o da un'altra console, è possibile inviare un segnale specifico al programma:

kill n_processo[Invio]

Attenzione: ho intercettato il segnale SIGTERM (15),
            però non intendo rispettarlo.

kill -s 4 n_processo[Invio]

Ho intercettato il segnale n. 4.

kill -s 11 n_processo[Invio]

Ho intercettato il segnale n. 11.

Secondo l'esempio, i segnali 4 e 11 sono, rispettivamente, SIGILL e SIGSEGV.

[q][Invio]

603.7   Riferimenti


Appunti di informatica libera 2008 --- Copyright © 2000-2008 Daniele Giacomini -- <appunti2 (ad) gmail·com>


Dovrebbe essere possibile fare riferimento a questa pagina anche con il nome c_171_signal_h_187.htm

[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico] [home]

Valid ISO-HTML!

CSS validator!

Gjlg Metamotore e Web Directory