Vai alla Home Page About me Courseware Federica Living Library Federica Federica Podstudio Virtual Campus 3D Le Miniguide all'orientamento Gli eBook di Federica La Corte in Rete
 
 
Il Corso Le lezioni del Corso La Cattedra
 
Materiali di approfondimento Risorse Web Il Podcast di questa lezione

Clemente Galdi » 18.I Segnali


Introduzione ai segnali

Un “segnale” è un “interrupt software”. La terminologia corretta è “exception” mentre “interrupt” è usata solo per gli interrupt “hardware”.
Consente la comunicazione asincrona tra processi e/o tra sistema operativo e processo.
Ogni segnale è idenfiticato da una stringa o nome:

  • Tutti i nomi cominciano per “SIG”
  • Definiti in
  • Associati ad interi positivi

Il numero di segnali disponibili dipende dal sistema operativo.
I sistemi Linux prevedono 64 segnali diversi:

  • 0: Utilizzato per controllare se si hanno I permessi sufficienti per inviare un segnale ad un processo;
  • 1-31: detti “normali”;
  • 32-63: detti “realt-time”.

Introduzione ai segnali (segue)

Un segnale viene generato da un processo (o dal sistema operativo) per un altro processo.
Un segnale viene consegnato ad un processo quando l’azione associata al proceso viene eseguita.
Un segnale che è stato generato ma non ancora consegnato è detto pending.
Un processo può bloccare la consegna di uno o più segnali.

  • Se il processo A blocca il segnale X allora:
    • Il processo B può inviare il segnale X ad A;
    • Il segnale X rimane pending finchè A non lo sblocca.
  • La system call sigpending() ristorna l’insieme dei segnali pending.
  • Ogni processo ha una maschera di segnali bloccati modificabile tramite la system call sigpending().

Generazione di segnali

I segnali possono essere generati a seguito di diversi eventi:

  • Segnali generati da “tastiera”
    • E.g., l’utente utilizza CTRL-C (SIGINT) CTRL-Z (SIGSTOP);
  • Segnali generati da eccezioni hardware
    • NULL pointer, divisione per zero.. (SIGSEGV);
  • Segnali generati da eccezioni software
    • SIGPIPE, SIGALRM…
  • Segnali generati dalla terminazione di un processo figlio
    • default SIGCHLD;
  • Segnali inviati tramite system call raise();
  • Segnali inviati tramite system call kill();
  • Segnali inviati tramite comando kill.

Generazione di segnali (segue)

La generazione di un segnale X consiste solo nell’indicare al sistema operativo che, prima o poi, il processo destinatario dovrà ricevere e gestire il segnale X.
Ogni processo possiede due code di segnali pending.

  • La prima coda è detta “privata” e contiene i segnali che sono stati inviati esplicitamente al processo;
  • La seconda coda è detta “condivisa” e contiene i segnali che sono stati inviati al thread group (cfr lezione 22) e che saranno gestiti da uno dei thread del gruppo.

La generazione di un segnale aggiunge un elemento ad una delle due code.

  • Nei sistemi linux:
    • un segnale normale viene accodato ai segnali pendenti se e solo senon esiste un’altra istanza dello stesso segnale in coda
      • Esempio: Inviando 3 volte SIGINT ad un processo:
        • la prima istanza viene inserita in coda
        • Le istanze successive vengono scartate finchè la prima istanza non è stata gestita.
    • tutte le istanze dei segnali real-time vengono sempre accodate, indipendentemente dalla presenza o meno di una istanza dello stesso segnale in coda.

Azioni associate ai segnali

La fase di consegna del segnale è successiva alla sua generazione.
Un segnale è ricevuto dal processo destinazione in modo asincrono.

  • Non è possibile sapere quanto tempo intercorre dalla generazione del segnale alla sua gestione.

