Dica: Sinais

Não estou falando do filme (que é uma porcaria, por sinal).
Se você faz um programa com um loop infinito:

#include <stdio.h>

int main(void)
{
  for (;;)
    printf("Teste...\n");

  return 0;
}

Quando executa o programa basta teclar um ^C para o programa parar, certo? Isso acontece quando o printf() está em ação, executando a syscall write. Só que num código de produção você pode querer tirar esse poder do usuário. Como fazer?

Se você veio do DOS/Windows, como eu, aprendeu que a função signal() não funciona direito por lá, mas ela é muito útil nos Unixes. Para desabilitar a interrupção pelo teclado basta dizer para signal() ignorar os sinais SIGINT:

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

int main(void)
{
  signal(SIGINT, SIG_IGN);

  for (;;)
    printf("Teste...\n");

  return 0;
}

Agora você não conseguirá mais parar o processo usando ^C.

Sinais são “ganchos” no seu código que são chamados para realizar ações específicas. Podemos pensar nos sinais como sendo interrupções de software. Por exemplo: Se seu processo encontra um access violation (nos unixes isso é chamado de segmentation fault), então seu programa receberá o sinal SIGSEGV do kernel. A ação default é imprimir a string “segmentation fault at …” e abortar a aplicação. Você pode mudar esse comportamento, fornecendo um tratador para esse sinal via chamada para signal. Essa função é definida em signal.h assim:

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

Dá pra reparar que o segundo parâmetro de signal() é um ponteiro para uma função, né? O primeiro parâmetro é uma das constantes SIGxxx definidas em signal.h. A constante SIGINT é o sinal recebido pelo programa se houver uma ação de interrupção pelo teclado (^C). SIGTERM é recebido se o kernel pede o encerramento do programa (isso é visível no procedimento de shutdown do seu Linux!), SIGKILL é recebido pelo processo se você usar um “kill -9 pid” no terminal… E por ai vai. No segundo parâmetro você pode passar o ponteiro para uma função sua ou usar SIG_IGN (ignore) ou SIG_DFL (default).

A função signal() também devolve o ponteiro para a função atribuida antes da alteração.

Alguns poucos sinais não podem ser “capturados”. É o caso de SIGKILL e SIGSTOP (veja o volume 7 das manpages sobre signal [man 7 signal]) — quer dizer, o tratamento desses sinais é inóquo, do ponto de vista do nosso processo. O kernel os tratará sempre! Aliás, instale o pacote manpages-dev para ter acesso às funções da libc nas páginas de manuais:

$ # Instala manuais de "desenvolvimento".
$ sudo apt-get install manpages-dev

$ # vê função signal() no manual das chamadas do sistema (manual 3).
$ man 3 signal

$ # vê detalhes de signal() no manual "Miscelaneous" (manual 7).
$ man 7 signal 

Sinais interessantes:

Alguns sinais podem parecer mais úteis que outros. SIGINT, por exemplo, é um dos mais úteis. SIGALRM pode ser usado para criarmos códigos que lidem com timeout de syscalls que bloqueiam a thread atual em execução. SIGFPE pode ser usada para tratar exceções em operações de ponto flutuante. SIGTERM pode implementar um tratador de housekeeping antes do encerramento do processo. SIGIO pode fazer o tratamento de I/O assíncrona, e por ai vai…

O detalhe é que cada sinal tem um comportamento default. SIGINT, por exemplo, terminará o processo. SIGCHLD, que é enviada ao processo pai se o processo filho, “forkado”, terminar, é totalmente ignorado. De fato, a maioria dos sinais terminará o processo, se não forem tratados. Novamente, as exceções são SIGKILL e SIGSTOP que, não podem ser capturados e só admitem o comportamento padrão (termino e suspensão, respectivamente).

signal() é obsoleta:

Embora a função signal ainda funcione e seja útil, existem algumas extensões que não são possíveis através da chamada a essa função. A função mais recente e recomendada para ajustar os tratadores de sinais é sigaction que usa a estrutura de mesmo nome. Uma maneira de ajustar o tratador de SIGINT para ignorar o sinal, usando sigaction é:

struct sigaction sa = { .sa_handler = SIG_IGN };

sigaction(SIGINT, &sa, NULL);

O último parâmetro de sigaction é um ponteiro para uma estrutura de mesmo nome que receberá a configuração atual do sinal. Não parece haver muita diferença, não é? Acontece que a estrutura sigaction possui alguns membros interessantes:

struct sigaction {
  void     (*sa_handler)(int);
  void     (*sa_sigaction)(int, siginfo_t *, void *);
  sigset_t   sa_mask;
  int        sa_flags;
  void     (*sa_restorer)(void);
};

Os ponteiros sa_handler e sa_sigaction não podem ser usados ao mesmo tempo. O primeiro é o ponteiro para o tratador tradicional, já o segundo é o ponteiro para um tratador estendido que será usado apenas se SA_SIGINFO for informado no membro sa_flags da estrutura. Note o typedef siginfo_t no segundo parâmetro. Ele contém informações adicionais sobre o sinal, como o PID do processo que originou o sinal, o file descriptor que gerou o sinal (se for o caso), etc… A função ainda recebe um terceiro parâmetro contendo o ponteiro para o “contexto” do sinal, se for o caso.

A estrutura ainda possui um conjunto de máscaras de sinais (sinais que devem ser bloquados enquanto o sinal informado está sendo tratado — o próprio sinal sob tratamento é bloqueado automaticamente) e flags. Dos flags, um chama a atenção: Normalmente um sinal “interrompe” uma syscall. Por exemplo: Se estivermos executando algo como “sleep(3600);”, a syscall ficará bloqueada por uma hora, mas se ocorrer um sinal, a syscall é interrompida e ao errno será assinalado EINTR. Nos flags podemos informar que queremos que a syscall seja “retomada” ou “reiniciada”, adicionando a constante SA_RESTART.

Ahhh… o ponteiro sa_restorer é obsoleto e não é usado.

Bloqueando sinais no processo:

Além dos tratadores podemos bloquear alguns sinais no escopo de nosso processo. Por exemplo, o sinal SIGTSTP é disparado quando o usuário tecla ^Z no terminal, parando o processo. Se quisermos que nosso processo não responda a esse sinal podemos fazer:

sigset_t sigset;

/* Inicializa o conjunto de sinais como vazio. */
sigemptyset(&sigset);

/* Adiciona o sinal SIGTSTP ao conjunto. */
sigaddset(SIGTSTP, &sigset);

/* Cria a máscara de bloqueio para o nosso processo. */
sigprocmask(SIG_BLOCK, &sigset, NULL);

O último ponteiro de sigprocmask é usado para obter o conjunto atual de sinais bloqueados. A função aceita três comandos possíveis: SIG_BLOCK, SIG_UNBLOCK e SIG_SETMASK. A diferença é que os dois primeiros só bloquearão ou desbloquearão os sinais contidos no conjunto fornecido, deixando os demais sinais intocados. SIG_SETMASK, por outro lado, ajustará o conjunto de sinais bloquados exatamente como se apresentam no conjunto… A não ser que você saiba o que está fazendo, usar SIG_SETMASK não é uma boa idéia.

Anúncios

Deixe um comentário

Faça o login usando um destes métodos para comentar:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s