Qual é a diferença entre sigaction e signal?

143

Eu estava prestes a adicionar um manipulador de sinal extra a um aplicativo que temos aqui e notei que o autor havia usado sigaction()para configurar os outros manipuladores de sinal. Eu ia usar signal(). Para seguir a convenção, devo usar, sigaction()mas se estava escrevendo do zero, qual devo escolher?

Matthew Smith
fonte

Respostas:

167

Use a sigaction()menos que você tenha razões muito convincentes para não fazer isso.

A signal()interface tem antiguidade (e, portanto, disponibilidade) a seu favor, e é definida no padrão C. No entanto, ele tem várias características indesejáveis ​​que sigaction()evitam - a menos que você use as bandeiras adicionadas explicitamente sigaction()para permitir que ele simule fielmente o signal()comportamento antigo .

  1. A signal()função não (necessariamente) impede que outros sinais cheguem enquanto o manipulador atual está em execução; sigaction()pode bloquear outros sinais até que o manipulador atual retorne.
  2. A signal()função (geralmente) redefine a ação do sinal de volta para SIG_DFL(padrão) para quase todos os sinais. Isso significa que o signal()manipulador deve se reinstalar como sua primeira ação. Ele também abre uma janela de vulnerabilidade entre o momento em que o sinal é detectado e o manipulador é reinstalado, durante o qual, se uma segunda instância do sinal chegar, o comportamento padrão (geralmente termina, às vezes com preconceito - também conhecido como core dump).
  3. O comportamento exato de signal()varia entre sistemas - e os padrões permitem essas variações.

Geralmente, essas são boas razões para usar em sigaction()vez de signal(). No entanto, a interface do sigaction()é inegavelmente mais complicada.

Qualquer que seja a dos dois que você usa, não ser tentado pelas interfaces de sinal alternativos, como sighold(), sigignore(), sigpause()e sigrelse(). Eles são nominalmente alternativas sigaction(), mas são pouco padronizados e estão presentes no POSIX para compatibilidade com versões anteriores, e não para uso sério. Observe que o padrão POSIX diz que seu comportamento em programas multiencadeados é indefinido.

Programas e sinais multithread são outra história complicada. AFAIK, ambos signal()e sigaction()estão OK em aplicativos multithread.

Cornstalks observa :

A página de manual do Linux para signal()diz:

  Os efeitos de signal()um processo multiencadeado não são especificados.

Portanto, acho que sigaction()é o único que pode ser usado com segurança em um processo multiencadeado.

Isso é interessante. A página de manual do Linux é mais restritiva que o POSIX neste caso. O POSIX especifica para signal():

Se o processo for multithread, ou se o processo for single-thread, e um manipulador de sinal for executado, exceto como resultado de:

  • O chamado processo abort(), raise(), kill(), pthread_kill(), ou sigqueue()para gerar um sinal de que não está bloqueado
  • Um sinal pendente sendo desbloqueado e entregue antes da chamada que o desbloqueou retornar

o comportamento é indefinido se o manipulador de sinal se referir a qualquer objeto que não seja errnocom duração de armazenamento estático, além de atribuir um valor a um objeto declarado como volatile sig_atomic_t, ou se o manipulador de sinal chamar qualquer função definida neste padrão que não seja uma das funções listadas em Conceitos de sinal .

Portanto, o POSIX especifica claramente o comportamento de signal()um aplicativo multiencadeado.

No entanto, sigaction()deve ser preferido em praticamente todas as circunstâncias - e o código multithread portátil deve sigaction()ser usado, a menos que haja uma razão avassaladora para que não possa (como "usar apenas funções definidas pelo Padrão C" - e sim, o código C11 pode ser multi rosqueado). Que é basicamente o que o parágrafo de abertura desta resposta também diz.

Jonathan Leffler
fonte
12
Esta descrição de signalé realmente o comportamento do Unix System V. O POSIX permite esse comportamento ou o BSD muito mais sensato, mas como você não tem certeza de qual deles obterá, ainda é melhor usá-lo sigaction.
G .. GitHub PARE DE AJUDAR O GELO 15/08
1
a menos que você use os sinalizadores adicionados explicitamente ao sigaction () para permitir simular fielmente o comportamento do sinal antigo (). Quais bandeiras seriam (especificamente)?
ChristianCuevas
@ AlexFritz: Principalmente SA_RESETHANDtambém SA_NODEFER.
Jonathan Leffler
2
@BulatM. Se você não pode usar sigaction(), você é essencialmente obrigado a usar a especificação C padrão signal(). No entanto, isso oferece um conjunto de opções extremamente empobrecido para o que você pode fazer. Você pode: modificar (escopo do arquivo) variáveis ​​do tipo volatile sig_atomic_t; chame uma das funções de 'saída rápida' ( _Exit(), quick_exit()) ou abort(); chamar signal()com o número do sinal atual como argumento do sinal; Retorna. E é isso. Não há garantia de que qualquer outra coisa seja portátil. Isso é tão rigoroso que a maioria das pessoas ignora essas regras - mas o código resultante é desonesto.
Jonathan Leffler 27/10
1
Excelente sigaction()demonstração do próprio GCC: gnu.org/software/libc/manual/html_node/… ; e excelente signal()demonstração do próprio GCC: gnu.org/software/libc/manual/html_node/… . Observe que, na signaldemonstração, eles evitam alterar o manipulador de ignore ( SIG_IGN) se for para isso que ele foi intencionalmente definido anteriormente.
Gabriel Staples
8

