C Sinais (signal)

Você aperta Ctrl+C e o programa para. Um programa lê um ponteiro nulo e morre com "Falha de segmentação". O computador vai desligar e avisa os programas para fecharem. Em todos esses casos, quem avisa o programa é um sinal: uma mensagem curtinha que o sistema operacional entrega a um programa que está rodando, interrompendo o que ele estiver fazendo. Nesta aula você vai conhecer os sinais mais comuns, entender os códigos de saída 130, 137, 139 e 143, e fazer o seu programa responder a um sinal, por exemplo salvando o trabalho antes de sair quando o usuário aperta Ctrl+C.

Você vai aprender
  • O que é um sinal e os mais comuns: SIGINT, SIGTERM, SIGKILL, SIGSEGV, SIGABRT, SIGFPE.
  • Por que um programa morto por um sinal termina com o código 128 + o número do sinal.
  • Como mandar um sinal para o próprio programa com raise e para outro com o comando kill.
  • Como tratar o Ctrl+C com signal, um tratador e uma variável volatile sig_atomic_t.
  • O que um tratador pode e não pode fazer.
  • Como ignorar um sinal, por que o SIGKILL não pode ser tratado e quando usar sigaction.

Todos os programas foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c/sinais, com gcc -std=c23 -Wall -Wextra arquivo.c -o programa. Os sinais existem em todo Linux e macOS; no Windows, só uma parte deles (para seguir esta aula no Windows, use o WSL: aula Instalar no Windows e no Mac).


A ideia: a campainha da casa

A Ana está cozinhando. A campainha toca. Ela tem três escolhas:

  • Fazer o de sempre: largar tudo e ir atender. É a ação padrão do sinal; para a maioria dos sinais, o padrão é terminar o programa.
  • Ignorar: continuar cozinhando como se nada tivesse acontecido.
  • Ter um combinado: "se tocar, desligo o fogão primeiro e depois atendo". É um tratador (em inglês, handler): uma função sua que roda quando o sinal chega.

E existe um toque que não dá para ignorar: os bombeiros arrombando a porta. Esse é o SIGKILL, que nenhum programa consegue tratar nem ignorar.


1. Os sinais que você já viu

Cada sinal tem um número e um nome que começa com SIG (de signal, sinal). O comando kill -l ("list", listar) do terminal mostra todos; as três primeiras linhas têm os mais conhecidos:

$ kill -l | head -3
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM

Quando um programa morre por causa de um sinal, o terminal (o Bash) mostra o código de saída 128 + o número do sinal. Você já encontrou vários deles nas aulas anteriores:

SinalNºQuem manda, e quandoCódigo de saídaO que o Bash escreve
SIGINT2O terminal, quando você aperta Ctrl+C ("interrupt", interromper)130(nada, só pula uma linha)
SIGABRT6O próprio programa, com abort() ou um assert falho (aula Testes)134Abortado (imagem do núcleo gravada)
SIGFPE8O processador, numa divisão inteira por zero136Exceção de ponto flutuante (imagem do núcleo gravada)
SIGKILL9Alguém que quer o programa morto já (kill -9); não dá para tratar137Morto
SIGSEGV11O processador, quando o programa mexe em memória que não é dele (aula Ponteiro Nulo)139Falha de segmentação (imagem do núcleo gravada)
SIGTERM15Um pedido educado para terminar ("terminate"): o comando kill sem opções, o desligamento do computador143Terminado
SIGHUP1O terminal foi fechado ("hang up", desligar o telefone)129Desligar
SIGUSR1, SIGUSR210, 12Livres para o seu programa usar como quiser ("user", usuário)138, 140Sinal 1 definido pelo usuário

Os números da tabela são os do Linux. Só seis sinais fazem parte do C padrão (SIGINT, SIGTERM, SIGABRT, SIGFPE, SIGSEGV e SIGILL); os outros vêm do POSIX. No código, use sempre os nomes, nunca os números.


2. raise: mandar um sinal para si mesmo

