Qual'è la differenza tra sigaction e signal?


143

Stavo per aggiungere un gestore di segnale aggiuntivo a un'app che abbiamo qui e ho notato che l'autore aveva usato sigaction()per configurare gli altri gestori di segnale. Stavo per usare signal(). Per seguire la convenzione dovrei usare, sigaction()ma se stavo scrivendo da zero, quale dovrei scegliere?

Risposte:


167

Usa a sigaction()meno che tu non abbia ragioni molto convincenti per non farlo.

L' signal()interfaccia ha l'antichità (e quindi la disponibilità) a suo favore, ed è definita nello standard C. Tuttavia, ha una serie di caratteristiche indesiderabili che sigaction()evita, a meno che tu non usi le bandiere esplicitamente aggiunte sigaction()per consentirgli di simulare fedelmente il vecchio signal()comportamento.

  1. La signal()funzione non (necessariamente) impedisce l'arrivo di altri segnali mentre il gestore corrente sta eseguendo; sigaction()può bloccare altri segnali fino al ritorno dell'attuale gestore.
  2. La signal()funzione (di solito) riporta l'azione del segnale su SIG_DFL(impostazione predefinita) per quasi tutti i segnali. Ciò significa che il signal()gestore deve reinstallarsi come prima azione. Si apre anche una finestra di vulnerabilità tra il momento in cui viene rilevato il segnale e il gestore viene reinstallato durante il quale se arriva una seconda istanza del segnale, si verifica il comportamento predefinito (di solito termina, a volte con pregiudizio, noto anche come core dump).
  3. L'esatto comportamento signal()varia tra i sistemi e gli standard consentono tali variazioni.

Questi sono generalmente buoni motivi per l'utilizzo sigaction()anziché signal(). Tuttavia, l'interfaccia di sigaction()è innegabilmente più complicata.

Quello dei due che si utilizza, non essere tentati dalle interfacce di segnale alternative come sighold(), sigignore(), sigpause()e sigrelse(). Sono nominalmente alternative a sigaction(), ma sono solo a malapena standardizzate e sono presenti in POSIX per compatibilità con le versioni precedenti piuttosto che per uso serio. Si noti che lo standard POSIX afferma che il loro comportamento nei programmi multi-thread non è definito.

Programmi e segnali multi-thread è un'altra storia complicata. AFAIK, entrambi signal()e sigaction()sono OK nelle applicazioni multi-thread.

Cornstalks osserva :

La pagina man di Linux per signal()dice:

  Gli effetti di signal()un processo multi-thread non sono specificati.

Pertanto, penso che sigaction()sia l'unico che può essere utilizzato in modo sicuro in un processo multi-thread.

Interessante. La pagina di manuale di Linux è più restrittiva di POSIX in questo caso. POSIX specifica per signal():

Se il processo è multi-thread o se il processo è single-thread e un gestore del segnale viene eseguito diverso da come risultato di:

  • La chiamata processo abort(), raise(), kill(), pthread_kill(), o sigqueue()per generare un segnale non bloccato
  • Viene restituito un segnale in sospeso che viene sbloccato e consegnato prima della chiamata che lo ha sbloccato

il comportamento non è definito se il gestore del segnale fa riferimento a qualsiasi oggetto diverso dalla errnodurata di memorizzazione statica diversa dall'assegnazione di un valore a un oggetto dichiarato come volatile sig_atomic_t, o se il gestore del segnale chiama una funzione definita in questo standard diversa da una delle funzioni elencate in Concetti di segnale .

Quindi POSIX specifica chiaramente il comportamento di signal()in un'applicazione multi-thread.

Tuttavia, sigaction()deve essere preferito essenzialmente in tutte le circostanze - e il codice multi-thread portatile dovrebbe usare a sigaction()meno che non ci sia una ragione schiacciante per cui non può (come "usare solo le funzioni definite dalla norma C" - e sì, il codice C11 può essere multi -threaded). Questo è fondamentalmente ciò che dice anche il paragrafo iniziale di questa risposta.


12
Questa descrizione di signalè in realtà il comportamento di Unix System V. POSIX consente questo comportamento o il comportamento BSD molto più sano, ma poiché non si può essere sicuri di quale si otterrà, è comunque meglio usare sigaction.
R .. GitHub smette di aiutare ICE il

1
a meno che non si utilizzino i flag esplicitamente aggiunti a sigaction () per consentirgli di simulare fedelmente il vecchio comportamento del segnale (). Quali bandiere sarebbero quelle (in particolare)?
ChristianCuevas,

@AlexFritz: In primo luogo SA_RESETHAND, anche SA_NODEFER.
Jonathan Leffler,