Il sistema operativo decide quando consegnare un segnale.
Prima della sua gestione, il sistema operativo preleva un segnale da una coda dei segnali pending;
è possibile indicare al kernel l’azione da intraprendere quando un segnale è ricevuto per un processo:

  • Ignora: Possibile per tutti i segnali tranne SIGKILL e SIGSTOP.
    • Il processo non esegue nessuna operazione.
  • Catturadel segnale: Indicare una procedura definita dall’utente, detta signal handler, da eseguire. Ad esempio:
    • SIGCHLD: L’handler visualizza un messaggio “Figlio terminato”
    • SIGINT: (CTRL-C) L’handler “cancella file temporanei”…
    • Non è possibile catturare SIGKILL o SIGSTOP.
  • Default: Eseguire l’azione di default associata al segnale.
    • In molti casi è la terminazione del processo.

Alcuni segnali ed azioni associate

Di seguito riportiamo esempi di alcuni segnali, gli eventi che li generano o il loro significato e dell’azione di default ad essi associata:

  • SIGINT:
    • Evento: interruzione da tastiera (Ctrl-c)
    • Azione di default: terminare il processo.
  • SIGSTOP (impossibile intercettare, bloccare od ignorare)
    • Significato: stop al processo
    • Azione di default: sospendere l’esecuzione.
  • SIGKILL (impossibile intercettare, bloccare od ignorare)
    • Significato: terminazione forzata
    • Azione di default: terminare il processo.

Alcuni segnali ed azioni associate (segue)

  • SIGCHLD
    • Significato: un processo figlio termina o viene fermato
    • Azione di default: ignorare.
  • SIGSEGV
    • Evento: segmentation fault
    • Azione di default: terminare il processo e salvare l’immagine della memoria (core).
  • SIGUSR1
    • Significato: a disposizione dell’utente
    • Azione di default: terminare il processo.

Handler di segnali

Un handler (o gestore) di un segnale è una funzione di tipo void che prende come parametro un int. Ad esempio:
void funzione(int num_segnale) { printf("%d", num_segnale); }
Il sistema operativo definisce I seguenti handler:

  • SIG_DFL: handler di default
  • SIG_IGN: ignora il segnale
  • SIG_ERR: errore

Un utente può definire propri gestori. Lo stesso signal handler può essere utilizzato per gestire più segnali.

  • Il parametro in input all’handler viene impostato dal sistema operativo in modo da contenere l’intero associato al segnale che si sta gestendo.
  • È sufficiente, all’interno del gestore, utilizzare il parametro per discriminare il comportamento.

Dopo la terminazione dell’handler, il processo riprende la sua esecuzione dal punto in cui era stato interrotto.

Esempio di handler definito dall’utente per i segnali SIGUSR1 e SIGUSR2: Mostra codice

Handler di segnali (segue)

Un handler non è altro che una funzione definita dal programmatore.
Può eseguire le stesse operazioni di una funzione “classica”; Ha accesso, ad esempio, alle variabili globali ed alla memoria dinamica del processo.
Una differenza sostanziale:

  • Per le funzioni classiche, il programmatore conosce o può definire esattamente lo stato in cui era il processo nel momento in cui ha invocato la funzione;
  • Per I gestori di segnale non è possibile avere informazioni su quale operazione stesse eseguendo il processo prima dell’inizio dell’handler.

Associare un handler ad un segnale

Per associare un handler ad un segnale è possibile utilizzare la system call signal

typedef void sighand(int);
sighand *signal(int, sighand*);

La signal riceve due parametri:

  • Un intero, codifica del segnale da associare;
  • Un puntatore a funzione (di tipo void con un unico parametro intero), l’handler.

La signal restituisce il gestore precedentemente associato al segnale:

  • l’indirizzo di un handler definito dall’utente oppure
  • SIG_DFL/SIG_IGN/SIG_ERR

In ogni istante, ad ogni segnale, è associato esattamente un solo handler:

  • Non è possibile associare due handler allo stesso segnale contemporaneamente;
  • È possibile modificare più volte l’handler associato ad un segnale durante l’esecuzione del processo.

Inviare segnali: Il comando kill

È possibile inviare segnali a:

  • Un singolo processo;
  • Un gruppi di processi identificati da un process group;
  • Un gruppo di thread appartenenti allo stesso thread group.