O jeito mais simples de ver um sinal funcionando é o programa mandar um para ele mesmo, com raise ("levantar", erguer a mão), do cabeçalho <signal.h>:

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

int main(void) {
    printf("Antes do sinal.\n");
    raise(SIGTERM);
    printf("Depois do sinal.\n");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra raise.c -o raise
$ ./raise
Antes do sinal.
Terminado                  ./raise
$ echo $?
143

O raise(SIGTERM) entregou o sinal na hora. Como o programa não disse o que fazer com ele, valeu a ação padrão do SIGTERM: terminar. O "Depois do sinal" nunca apareceu, o return 0 nunca rodou, e o Bash avisou Terminado com o código 143 (128 + 15).

Experimente: o editor do guia sabe entregar sinais com raise. Rode e veja o Terminado e o 143. Depois troque SIGTERM por SIGINT, SIGABRT, SIGSEGV e SIGKILL, um de cada vez, e confira cada código de saída com a tabela da seção 1.

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

int main(void) {
    printf("Antes do sinal.\n");
    raise(SIGTERM);
    printf("Depois do sinal.\n");
    return 0;
}

3. O Ctrl+C é um sinal

Este programa trabalha por 10 segundos, mostrando cada segundo:

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

int main(void) {
    for (int s = 1; s <= 10; s++) {
        printf("Trabalhando... %d s\n", s);
        sleep(1);
    }
    printf("Terminei!\n");
    return 0;
}

O sleep(1), do <unistd.h> (POSIX), faz o programa dormir 1 segundo. A Ana roda e, no segundo 2, perde a paciência e aperta Ctrl+C (o terminal mostra ^C):

$ gcc -std=c23 -Wall -Wextra contar.c -o contar
$ ./contar
Trabalhando... 1 s
Trabalhando... 2 s
^C
$ echo $?
130

Quando você aperta Ctrl+C, quem mata o programa não é o teclado: o terminal manda o sinal SIGINT ao programa que está rodando, e a ação padrão do SIGINT é terminar. O código 130 é 128 + 2. Para um contador, tudo bem. Mas e se o programa estivesse no meio de gravar a lista de compras? O arquivo ficaria pela metade.


4. signal: o seu tratador

Com signal, você diz ao sistema "quando chegar este sinal, chame esta função". O jeito seguro de usar é o tratador só anotar que o sinal chegou, e o programa principal decidir com calma o que fazer:

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

volatile sig_atomic_t parar = 0;

void pedir_parada(int sinal) {
    (void) sinal;
    parar = 1;
}

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

    int s = 0;
    while (!parar && s < 10) {
        s++;
        printf("Trabalhando... %d s\n", s);
        sleep(1);
    }

    if (parar) {
        printf("\nRecebi o Ctrl+C. Salvando a lista antes de sair...\n");
    }
    printf("Tchau!\n");
    return 0;
}
volatile sig_atomic_t parar = 0;
A "anotação". Uma variável global, para o tratador e o main enxergarem a mesma. sig_atomic_t é um inteiro que o processador lê e escreve de uma vez só, sem o sinal poder chegar "no meio" da escrita; volatile obriga o laço a reler a variável a cada volta (sem ele, com -O2, o laço pode nunca ver a mudança: aula volatile e restrict).
void pedir_parada(int sinal)
O tratador. Tem de ter exatamente este formato: recebe um int (o número do sinal que chegou) e não devolve nada.
(void) sinal;
"Sei que não uso este parâmetro." Sem isso, o -Wextra avisa unused parameter.
parar = 1;
Tudo o que o tratador faz. Curto de propósito (a próxima seção explica).
signal(SIGINT, pedir_parada)
Instala o tratador: daqui em diante, um SIGINT chama pedir_parada em vez de matar o programa. Repare que é o nome da função, sem parênteses: você passa a função, não a chama (aula Ponteiros para Funções).
while (!parar && s < 10)
O laço confere a anotação a cada volta.
if (parar)
Fora do tratador, com calma, o programa faz o que precisa: salvar, fechar arquivos, avisar.
$ gcc -std=c23 -Wall -Wextra tratador.c -o tratador
$ ./tratador
Trabalhando... 1 s
Trabalhando... 2 s
Trabalhando... 3 s
^C
Recebi o Ctrl+C. Salvando a lista antes de sair...
Tchau!
$ echo $?
0