2
@BulatM. Se non puoi utilizzarlo sigaction(), sei essenzialmente obbligato a utilizzare la specifica Standard C per signal(). Tuttavia, questo ti dà una serie estremamente impoverita di opzioni per quello che puoi fare. È possibile: modificare (tipo di file) variabili di tipo volatile sig_atomic_t; chiamare una delle funzioni di 'uscita rapida' ( _Exit(), quick_exit()) o abort(); chiama signal()con il numero del segnale corrente come argomento del segnale; ritorno. E questo è tutto. Non è garantito che qualsiasi altra cosa sia portatile. È così rigoroso che la maggior parte delle persone ignora quelle regole, ma il codice risultante è complicato.
Jonathan Leffler,

1
Eccellente sigaction()demo degli stessi GCC: gnu.org/software/libc/manual/html_node/… ; ed eccellente signal()demo dagli stessi GCC: gnu.org/software/libc/manual/html_node/… . Si noti che nella signaldemo evitano di cambiare il gestore da ignore ( SIG_IGN) se è quello su cui era stato intenzionalmente impostato in precedenza.
Gabriel Staples,


5

Sono interfacce diverse per le strutture di segnale del sistema operativo. Si dovrebbe preferire l'uso della sigazione per segnalare, se possibile, poiché il segnale () ha un comportamento definito dall'implementazione (spesso incline alla razza) e si comporta in modo diverso su Windows, OS X, Linux e altri sistemi UNIX.

Vedi questa nota di sicurezza per i dettagli.


1
Ho appena guardato il codice sorgente di glibc e signal () chiama solo in sigaction (). Vedi anche sopra dove la pagina man di MacOS rivendica lo stesso.
bmdhacks,

buono a sapersi. Ho mai visto solo gestori di segnali usati per chiudere ordinatamente le cose prima di uscire, quindi di solito non farei affidamento sul comportamento relativo alla reinstallazione del gestore.
Matthew Smith,

5

signal () è lo standard C, sigaction () non lo è.

Se sei in grado di usare uno dei due (ovvero, sei su un sistema POSIX), allora usa sigaction (); non è specificato se signal () reimposta il gestore, il che significa che per essere portatile è necessario chiamare nuovamente signal () all'interno del gestore. Quel che è peggio è che c'è una gara: se ricevi due segnali in rapida successione e il secondo viene consegnato prima di reinstallare il gestore, avrai l'azione predefinita, che probabilmente ucciderà il tuo processo. sigaction () , d'altra parte, è garantito l'uso di semantica del segnale "affidabile". Non è necessario reinstallare il gestore, perché non verrà mai ripristinato. Con SA_RESTART, puoi anche ottenere alcune chiamate di sistema per riavviare automaticamente (quindi non devi controllare manualmente la presenza di EINTR). sigaction () ha più opzioni ed è affidabile, quindi il suo utilizzo è incoraggiato.

Psst ... non dire a nessuno che te l'ho detto, ma POSIX attualmente ha una funzione bsd_signal () che agisce come segnale () ma fornisce semantica BSD, il che significa che è affidabile. Il suo uso principale è per il porting di vecchie applicazioni che presupponevano segnali affidabili e POSIX non consiglia di usarlo.


POSIX non ha una funzione bsd_signal()- alcune implementazioni POSIX potrebbero avere la funzione, ma POSIX stesso non contiene tale funzione (vedere POSIX ).
Jonathan Leffler,

4

In breve:

sigaction()è buono e ben definito, ma è una funzione Linux e quindi funziona solo su Linux. signal()è cattivo e mal definito, ma è una funzione standard C e quindi funziona su qualsiasi cosa.

Cosa dicono le pagine man di Linux al riguardo?

man 2 signal(vederlo online qui ) afferma:

Il comportamento di signal () varia tra le versioni UNIX e ha anche variato storicamente tra le diverse versioni di Linux. Evita il suo uso: usa sigaction(2)invece. Vedi Portabilità sotto.

Portabilità L'unico uso portatile di signal () è impostare la disposizione di un segnale su SIG_DFL o SIG_IGN. La semantica quando si utilizza signal () per stabilire un gestore di segnale varia tra i sistemi (e POSIX.1 consente esplicitamente questa variazione); non usarlo per questo scopo.

In altre parole: non usare signal(). Usa sigaction()invece!

Cosa pensa GCC?

Nota di compatibilità: come detto sopra per signal, questa funzione dovrebbe essere evitata quando possibile. sigactionè il metodo preferito.

Fonte: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling

Quindi, se sia Linux che GCC dicono di non usare signal(), ma di usare sigaction()invece, ciò pone la domanda: come diamine usiamo questa sigaction()cosa confusa !?

Esempi di utilizzo:

