O manipulador SIGINT é executado apenas uma vez

1

Estou aprendendo sobre sinais e funções inseguras de sinais assíncronos. Em particular, aprendi que printf é assíncrono e inseguro e pode causar um impasse quando chamado a partir do encadeamento principal do programa e de um manipulador de sinal. Para verificar isso, eu escrevi o seguinte programa (é um pouco feio):

/*
 * sig-deadlock.c
 */

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

void
sigint_handler(int signum)
{
        int i;
        printf("Signal");
        for (i = 0; i < 30; i++) {
                printf("%d\n", i);
        }
}

int
main(void)
{
        int i;
        printf("PID: %d\n", getpid());
        signal(SIGINT, sigint_handler);
        for (i = 0; i < 1e9; i++) {
                printf("a");
                sleep(1);
        }

        return EXIT_SUCCESS;
}

Espero que o programa execute o loop interno (e, portanto, chame printf ) várias vezes e cada vez que enviar um SIGINT (usando kill -INT $PID ) para executar o manipulador de sinal, que conta como 30.

Ao executar isso, no entanto, observei que o manipulador de sinal é executado uma vez e o próximo sinal encerra o processo. O que causa esse comportamento e como posso corrigir isso?

OS: Linux 4.9.0.

    
por Shrikant Giridhar 14.03.2017 / 08:30

1 resposta

2

signal(2) faz parte da antiga API de sinais, que não é sempre conveniente usar, principalmente porque ...

The only portable use of signal() is to set a signal's disposition to SIG_DFL or SIG_IGN. The semantics when using signal() to establish a signal handler vary across systems (and POSIX.1 explicitly permits this variation); do not use it for this purpose.

In the original UNIX systems, when a handler that was established using signal() was invoked by the delivery of a signal, the disposition of the signal would be reset to SIG_DFL, and the system did not block delivery of further instances of the signal. System V also provides these semantics for signal().

On BSD, when a signal handler is invoked, the signal disposition is not reset, and further instances of the signal are blocked from being delivered while the handler is executing.

The situation on Linux is as follows:

  • The kernel's signal() system call provides System V semantics.
  • By default, in glibc 2 and later, the signal() wrapper function [...] calls sigaction(2) using flags that supply BSD semantics.
  • On glibc 2 and later, if the _BSD_SOURCE feature test macro is not defined, then signal() provides System V semantics. (The default implicit definition of _BSD_SOURCE is not provided if one invokes gcc(1) in one of its standard modes (-std=xxx or -ansi) or... see signal(2).

Em outras palavras, há uma miríade de casos em que um manipulador definido com signal não será usado mais de uma vez: após um sinal, a função que você definiu será descartada e o manipulador será redefinido para o padrão , que, para SIGINT , é para matar o processo.

Embora eu sugira que você leia a man page que acabei de citar, a melhor maneira de lidar com essa situação é provavelmente parar de usar signal e mudar para sigaction . A página man fornecerá todos os detalhes de que você precisa, mas aqui está um exemplo rápido:

void sighandler(int signum);

int main(void) {
    struct sigaction sa;
    sa.sa_handler = sighandler;
    sigemptyset(&(sa.sa_mask));
    sigaddset(&(sa.sa_mask), SIGINT);
    sigaction(SIGINT, &sa, NULL);

    int i;
    for(i = 0; i < 5; i++) {
        printf("Sleeping...\n");
        sleep(5);
        printf("Woke up!\n");
    }
}

void sighandler(int signum) {
    printf("Signal caught!\n");
}

Uma nota importante: como você já deve saber (daí seu loop "infinito" na sua função main ), os sinais irão interromper as chamadas do sistema (como aquelas sleep(3) makes) recepção de sinal, se esse sinal é tratado pelo programa ou não. No meu exemplo acima, o programa irá imprimir Woke up! toda vez que você kill ele sair da chamada de sono prematuramente. Se você der uma olhada na página do manual, verá que a chamada deve retornar o número de segundos restantes para dormir quando for interrompida.

    
por 14.03.2017 / 09:05