Para mim, essa linha abaixo foi suficiente para decidir:

A função sigaction () fornece um mecanismo mais abrangente e confiável para controlar sinais; novas aplicações devem usar sigaction () ao invés de signal ()

http://pubs.opengroup.org/onlinepubs/009695399/functions/signal.html#tag_03_690_07

Esteja você começando do zero ou modificando um programa antigo, a sigação deve ser a opção certa.

San
fonte
5

São interfaces diferentes para os recursos de sinal do SO. Deve-se preferir usar sigaction para sinalizar, se possível, pois o sinal () possui um comportamento definido pela implementação (geralmente propenso a corridas) e se comporta de maneira diferente no Windows, OS X, Linux e outros sistemas UNIX.

Consulte esta nota de segurança para obter detalhes.

ididak
fonte
1
Eu apenas olhei o código-fonte glibc e o sinal () chama apenas sigaction (). Veja também acima, onde a página do manual do MacOS afirma o mesmo.
bmdhacks
É bom saber. Eu só vi manipuladores de sinal usados ​​para fechar as coisas perfeitamente antes de sair, para que eu normalmente não confiasse no comportamento relacionado à reinstalação do manipulador.
Matthew Smith
5

sinal () é padrão C, sigaction () não é.

Se você pode usar qualquer um (ou seja, você está em um sistema POSIX), use sigaction (); não é especificado se signal () redefine o manipulador, o que significa que, para ser portátil, você precisa chamar o sinal () novamente dentro do manipulador. O pior é que há uma corrida: se você receber dois sinais em rápida sucessão e o segundo for entregue antes de reinstalar o manipulador, você terá a ação padrão, que provavelmente matará seu processo. sigaction () , por outro lado, é garantido para usar semântica de sinal "confiável". Você não precisa reinstalar o manipulador, porque ele nunca será redefinido. Com o SA_RESTART, você também pode receber algumas chamadas do sistema para reiniciar automaticamente (para que você não precise verificar manualmente o EINTR). sigaction () tem mais opções e é confiável, portanto, seu uso é incentivado.

Psst ... não conte a ninguém que eu te disse isso, mas o POSIX atualmente possui uma função bsd_signal () que age como sinal (), mas fornece semântica BSD, o que significa que é confiável. Seu principal uso é para portar aplicativos antigos que assumiam sinais confiáveis, e o POSIX não recomenda usá-lo.

Huzzefakhan
fonte
O POSIX não possui uma função bsd_signal()- algumas implementações do POSIX podem ter a função, mas o próprio POSIX não contém essa função (consulte POSIX ).
Jonathan Leffler
4

Em resumo:

sigaction()é bom e bem definido, mas é uma função do Linux e, portanto, funciona apenas no Linux. signal()é ruim e mal definido, mas é uma função padrão C e, portanto, funciona em qualquer coisa.

O que as páginas de manual do Linux têm a dizer sobre isso?

man 2 signal(veja online aqui ) declara:

O comportamento do sinal () varia entre as versões UNIX e também variou historicamente entre diferentes versões do Linux. Evite seu uso: use em sigaction(2)vez disso. Veja Portabilidade abaixo.

Portabilidade O único uso portátil de signal () é definir a disposição de um sinal como SIG_DFL ou SIG_IGN. A semântica ao usar o sinal () para estabelecer um manipulador de sinal varia entre os sistemas (e o POSIX.1 permite explicitamente essa variação); não o utilize para esse fim.

Em outras palavras: não use signal(). Use em sigaction()vez disso!

O que o GCC pensa?

Nota de compatibilidade: Como dito acima signal, esta função deve ser evitada quando possível. sigactioné o método preferido.

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

Então, se o Linux e o GCC dizem para não usar signal(), mas usar sigaction(), isso levanta a questão: como diabos usamos essa sigaction()coisa confusa ?

Exemplos de uso:

Leia o EXCELENTE signal()exemplo do GCC aqui: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling

E o EXCELENTE sigaction()exemplo aqui: https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html

Depois de ler essas páginas, criei a seguinte técnica para sigaction():

1. sigaction(), pois é o caminho certo para conectar um manipulador de sinal, conforme descrito acima:

#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(), apesar de não ser uma boa maneira de conectar um manipulador de sinal, como descrito acima, ainda é bom saber como usá-lo.