Leggi l' signal()esempio ECCELLENTE di GCC qui: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling

E il loro sigaction()esempio ECCELLENTE qui: https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html

Dopo aver letto quelle pagine, mi è venuta in mente la seguente tecnica per sigaction():

1. sigaction(), poiché è il modo giusto di collegare un gestore di segnale, come descritto sopra:

#include <errno.h>  // errno
#include <signal.h> // sigaction()
#include <stdio.h>  // printf()
#include <string.h> // strerror()

#define LOG_LOCATION __FILE__, __LINE__, __func__ // Format: const char *, unsigned int, const char *
#define LOG_FORMAT_STR "file: %s, line: %u, func: %s: "

/// @brief      Callback function to handle termination signals, such as Ctrl + C
/// @param[in]  signal  Signal number of the signal being handled by this callback function
/// @return     None
static void termination_handler(const int signal)
{
    switch (signal)
    {
    case SIGINT:
        printf("\nSIGINT (%i) (Ctrl + C) signal caught.\n", signal);
        break;
    case SIGTERM:
        printf("\nSIGTERM (%i) (default `kill` or `killall`) signal caught.\n", signal);
        break;
    case SIGHUP:
        printf("\nSIGHUP (%i) (\"hang-up\") signal caught.\n", signal);
        break;
    default:
        printf("\nUnk signal (%i) caught.\n", signal);
        break;
    }

    // DO PROGRAM CLEANUP HERE, such as freeing memory, closing files, etc.


    exit(signal);
}

/// @brief      Set a new signal handler action for a given signal
/// @details    Only update the signals with our custom handler if they are NOT set to "signal ignore" (`SIG_IGN`),
///             which means they are currently intentionally ignored. GCC recommends this "because non-job-control
///             shells often ignore certain signals when starting children, and it is important for children
///             to respect this." See
///             https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
///             and https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html.
///             Note that termination signals can be found here:
///             https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html#Termination-Signals
/// @param[in]  signal  Signal to set to this action
/// @param[in]  action  Pointer to sigaction struct, including the callback function inside it, to attach to this signal
/// @return     None
static inline void set_sigaction(int signal, const struct sigaction *action)
{
    struct sigaction old_action;

    // check current signal handler action to see if it's set to SIGNAL IGNORE
    sigaction(signal, NULL, &old_action);
    if (old_action.sa_handler != SIG_IGN)
    {
        // set new signal handler action to what we want
        int ret_code = sigaction(signal, action, NULL);
        if (ret_code == -1)
        {
            printf(LOG_FORMAT_STR "sigaction failed when setting signal to %i;\n"
                   "  errno = %i: %s\n", LOG_LOCATION, signal, errno, strerror(errno));
        }
    }
}

int main(int argc, char *argv[])
{
    //...

    // Register callbacks to handle kill signals; prefer the Linux function `sigaction()` over the C function
    // `signal()`: "It is better to use sigaction if it is available since the results are much more reliable."
    // Source: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
    // and /programming/231912/what-is-the-difference-between-sigaction-and-signal/232711#232711.
    // See here for official gcc `sigaction()` demo, which this code is modeled after:
    // https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html

    // Set up the structure to specify the new action, per GCC's demo.
    struct sigaction new_action;
    new_action.sa_handler = termination_handler; // set callback function
    sigemptyset(&new_action.sa_mask);
    new_action.sa_flags = 0;

    // SIGINT: ie: Ctrl + C kill signal
    set_sigaction(SIGINT, &new_action);
    // SIGTERM: termination signal--the default generated by `kill` and `killall`
    set_sigaction(SIGTERM, &new_action);
    // SIGHUP: "hang-up" signal due to lost connection
    set_sigaction(SIGHUP, &new_action);

    //...
}

2. E signal(), anche se non è un buon modo per collegare un gestore di segnale, come descritto sopra, è comunque buono sapere come usarlo.

Ecco il codice dimostrativo GCC copiato e incollato, in quanto è buono come sta per ottenere:

#include <signal.h>

void
termination_handler (int signum)
{
  struct temp_file *p;

  for (p = temp_file_list; p; p = p->next)
    unlink (p->name);
}

int
main (void)
{
  
  if (signal (SIGINT, termination_handler) == SIG_IGN)
    signal (SIGINT, SIG_IGN);
  if (signal (SIGHUP, termination_handler) == SIG_IGN)
    signal (SIGHUP, SIG_IGN);
  if (signal (SIGTERM, termination_handler) == SIG_IGN)
    signal (SIGTERM, SIG_IGN);
  
}

