Signal handling seguro: sigaction e signalfd
1. Introdução aos Sinais em Unix/Linux
Sinais são interrupções assíncronas enviadas a um processo para notificar eventos específicos. Eles podem ser gerados pelo kernel (como SIGSEGV para falha de segmentação), pelo usuário (Ctrl+C envia SIGINT), ou por outros processos via kill(). Quando um sinal é recebido, o fluxo normal do programa é pausado e um manipulador (handler) é executado.
Sinais comuns incluem:
- SIGINT (2): Interrupção do terminal (Ctrl+C)
- SIGTERM (15): Solicitação de término
- SIGSEGV (11): Violação de memória
- SIGCHLD (17): Filho terminou ou parou
- SIGHUP (1): Hangup do terminal
O principal problema com sinais é a reentrância: manipuladores podem interromper funções não reentrantes como malloc() ou printf(), causando deadlocks ou corrupção de dados. Além disso, a função signal() tradicional tem comportamento inconsistente entre plataformas.
2. A Função sigaction: Controle Avançado sobre Manipuladores
sigaction() é a alternativa moderna e portável a signal(). Ela oferece controle fino sobre o comportamento do manipulador através da estrutura struct sigaction:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
Diferenças entre signal() e sigaction():
- signal() pode redefinir o manipulador para SIG_DFL após o primeiro sinal
- sigaction() permite bloquear outros sinais durante a execução do handler
- sigaction() é POSIX e portável; signal() tem comportamento diferente no System V vs BSD
Exemplo prático: registrando um manipulador para SIGINT:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
volatile sig_atomic_t keep_running = 1;
void handle_sigint(int sig) {
keep_running = 0;
}
int main() {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handle_sigint;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
while (keep_running) {
printf("Trabalhando... PID: %d\n", getpid());
sleep(1);
}
printf("\nSinal recebido. Encerrando.\n");
return 0;
}
3. Configurações de Flags e Máscaras de Sinais
Flags importantes:
- SA_RESTART: Reinicia chamadas de sistema interrompidas (como read())
- SA_SIGINFO: Usa sa_sigaction em vez de sa_handler, fornecendo info estendida
- SA_NOCLDSTOP: Não gera SIGCHLD quando filhos param
- SA_RESETHAND: Reseta o manipulador para SIG_DFL após execução
Uso de sigprocmask() para bloquear sinais durante regiões críticas:
sigset_t block_set;
sigemptyset(&block_set);
sigaddset(&block_set, SIGINT);
sigaddset(&block_set, SIGTERM);
// Bloqueia SIGINT e SIGTERM
sigprocmask(SIG_BLOCK, &block_set, NULL);
// Região crítica - sinais bloqueados
// ... operações seguras ...
// Desbloqueia
sigprocmask(SIG_UNBLOCK, &block_set, NULL);
A máscara em sigaction (sa_mask) define sinais automaticamente bloqueados durante a execução do manipulador, prevenindo interrupções aninhadas:
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGTERM); // Bloqueia SIGTERM durante handler
sigaddset(&sa.sa_mask, SIGHUP); // Bloqueia SIGHUP também
4. Manipuladores com Informação Estendida (SA_SIGINFO)
Com SA_SIGINFO, o handler recebe uma estrutura siginfo_t com detalhes do sinal:
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
void handle_sigsegv(int sig, siginfo_t *info, void *context) {
fprintf(stderr, "Sinal %d recebido\n", sig);
fprintf(stderr, "Endereço da falha: %p\n", info->si_addr);
fprintf(stderr, "PID do emissor: %d\n", info->si_pid);
_exit(1);
}
int main() {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = handle_sigsegv;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGSEGV, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
// Causa falha proposital
int *p = NULL;
*p = 42;
return 0;
}
A estrutura siginfo_t fornece:
- si_signo: Número do sinal
- si_errno: Código de erro
- si_code: Código de origem do sinal
- si_pid: PID do processo emissor
- si_uid: UID do processo emissor
- si_addr: Endereço da falha (para SIGSEGV, SIGBUS, etc.)
- si_value: Valor inteiro ou ponteiro (para sinais com sigqueue())
5. Introdução ao signalfd: Tratamento de Sinais como Eventos de Arquivo
signalfd() converte sinais em descritores de arquivo, permitindo tratamento síncrono via read():
#include <sys/signalfd.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
// Bloqueia os sinais para que não sejam tratados normalmente
if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) {
perror("sigprocmask");
return 1;
}
// Cria o descritor signalfd
int sfd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
if (sfd == -1) {
perror("signalfd");
return 1;
}
struct signalfd_siginfo fdsi;
printf("Aguardando sinais... PID: %d\n", getpid());
while (1) {
ssize_t s = read(sfd, &fdsi, sizeof(fdsi));
if (s != sizeof(fdsi)) {
perror("read");
break;
}
if (fdsi.ssi_signo == SIGINT) {
printf("\nSIGINT recebido! Encerrando.\n");
break;
} else if (fdsi.ssi_signo == SIGTERM) {
printf("\nSIGTERM recebido! Encerrando.\n");
break;
}
}
close(sfd);
return 0;
}
A estrutura signalfd_siginfo contém:
- ssi_signo: Número do sinal
- ssi_errno: Código de erro
- ssi_code: Código de origem
- ssi_pid: PID do emissor
- ssi_uid: UID do emissor
- ssi_fd: Descritor de arquivo associado
- ssi_int / ssi_ptr: Valor ou ponteiro enviado
6. Integração com Loops de Eventos (I/O Multiplexing)
A grande vantagem do signalfd é integrar sinais com loops de I/O multiplexado como epoll:
#include <sys/epoll.h>
#include <sys/signalfd.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
// Configurar signalfd
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
sigprocmask(SIG_BLOCK, &mask, NULL);
int sfd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
if (sfd == -1) {
perror("signalfd");
return 1;
}
// Criar epoll
int epfd = epoll_create1(0);
if (epfd == -1) {
perror("epoll_create1");
return 1;
}
// Adicionar signalfd ao epoll
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sfd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, sfd, &ev) == -1) {
perror("epoll_ctl");
return 1;
}
printf("Servidor rodando. PID: %d\n", getpid());
printf("Aguardando eventos (incluindo sinais)...\n");
struct epoll_event events[10];
struct signalfd_siginfo fdsi;
while (1) {
int nfds = epoll_wait(epfd, events, 10, -1);
if (nfds == -1) {
perror("epoll_wait");
break;
}
for (int i = 0; i < nfds; i++) {
if (events[i].data.fd == sfd) {
// Sinal recebido
ssize_t s = read(sfd, &fdsi, sizeof(fdsi));
if (s != sizeof(fdsi)) continue;
if (fdsi.ssi_signo == SIGINT || fdsi.ssi_signo == SIGTERM) {
printf("Sinal de término recebido. Saindo...\n");
close(sfd);
close(epfd);
return 0;
}
printf("Sinal %d recebido (ignorado)\n", fdsi.ssi_signo);
} else {
// Outros descritores (ex: sockets)
printf("Evento no fd %d\n", events[i].data.fd);
}
}
}
return 0;
}
7. Boas Práticas e Armadilhas Comuns
Nunca usar funções não reentrantes em handlers tradicionais:
- Evite: printf(), malloc(), free(), fprintf(), strtok()
- Use apenas chamadas seguras: write(), sig_atomic_t, _exit()
Variáveis voláteis e sig_atomic_t:
volatile sig_atomic_t flag = 0;
void handler(int sig) {
flag = 1; // Seguro: atribuição atômica
}
Sinais síncronos vs assíncronos:
- Sinais síncronos (SIGSEGV, SIGFPE) são gerados pelo próprio processo e podem ser tratados com mais segurança
- Sinais assíncronos (SIGINT, SIGTERM) podem chegar a qualquer momento, exigindo cuidado redobrado
Preferir signalfd para código moderno: Elimina handlers assíncronos completamente, permitindo tratamento síncrono e seguro dentro do loop principal.
Sempre verificar retornos: sigaction(), signalfd(), sigprocmask() podem falhar; verifique sempre.
Referências
- sigaction(2) - Linux manual page — Documentação oficial da função sigaction, incluindo flags e estrutura siginfo_t
- signalfd(2) - Linux manual page — Documentação oficial do signalfd, com detalhes sobre criação e leitura do descritor
- Signal Handling in Linux - GNU C Library Manual — Capítulo completo sobre tratamento de sinais na glibc, com exemplos e boas práticas
- Beej's Guide to Unix IPC: Signals — Tutorial prático sobre sinais em Unix, incluindo sigaction e signalfd
- Using signalfd with epoll - IBM Developer — Artigo técnico demonstrando integração de signalfd com epoll para loops de eventos unificados