O mesmo Ctrl+C, outro final: o programa terminou do jeito dele, com código 0. E a mensagem apareceu na hora, sem esperar o segundo acabar: quando um sinal chega e é tratado, o sleep é interrompido e volta mais cedo.

O que um tratador pode fazer

O sinal pode chegar em qualquer instante: até no meio de um printf ou de um malloc do programa principal. Se o tratador chamar printf nessa hora, as duas chamadas mexem nas mesmas estruturas internas ao mesmo tempo, e o resultado pode ser texto embaralhado ou o programa travado para sempre. O padrão C só garante um punhado de coisas dentro de um tratador:

Dentro do tratadorPode?
Escrever numa variável volatile sig_atomic_tSim: é o uso recomendado
_Exit(código), abort(), signal do mesmo sinalSim (padrão C)
write (do <unistd.h>), para mostrar uma mensagem fixaSim, no POSIX, que tem uma lista maior de funções seguras (man signal-safety)
printf, malloc, free, fopen, exitNão: funcionam quase sempre, e falham na pior hora

5. kill: mandar sinais de fora

Qualquer programa pode receber sinais de outro programa. No terminal, quem manda é o comando kill (o nome é "matar", mas ele manda qualquer sinal). Ele precisa do PID ("process ID", o número do processo; aula Processos). Este programa simula o caixa da lojinha: ele fica esperando, e qualquer SIGINT ou SIGTERM o faz fechar com calma.

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

volatile sig_atomic_t sinal_recebido = 0;

void anotar(int sinal) {
    sinal_recebido = sinal;
}

int main(void) {
    signal(SIGINT, anotar);
    signal(SIGTERM, anotar);
    printf("Caixa da loja aberto (PID %d).\n", (int) getpid());

    while (sinal_recebido == 0) {
        sleep(1);
    }

    printf("Chegou o sinal %d. Fechando o caixa com calma.\n", (int) sinal_recebido);
    return 0;
}
sinal_recebido = sinal;
O mesmo tratador para os dois sinais. Agora o parâmetro é usado: ele diz qual sinal chegou.
getpid()
"Get process ID": o número deste processo, que o kill precisa.
while (sinal_recebido == 0) { sleep(1); }
Espera, dormindo de segundo em segundo, até chegar um sinal.

Na vida real, a Ana deixaria o caixa rodando num terminal e mandaria o kill de outro. Aqui, para caber numa linha só: o & roda o programa "em segundo plano", o $! é o PID dele, e o wait espera ele terminar e devolve o código de saída.

$ gcc -std=c23 -Wall -Wextra loja.c -o loja
$ ./loja & sleep 1; kill -TERM $!; wait $!; echo "código: $?"
[1] 4
Caixa da loja aberto (PID 4).
Chegou o sinal 15. Fechando o caixa com calma.
[1]+  Concluído                 ./loja
código: 0
$ ./loja & sleep 1; kill -INT $!; wait $!; echo "código: $?"
[1] 6
Caixa da loja aberto (PID 6).
Chegou o sinal 2. Fechando o caixa com calma.
[1]+  Concluído                 ./loja
código: 0
$ ./loja & sleep 1; kill -KILL $!; wait $!; echo "código: $?"
[1] 8
Caixa da loja aberto (PID 8).
[1]+  Morto                      ./loja
código: 137

Com SIGTERM e SIGINT, o tratador anotou e o caixa fechou com calma (código 0). Com SIGKILL, o tratador nem foi chamado: o sistema removeu o programa na hora (Morto, 137). É por isso que os administradores tentam primeiro kill PID (que manda SIGTERM, o pedido educado) e só depois, se o programa não sair, kill -9 PID (o SIGKILL).