Aqui está o código de demonstração do GCC copiado e colado, pois é tão bom quanto possível:

#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);
  
}

Os principais links para estar ciente de:

  1. Sinais padrão: https://www.gnu.org/software/libc/manual/html_node/Standard-Signals.html#Standard-Signals
    1. Sinais de rescisão: https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html#Termination-Signals
  2. Manipulação básica de sinais, incluindo signal()exemplo oficial de uso do GCC : https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
  3. sigaction()Exemplo oficial de uso do GCC : https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html
  4. Conjuntos de sinais, incluindo sigemptyset()e sigfillset(); Ainda não entendi exatamente, mas sei que são importantes: https://www.gnu.org/software/libc/manual/html_node/Signal-Sets.html

Veja também:

  1. TutorialsPoint C ++ Signal Handling [com excelente código de demonstração]: https://www.tutorialspoint.com/cplusplus/cpp_signal_handling.htm
  2. https://www.tutorialspoint.com/c_standard_library/signal_h.htm
Gabriel Staples
fonte
2

Na signal(3)página do manual:

DESCRIÇÃO

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

Ambos invocam o mesmo recurso subjacente. Presumivelmente, você não deve manipular a resposta de um único sinal com ambos, mas misturá-los não deve fazer com que nada quebre ...

dmckee --- gatinho ex-moderador
fonte
Isso não está na minha página de manual! Tudo o que recebo é "DESCRIÇÃO A chamada do sistema signal () instala um novo manipulador de sinal para o sinal com número numérico." Eu preciso atualizar para o pacote útil de páginas de manual.
Matthew Smith
1
Isso está fora das páginas do Mac OS X 10.5.
dmckee --- ex-moderador gatinho
Também verificado a partir do código fonte da glibc. signal () apenas chama sigaction ()
bmdhacks
2
Isso não é verdade em todas as implementações de sinal, no entanto. Se você deseja determinar o comportamento de "sigação", não confie nessa suposição.
Ben Burns
1

Eu também sugeriria usar sigaction () sobre o sinal () e gostaria de adicionar mais um ponto. sigaction () oferece mais opções, como pid do processo que morreu (possível usando a estrutura siginfo_t).

pradyumna kaushik
fonte
0

Eu usaria o sinal (), pois é mais portátil, pelo menos em teoria. Vou votar em qualquer comentarista que possa criar um sistema moderno que não tenha uma camada de compatibilidade POSIX e suporte a sinal ().

Citando a partir da documentação GLIBC :

É possível usar as funções de sinal e sigação em um único programa, mas é preciso ter cuidado, pois elas podem interagir de maneiras um pouco estranhas.

A função de sigação especifica mais informações que a função de sinal; portanto, o valor de retorno do sinal não pode expressar toda a gama de possibilidades de sigação. Portanto, se você usar o sinal para salvar e posteriormente restabelecer uma ação, talvez não seja possível restabelecer adequadamente um manipulador que foi estabelecido com sigaction.

Para evitar problemas como resultado, sempre use sigaction para salvar e restaurar um manipulador, se o seu programa usar sigaction. Como a sigação é mais geral, ela pode salvar e restabelecer adequadamente qualquer ação, independentemente de ter sido estabelecida originalmente com sinal ou sigação.

Em alguns sistemas, se você estabelecer uma ação com sinal e examiná-la com sigação, o endereço do manipulador que você obtém pode não ser o mesmo que você especificou com sinal. Pode até não ser adequado para uso como argumento de ação com sinal. Mas você pode usá-lo como argumento para a sigação. Esse problema nunca acontece no sistema GNU.

Portanto, é melhor usar um ou outro mecanismo de maneira consistente em um único programa.

Portabilidade Nota: A função básica do sinal é um recurso da ISO C, enquanto a sigação faz parte do padrão POSIX.1. Se você está preocupado com a portabilidade para sistemas não-POSIX, use a função de sinal.

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

É concedida permissão para copiar, distribuir e / ou modificar este documento sob os termos da GNU Free Documentation License, Versão 1.2 ou qualquer versão posterior publicada pela Free Software Foundation; sem seções invariantes, sem textos de capa e sem textos de contracapa. Uma cópia da licença está incluída na seção "GNU Free Documentation License".

bmdhacks
fonte
0

Do sinal da página de manual (7)

Um sinal direcionado ao processo pode ser entregue a qualquer um dos encadeamentos que atualmente não tem o sinal bloqueado. Se mais de um dos threads tiver o sinal desbloqueado, o kernel escolherá um thread arbitrário para o qual o sinal será entregue.

E eu diria que esse "problema" existe para o sinal (2) e sigaction (2) . Portanto, tenha cuidado com sinais e pinos.

... e o sinal (2) parece chamar sigaction (2) abaixo no Linux com glibc.

robert.berger
fonte