Un processo è identificato univocamente dal suo process id (pid):

  • un pid è un intero non negativo;
  • i pid 0 ed 1 sono utilizzati dal sistema;
  • è possibile visualizzare i pid dei propri processi in esecuzione, ad esempio, utilizzando il comando ps.

Affinché un processo sia in grado di inviare un segnale ad un altro processo è necessario che abbia i permessi per farlo; In pratica, ogni processo (utente) può inviare segnali agli altri processi dello stesso utente.
È possibile inviare segnali ad un processo utilizzando il comando kill:

  • kill -INT 127 invia il segnale SIGINT al processo il cui pid è 127;
  • kill -l elenca tutti i segnali ed i loro valori numerici;
  • kill 127 equivale a kill -TERM 127.

Esempi

Il codice: Esempio di gestione segnali Mostra codice.

L'esecuzione: Esempio di esecuzione del codice Mostra codice.

Inviare segnali: Le system call kill e raise

È possibile inviare segnali utilizzando la system call kill() int kill(pid_t pid, int sig);
Il parametro pid puo assumere i seguenti valori:

  • pid>0: Identifica il processo con PID=pid
  • pid=0: Tutti i processi con group ID pari al group ID del process che esegue la kill.
  • pid<0: Tutti i processi con group id pari al valore assoluto di pid. pid=-1 Inviato a tutti i processi del sistema per cui il processo che esegue la kill ha il permesso di inviare un segnale.

Il parametro sig può assumere i seguenti valori:

  • sig>0: è un intero specificato in signal.h.
  • sig=0: è utilizzato per verificare se il processo ha i permessi per inviare un segnale al/i processo/i specificati da pid.
    • Nessun segnale viene inviato;
    • Utile per verificare l’esistenza di un processo;
    • Attenzione: UNIX ricicla i pid!

Restituisce 0 in caso di successo e -1 in caso di errore.
La system call int raise(int signo) consente ad un processo di inviare un segnale a se stesso; Utile per forzare l’esecuzione dell’handler.

Impostare una “sveglia”

È possibile richiedere al sistema operativo di generare il segnale SIGALRM per il processo corrente dopo un certo intervallo di tempo attraverso la system call alarm() unsigned int alarm(unsigned int seconds)

  • riceve in input il numero di secondi dopo cui inviare il segnale;
  • restituisce 0 se non c’era nessuna sveglia già prenotata;
  • altrimenti, restituisce il tempo rimanente affinché la vecchia sveglia suonasse;
  • alarm(0) cancella la prenotazione precedente.

Esiste un’unica “sveglia” per processo.

Può trascorrere un tempo “indefinito” da quando il kernel genera il segnale fino all’esecuzione del handler.

Attenzione: L’azione di default di SIGALRM è la terminazione

  • Definire l’handler prima di eseguire l’alarm.

All’interno dell’handler, viene richiesto un nuovo segnale: Mostra codice

Attendere un segnale

Il sistema operativo consente la possibilità di sospendere volontariamente un processo finché questi non riceva un segnale.

La system call pause sospende il processo corrente fino alla ricezione di un segnale:

  • int pause(void);
  • pause() ritorna solo quando l’handler di un segnale termina.

Attenzione:

  • È necessario intercettare il segnale affinché pause ritorni;
  • Pause NON impedisce la terminazione del processo se l’azione associata al segnale è la terminazione.

Insiemi di segnali

In alcuni casi è necessario definire un insieme di segnali:

  • E.g., per indicare al kernel quali segnali “bloccare”.

È possibile che il numero di segnali resi disponibili da un sistema operativo sia maggiore del numero di bit utilizzati per rappresentare un intero

  • Non sarebbe possibile rappresentare tutti i segnali in un unico intero.

Per questo motivo POSIX definisce il tipo sigset_t ed una serie di funzioni su questo tipo di dato:

  • In modo da rendere indipendente l’interfaccia per gli insiemi di segnali da sistema operativo e/o architettura.