I principali collegamenti da tenere presente:

  1. Segnali standard: https://www.gnu.org/software/libc/manual/html_node/Standard-Signals.html#Standard-Signals
    1. Segnali di terminazione: https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html#Termination-Signals
  2. Gestione del segnale di base, incluso un signal()esempio di utilizzo ufficiale di GCC : https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
  3. sigaction()Esempio di utilizzo ufficiale di GCC : https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html
  4. Set di segnali, inclusi sigemptyset()e sigfillset(); Non li capisco ancora esattamente, ma so che sono importanti: https://www.gnu.org/software/libc/manual/html_node/Signal-Sets.html

Guarda anche:

  1. TutorialsPoint C ++ Signal Handling [con eccellente codice demo]: https://www.tutorialspoint.com/cplusplus/cpp_signal_handling.htm
  2. https://www.tutorialspoint.com/c_standard_library/signal_h.htm

2

Dalla signal(3)pagina man:

DESCRIZIONE

 This signal() facility is a simplified interface to the more
 general sigaction(2) facility.

Entrambi invocano la stessa struttura sottostante. Presumibilmente non dovresti manipolare la risposta di un singolo segnale con entrambi, ma mescolarli non dovrebbe causare la rottura di nulla ...


Non è nella mia pagina man! Tutto quello che ottengo è "DESCRIZIONE La chiamata di sistema signal () installa un nuovo gestore di segnale per il segnale con numero signum." Devo passare all'utile pacchetto di pagine man.
Matthew Smith,

1
È fuori dalle pagine di Mac OS X 10.5.
dmckee --- ex gattino moderatore

Verificato anche dal codice sorgente di glibc. signal () chiama solo sigaction ()
bmdhacks

2
Questo non è vero su tutte le implementazioni del segnale, tuttavia. Se si desidera imporre il comportamento di "sigazione", non fare affidamento su questo presupposto.
Ben Burns,

1

Vorrei anche suggerire di usare sigaction () su signal () e vorrei aggiungere un altro punto. sigaction () offre più opzioni come pid del processo che è morto (possibile usando la struttura siginfo_t).


0

Userei signal () poiché è più portatile, almeno in teoria. Voterò qualsiasi commentatore che può inventare un sistema moderno che non ha un livello di compatibilità POSIX e supporta il segnale ().

Citando dalla documentazione GLIBC :

È possibile utilizzare entrambe le funzioni di segnale e sigazione all'interno di un singolo programma, ma è necessario fare attenzione perché possono interagire in modi leggermente strani.

La funzione di sigazione specifica più informazioni rispetto alla funzione di segnale, quindi il valore di ritorno dal segnale non può esprimere l'intera gamma di possibilità di sigazione. Pertanto, se si utilizza il segnale per salvare e successivamente ristabilire un'azione, potrebbe non essere in grado di ristabilire correttamente un gestore stabilito con sigazione.

Di conseguenza, per evitare problemi, utilizzare sempre sigaction per salvare e ripristinare un gestore se il programma utilizza affatto sigaction. Poiché la sigazione è più generale, può salvare e ristabilire correttamente qualsiasi azione, indipendentemente dal fatto che sia stata stabilita originariamente con segnale o sigazione.

Su alcuni sistemi se si stabilisce un'azione con segnale e quindi si esamina con sigazione, l'indirizzo del gestore che si ottiene potrebbe non essere lo stesso di quello specificato con il segnale. Potrebbe anche non essere adatto all'uso come argomento di azione con segnale. Ma puoi fare affidamento sul suo utilizzo come argomento per la sigazione. Questo problema non si verifica mai sul sistema GNU.

Quindi, stai meglio usando l'uno o l'altro dei meccanismi in modo coerente all'interno di un singolo programma.

Nota sulla portabilità: la funzione di segnale di base è una caratteristica di ISO C, mentre la sigazione fa parte dello standard POSIX.1. Se sei preoccupato per la portabilità a sistemi non POSIX, devi invece utilizzare la funzione di segnale.

Copyright (C) 1996-2008 Free Software Foundation, Inc.

È concesso il permesso di copiare, distribuire e / o modificare questo documento secondo i termini della GNU Free Documentation License, Versione 1.2 o successiva pubblicata dalla Free Software Foundation; senza sezioni invarianti, senza testi di copertina e senza testi di copertina. Una copia della licenza è inclusa nella sezione intitolata "Licenza per documentazione gratuita GNU".


0

Dal segnale della pagina man (7)

Un segnale diretto al processo può essere inviato a qualsiasi thread che al momento non ha il segnale bloccato. Se in uno dei thread è sbloccato il segnale, il kernel sceglie un thread arbitrario a cui inviare il segnale.

E direi che questo "problema" esiste per signal (2) e sigaction (2) . Quindi fai attenzione con segnali e pthreads.

... e il segnale (2) sembra chiamare sigaction (2) sotto in Linux con glibc.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.