O programa contar.c, que não tem tratador, morre com o kill comum:

$ ./contar & sleep 2.5; kill $!; wait $!; echo "código: $?"
[1] 12
Trabalhando... 1 s
Trabalhando... 2 s
Trabalhando... 3 s
[1]+  Terminado                  ./contar
código: 143
No editor do guia não há sinais vindos de fora: não existe outro terminal para mandar um kill, e não dá para apertar Ctrl+C no meio de um sleep para gerar um SIGINT. Lá, só o raise funciona. Os tratadores, sim, rodam normalmente: é o que as caixas desta aula usam. (O Ctrl+C no terminal da caixa e o botão Parar encerram o programa por fora, sem passar pelo tratador: aparece [programa interrompido].)

6. Ignorar e voltar ao padrão: SIG_IGN e SIG_DFL

No lugar de uma função, o signal aceita dois valores especiais:

  • SIG_IGN ("ignore"): ignorar o sinal, como se nunca tivesse chegado;
  • SIG_DFL ("default"): voltar à ação padrão.
#include <stdio.h>
#include <signal.h>

int main(void) {
    signal(SIGINT, SIG_IGN);
    printf("Vou mandar um Ctrl+C para mim mesmo...\n");
    raise(SIGINT);
    printf("Ainda estou aqui!\n");

    signal(SIGINT, SIG_DFL);
    printf("Agora de novo, com o comportamento padrão...\n");
    raise(SIGINT);
    printf("Esta linha não aparece.\n");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra ignorar.c -o ignorar
$ ./ignorar
Vou mandar um Ctrl+C para mim mesmo...
Ainda estou aqui!
Agora de novo, com o comportamento padrão...

$ echo $?
130

Ignorar o Ctrl+C serve, por exemplo, num programa que está gravando algo que não pode ser interrompido: ignore durante a gravação e volte ao padrão no fim. Mas cuidado: o usuário fica sem o jeito mais fácil de parar o programa.

SIGKILL e SIGSTOP não podem ser tratados

Imagine um programa com defeito que ignora todos os sinais: ninguém conseguiria pará-lo. Por isso o sistema reserva dois sinais que nenhum programa pode tratar nem ignorar: SIGKILL (matar) e SIGSTOP (pausar). Quem tenta recebe um erro:

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

void anotar(int sinal) {
    (void) sinal;
}

int main(void) {
    if (signal(SIGKILL, anotar) == SIG_ERR) {
        perror("signal(SIGKILL)");
    }
    if (signal(SIGTERM, anotar) == SIG_ERR) {
        perror("signal(SIGTERM)");
    } else {
        printf("SIGTERM: tratador instalado.\n");
    }
    return 0;
}
$ gcc -std=c23 -Wall -Wextra sigkill.c -o sigkill
$ ./sigkill
signal(SIGKILL): Invalid argument
SIGTERM: tratador instalado.

Quando falha, o signal devolve SIG_ERR e põe o motivo em errno: aqui, EINVAL, "argumento inválido" (aula errno e perror). O editor do guia dá a mesma resposta.

Experimente: rode e veja o programa sobreviver ao primeiro SIGINT e morrer no segundo (código 130). Depois apague a linha signal(SIGINT, SIG_IGN);: em que ponto o programa morre agora? E se você trocar o SIG_DFL por SIG_IGN?

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

int main(void) {
    signal(SIGINT, SIG_IGN);
    printf("Vou mandar um Ctrl+C para mim mesmo...\n");
    raise(SIGINT);
    printf("Ainda estou aqui!\n");

    signal(SIGINT, SIG_DFL);
    printf("Agora de novo, com o comportamento padrão...\n");
    raise(SIGINT);
    printf("Esta linha não aparece.\n");
    return 0;
}

7. O signal pode esquecer o tratador: sigaction

O signal é do C padrão desde 1989, e o padrão deixou um detalhe em aberto: depois de chamar o tratador uma vez, o sistema pode voltar o sinal ao padrão. Cada biblioteca escolheu um jeito. Veja este programa, que conta quantos SIGINT recebeu:

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

volatile sig_atomic_t vezes = 0;

void contar(int sinal) {
    (void) sinal;
    vezes++;
}

int main(void) {
    signal(SIGINT, contar);
    raise(SIGINT);
    printf("Depois do 1º sinal: vezes = %d\n", (int) vezes);
    raise(SIGINT);
    printf("Depois do 2º sinal: vezes = %d\n", (int) vezes);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra duas.c -o duas
$ ./duas
Depois do 1º sinal: vezes = 1

$ echo $?
130
$ gcc -Wall -Wextra duas.c -o duas-gnu        « sem o -std=c23
$ ./duas-gnu
Depois do 1º sinal: vezes = 1
Depois do 2º sinal: vezes = 2

Com -std=c23 (C puro), a glibc segue o comportamento antigo do Unix: o tratador vale uma vez só, e o segundo SIGINT matou o programa. Sem o -std, o gcc usa o modo "GNU", e a glibc mantém o tratador. No editor do guia (outra biblioteca C), o tratador também fica: o programa mostra as duas linhas. O mesmo código, três comportamentos possíveis, todos permitidos pelo padrão.

A solução do POSIX é o sigaction ("signal action", a ação do sinal), que existe em todo Linux e macOS e se comporta igual em todos:

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <signal.h>

volatile sig_atomic_t vezes = 0;

void contar(int sinal) {
    (void) sinal;
    vezes++;
}

int main(void) {
    struct sigaction acao = {0};
    acao.sa_handler = contar;
    sigemptyset(&acao.sa_mask);
    sigaction(SIGINT, &acao, NULL);

    raise(SIGINT);
    printf("Depois do 1º sinal: vezes = %d\n", (int) vezes);
    raise(SIGINT);
    printf("Depois do 2º sinal: vezes = %d\n", (int) vezes);
    return 0;
}
LinhaO que faz
#define _POSIX_C_SOURCE 200809LPede ao cabeçalho as funções do POSIX. Com -std=c23, sem esta linha, o sigaction não existe (aula Compilação Condicional).
struct sigaction acao = {0};Uma struct com a "ficha" do sinal, toda zerada. Tem o mesmo nome da função (o C deixa, porque uma é struct sigaction).
acao.sa_handler = contar;O tratador (ou SIG_IGN, SIG_DFL).
sigemptyset(&acao.sa_mask);A lista de outros sinais a segurar enquanto o tratador roda: nenhum.
sa_flags (ficou 0)Opções. Com 0, o tratador fica instalado para sempre. SA_RESTART faz funções de leitura interrompidas pelo sinal recomeçarem sozinhas.
sigaction(SIGINT, &acao, NULL)Instala. O NULL é onde guardar a ficha antiga (não precisamos). Devolve -1 se falhar.
$ gcc -std=c23 -Wall -Wextra duas2.c -o duas2
$ ./duas2
Depois do 1º sinal: vezes = 1
Depois do 2º sinal: vezes = 2

O sigaction não existe no editor do guia (a biblioteca dele só tem signal e raise): lá, struct sigaction dá error: variable has incomplete type 'struct sigaction'. Por isso as caixas desta aula usam signal, com um sinal só por tratador.


signal ou sigaction: qual usar?

FormaQuando usar
signal(sinal, tratador)Programas que precisam ser C puro (rodar até no Windows), ou que só esperam o sinal uma vez para encerrar com calma (o caso mais comum).
sigactionProgramas para Linux e macOS que recebem o mesmo sinal várias vezes ou precisam das opções (SA_RESTART). É o que os programas de verdade usam.
signal(sinal, SIG_IGN)Ignorar um sinal por um tempo (lembre de voltar com SIG_DFL).
raise(sinal)Mandar um sinal para o próprio programa (testes, ou raise(SIGABRT) como o abort).

Quando dá errado

error: passing argument 2 of ‘signal’ from incompatible pointer type

O tratador foi escrito sem o parâmetro: void tratar(void).

$ gcc -std=c23 -Wall -Wextra assinatura.c -o assinatura
assinatura.c: In function ‘main’:
assinatura.c:9:20: error: passing argument 2 of ‘signal’ from incompatible pointer type [-Wincompatible-pointer-types]
    9 |     signal(SIGINT, tratar);
      |                    ^~~~~~
      |                    |
      |                    void (*)(void)
In file included from /usr/include/features.h:539,
                 from /usr/include/x86_64-linux-gnu/bits/libc-header-start.h:33,
                 from /usr/include/stdio.h:28,
                 from assinatura.c:1:
/usr/include/signal.h:93:23: note: expected ‘__sighandler_t’ {aka ‘void (*)(int)’} but argument is of type ‘void (*)(void)’
   93 | extern __sighandler_t __REDIRECT_NTH (signal,
      |                       ^~~~~~~~~~~~~~
assinatura.c:4:6: note: ‘tratar’ declared here
    4 | void tratar(void) {
      |      ^~~~~~
In file included from assinatura.c:2:
/usr/include/signal.h:72:16: note: ‘__sighandler_t’ declared here
   72 | typedef void (*__sighandler_t) (int);
      |                ^~~~~~~~~~~~~~

O que significa: o signal espera void (*)(int), "ponteiro para uma função que recebe um int e não devolve nada", e recebeu void (*)(void). A mensagem é longa porque mostra onde, dentro do signal.h, está o tipo esperado. No editor, o Clang diz incompatible function pointer types passing 'void (void)' to parameter of type 'void (*)(int)'.

Como consertar: void tratar(int sinal), com (void) sinal; se não for usar o número.

warning: unused parameter ‘sinal’ [-Wunused-parameter]
$ gcc -std=c23 -Wall -Wextra semvoid.c -o semvoid
semvoid.c: In function ‘pedir_parada’:
semvoid.c:5:23: warning: unused parameter ‘sinal’ [-Wunused-parameter]
    5 | void pedir_parada(int sinal) {
      |                   ~~~~^~~~~

O que significa: o tratador recebe o número do sinal e não usa. Não é erro: o formato void f(int) é obrigatório mesmo assim.

Como consertar: (void) sinal; na primeira linha do tratador. No C23 também dá para omitir o nome: void pedir_parada(int) C23.

O segundo Ctrl+C mata o programa, mesmo com tratador

O que significa: com -std=c23 no Linux, o signal vale uma vez só (o duas.c da seção 7).

Como consertar: use sigaction. (O truque antigo era chamar signal(sinal, tratador) de novo dentro do próprio tratador.)

Com -O2, o programa ignora o Ctrl+C e roda para sempre

O que significa: a variável da anotação não é volatile. O laço leu parar uma vez, guardou num registrador e nunca mais olhou a memória. A aula volatile e restrict mostra esse caso com um alarme: sem volatile, o timeout teve de matar o programa.

Como consertar: volatile sig_atomic_t parar = 0;.

O programa trava (ou mistura letras) de vez em quando ao receber um sinal

O que significa: o tratador chama printf, malloc ou outra função que não é segura em tratadores, e o sinal chegou bem quando o programa principal estava dentro dela. Acontece uma vez em mil, por isso é tão difícil de achar.

Como consertar: deixe no tratador só a anotação na variável, e faça o resto no programa principal.


Referência rápida

EscritaO que faz
#include <signal.h>Traz signal, raise, sig_atomic_t e os nomes dos sinais
void tratador(int sinal) { … }O formato obrigatório de um tratador
volatile sig_atomic_t x = 0;A variável que o tratador pode mudar com segurança
signal(SIGINT, tratador)Instala o tratador; devolve o anterior, ou SIG_ERR se falhar
signal(SIGINT, SIG_IGN) / SIG_DFLIgnorar / voltar à ação padrão
raise(SIGTERM)Manda o sinal para o próprio programa
sigaction(SIGINT, &acao, NULL)A forma do POSIX, sem surpresas (_POSIX_C_SOURCE)
kill PID / kill -INT PID / kill -9 PIDNo terminal: manda SIGTERM / SIGINT / SIGKILL
kill -lNo terminal: lista os sinais
Código 128 + nMorto pelo sinal n: 130 (SIGINT), 134, 136, 137, 139, 143 (SIGTERM)

Exercícios

1. O tipo da anotação

Complete a declaração da variável que o tratador muda:

volatile parar = 0;

2. Instalar o tratador

Complete para chamar pedir_parada quando o usuário apertar Ctrl+C:

signal(, pedir_parada);

3. Ignorar

Complete para ignorar o SIGINT:

signal(SIGINT, );

4. Um programa terminou com o código 143. O que aconteceu?

5. Qual sinal nenhum programa consegue tratar nem ignorar?

6. Qual é o jeito mais seguro de escrever um tratador?

Missão final: fechar o caixa com calma

Missão: no pedido 3, alguém manda um SIGTERM ao caixa (aqui, simulado com raise). Hoje o programa morre na hora (Terminado, 143) e perde os pedidos. O tratador já está escrito; falta instalá-lo no começo do main, para o laço parar com calma. A saída deve ser:

Atendendo o pedido 1
Atendendo o pedido 2
Atendendo o pedido 3
Recebi SIGTERM. Salvei os pedidos antes de sair.
Caixa fechado.
#include <stdio.h>
#include <signal.h>

volatile sig_atomic_t parar = 0;

void pedir_parada(int sinal) {
    (void) sinal;
    parar = 1;
}

int main(void) {
    // instale o tratador aqui

    for (int pedido = 1; pedido <= 5 && !parar; pedido++) {
        printf("Atendendo o pedido %d\n", pedido);
        if (pedido == 3) {
            raise(SIGTERM);       // alguém pediu para o programa parar
        }
    }

    if (parar) {
        printf("Recebi SIGTERM. Salvei os pedidos antes de sair.\n");
    }
    printf("Caixa fechado.\n");
    return 0;
}
#include <stdio.h>
#include <signal.h>

volatile sig_atomic_t parar = 0;

void pedir_parada(int sinal) {
    (void) sinal;
    parar = 1;
}

int main(void) {
    // instale o tratador aqui
    signal(SIGTERM, pedir_parada);

    for (int pedido = 1; pedido <= 5 && !parar; pedido++) {
        printf("Atendendo o pedido %d\n", pedido);
        if (pedido == 3) {
            raise(SIGTERM);       // alguém pediu para o programa parar
        }
    }

    if (parar) {
        printf("Recebi SIGTERM. Salvei os pedidos antes de sair.\n");
    }
    printf("Caixa fechado.\n");
    return 0;
}

No terminal da Ana, a solução dá a mesma saída:

$ gcc -std=c23 -Wall -Wextra pedidos.c -o pedidos
$ ./pedidos
Atendendo o pedido 1
Atendendo o pedido 2
Atendendo o pedido 3
Recebi SIGTERM. Salvei os pedidos antes de sair.
Caixa fechado.

Resumo

  • Um sinal é um aviso do sistema a um programa: SIGINT (Ctrl+C), SIGTERM (pedido para terminar), SIGKILL (morte imediata), SIGSEGV, SIGABRT, SIGFPE.
  • Morto por um sinal, o programa termina com o código 128 + o número: 130, 134, 136, 137, 139, 143.
  • raise(sinal) manda um sinal para o próprio programa; o comando kill manda para outro, pelo PID.
  • signal(sinal, tratador) instala uma função void f(int). O tratador só deve mudar uma variável volatile sig_atomic_t; o programa principal faz o resto.
  • SIG_IGN ignora e SIG_DFL volta ao padrão. SIGKILL e SIGSTOP não podem ser tratados.
  • O signal pode valer uma vez só (glibc com -std=c23); para tratar o mesmo sinal várias vezes no Linux e no macOS, use sigaction.
  • No editor do guia: raise e os tratadores funcionam; sinais de fora e sigaction, não.