Insiemi di segnali: Funzioni

  • int sigemptyset(sigset_t *set);
    • Inizializza la variabile *set con un insieme vuoto.
  • int sigfillset(sigset_t *set);
    • Inizializza la variabile *set con un insieme contenente tutti i segnali.
  • int sigaddset(sigset_t *set, int signum);
    • Aggiunge all’insieme *set il segnale rappresentato da signum.
  • int sigdelset(sigset_t *set, int signum);
    • Elimina dall’insieme *set il segnale rappresentato da signum.
  • int sigismember(const sigset_t *set, int signum);
    • Ritorna 1 se signum appartiene a *set, 0 altrimenti.
  • Tutte le funzioni ritornano -1 in caso di errore.

Bloccare la ricezione di segnali

Ogni processo può bloccare un insieme di segnali.

  • Un processo riceve un segnale inviatogli da un altro processo/dal kernel se e solo se il segnale non è bloccato.
  • Non è possibile bloccare SIGSTOP e SIGKILL.

La “signal mask” identifica l’insieme di segnali bloccati in ogni istante dal processo.
La system call sigprocmask() consente di leggere, modificare (od eseguire entrambe le operazioni) sulla maschera dei segnali.
È possibile modificare la maschera dei segnali bloccati in qualsiasi momento durante l’esecuzione:

  • Ogni invocazione a sigprocmask ha effetto sulla maschera dell’intero processo
    • La modifica non è locale alla funzione che la esegue.
  • Ogni modifica eseguita all’interno di un handler viene persa quando il gestore termina
    • L’handler opera su un copia “locale” della signal mask del processo. La modifica a questa copia non influenza la signal mask del processo.

Se l’handler è installato con la system call signal(), il comportamento di default è bloccare il segnale durante la sua gestione.

Bloccare la ricezione di segnali (segue)

La system call per modificare la maschera di segnali è la seguente:

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

Se oldset è non nullo, conterrà la “vecchia” maschera;
Se set è non nullo, la nuova maschera viene calcolata in base i parametri set e how.
how può assumere i valori:

  • SIG_BLOCK: La nuova maschera è l’unione (in senso “algebrico”) tra (l’insieme rappresentato dal) la vecchia maschera e (l’insieme rappresentato dal) parametro set;
  • SIG_UNBLOCK: La nuova maschera è l’intersezione (in senso algebrico) tra la vecchia maschera ed il complementodella nuova.
    • L’effetto di SIG_UNBLOCK è rimuovere dalla maschera corrente, se presenti, i segnali indicati dal parametro set.
  • SIG_SETMASK: La nuova maschera è uguale alla maschera definita dal parametro set.

Il codice: Esempio utilizzo di sigprocmask Mostra codice

Un Esempio: L’esecuzione

Il segnale SIGUSR1 è bloccato fino alla ricezione della prima istanza del segnale SIGUSR2.

Si noti che, quando il processo riceve SIGUSR2, il segnale SIGUSR1 è pending.

Al termine dell’handler di SIGUSR2, il processo

  • sblocca SIGUSR1
  • Esegue l’handler ad esso associato, senza attendere la ricezione di una ulteriore istanza di SIGUSR2

Esempio di handler definito dall’utente per i segnali SIGUSR1 e SIGUSR2: Mostra codice

Associare un handler ad un segnale

La system call signal consente di modificare l’handler associato ad un segnale:

  • Semplice da utilizzare;
  • Non consente di definire un comportamento non standard all’atto dell’installazione dell’handler;
  • Non è possibile utilizzare la signal per conoscere l’handler associato ad un segnale senza modificarlo.

La system call sigaction consente di leggere o modificare (od eseguire entrambe le operazioni contemporaneamente) l’associazione tra un segnale ed un handler.

int sigaction(int signo, constr struct sigaction *act, struct sigaction *oact);

Il primo parametro indica il segnale sul cui handler si sta operando;
La struttura puntata da act, se non nullo, contiene l’indicazione del nuovo signal handler da associare al segnale;
La struttura puntata da oact, se non nulla, conterrà l’indicazione del vecchio valore dell’handler.

La struttura sigaction

La struttura sigaction consiste dei seguenti campi:

  • void (*sa_handler)(int): Specifica l’azione da associare al segnale, SIG_IGN o SIG_DFL o signal handler. Nell’ultimo caso, l’handler riceve come unico parametro l’intero associato al segnale da gestire;
  • void (*sa_sigaction)(int, siginfo_t *, void *): Specifica l’azione da associare al segnale. In questo caso la funzione riceve tre parametri:
    • L’intero associato al segnale da gestire;
    • Un puntatore ad una struttura siginfo_t contenente informazioni sulle ragioni per cui il segnale è stato generato
      • E.g., se il segnale è SIGCHLD, la struttura siginfo contiene il pid del figlio del processo corrente che ha cambiato stato;
    • Un puntatore al contesto del processo precedente l’esecuzione dell’handler (contenuto dei registri, puntatore allo stack e maschera dei segnali).
  • In molti sistemi, i campi sa_handler e sa_sigaction sono inclusi in una union.
    • NON impostare mai entrambi I campi della struttura.

La struttura sigaction (segue)

  • sigset_t sa_mask: Maschera dei segnali che verranno aggiunti alla signal mask del processo primadi invocare l’handler.
    • Durante l’esecuzione dell’handler, il sistema operativo blocca:
      • i segnali contenuti nella signal mask corrente del processo;
      • i segnali contenuti nel campo sa_mask;
      • il segnale da gestire.
  • int sa_flags: Opzioni del segnale. Alcuni esempi:
    • SA_NOCLDSTOP: Per SIGCHLD, indica al sistema operativo di inviare questo segnale solo quando il processo figlio termina (i.e., non inviare questo segnale quando il processo figlio viene fermato);
    • SA_NODEFER: Non bloccare il segnale durante l’esecuzione del suo handler;
    • SA_SIGINFO: Consente di avere informazioni aggiuntive sul segnale. In questo caso, l’handler del segnale deve essere definito con tre parametri. Uno di questi è un puntatore ad una struttura di tipo siginfo_t che conterrà informazioni sul segnale.

La struttura siginfo_t

La struttura siginfo contiene informazioni su sulle ragioni per cui il segnale è stato generato.
Queste informazioni dipendono dal segnale da gestire.

I campi si_signo (numero del segnale) , si_errno (codice d’errore) ed si_code (“ragione” per cui si è verificato il segnale) sono definiti per tutti i segnali.

Altri campi della struttura possono essere definiti solo per alcuni segnali. Ad esempio:

  • per la gestione di SIGCHLD il sistema operativo riempie i campi
    • si_pid: pid del processo che ha cambiato stato;
    • si_uid: real user id del processo figlio;
    • si_status: exit status del processo figlio;
    • si_utime: tempo macchina utilizzato dal processo figlio.
  • per la gestione di SIGILL, SIGFPE, SIGSEGV e SIGBUS il sistema operativo riempie il campo
    • si_addr: indirizzo a cui si è verificato il fault.

Un esempio di utilizzo di sigaction: Mostra codice. L'handler è definito nella slide successiva.

Sigaction: un esempio

All’interno di un handler, è possibile far riferimento alle informazioni contenute nella struttura di tip siginfo_t.

Si tenga conto che le informazioni contenute nella struttura siginfo dipendono dal segnale che si sta gestendo.

L’output del programma sarà il seguente:

pid figlio 1 8794
8794 termina normalmente
pid figlio 2 8795
8795 termina con errore

Esempio di utilizzo della struttura siginfo all’interno dell’handler: Mostra codice

  • Contenuti protetti da Creative Commons
  • Feed RSS
  • Condividi su FriendFeed
  • Condividi su Facebook
  • Segnala su Twitter
  • Condividi su LinkedIn
Progetto "Campus Virtuale" dell'Università degli Studi di Napoli Federico II, realizzato con il cofinanziamento dell'Unione europea. Asse V - Società dell'informazione - Obiettivo Operativo 5.1 e-Government ed e-Inclusion