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.
- 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
raisee para outro com o comandokill. - Como tratar o Ctrl+C com
signal, um tratador e uma variávelvolatile sig_atomic_t. - O que um tratador pode e não pode fazer.
- Como ignorar um sinal, por que o
SIGKILLnão pode ser tratado e quando usarsigaction.
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:
| Sinal | Nº | Quem manda, e quando | Código de saída | O que o Bash escreve |
|---|---|---|---|---|
SIGINT | 2 | O terminal, quando você aperta Ctrl+C ("interrupt", interromper) | 130 | (nada, só pula uma linha) |
SIGABRT | 6 | O próprio programa, com abort() ou um assert falho (aula Testes) | 134 | Abortado (imagem do núcleo gravada) |
SIGFPE | 8 | O processador, numa divisão inteira por zero | 136 | Exceção de ponto flutuante (imagem do núcleo gravada) |
SIGKILL | 9 | Alguém que quer o programa morto já (kill -9); não dá para tratar | 137 | Morto |
SIGSEGV | 11 | O processador, quando o programa mexe em memória que não é dele (aula Ponteiro Nulo) | 139 | Falha de segmentação (imagem do núcleo gravada) |
SIGTERM | 15 | Um pedido educado para terminar ("terminate"): o comando kill sem opções, o desligamento do computador | 143 | Terminado |
SIGHUP | 1 | O terminal foi fechado ("hang up", desligar o telefone) | 129 | Desligar |
SIGUSR1, SIGUSR2 | 10, 12 | Livres para o seu programa usar como quiser ("user", usuário) | 138, 140 | Sinal 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
mainenxergarem 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;volatileobriga 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
-Wextraavisaunused 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
SIGINTchamapedir_paradaem 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 tratador | Pode? |
|---|---|
Escrever numa variável volatile sig_atomic_t | Sim: é o uso recomendado |
_Exit(código), abort(), signal do mesmo sinal | Sim (padrão C) |
write (do <unistd.h>), para mostrar uma mensagem fixa | Sim, no POSIX, que tem uma lista maior de funções seguras (man signal-safety) |
printf, malloc, free, fopen, exit | Nã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
killprecisa. - 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
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;
}
| Linha | O que faz |
|---|---|
#define _POSIX_C_SOURCE 200809L | Pede 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?
| Forma | Quando 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). |
sigaction | Programas 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 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.)
-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 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
| Escrita | O 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_DFL | Ignorar / 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 PID | No terminal: manda SIGTERM / SIGINT / SIGKILL |
kill -l | No terminal: lista os sinais |
| Código 128 + n | Morto 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:
2. Instalar o tratador
Complete para chamar pedir_parada quando o usuário apertar Ctrl+C:
3. Ignorar
Complete para ignorar o 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 comandokillmanda para outro, pelo PID.signal(sinal, tratador)instala uma funçãovoid f(int). O tratador só deve mudar uma variávelvolatile sig_atomic_t; o programa principal faz o resto.SIG_IGNignora eSIG_DFLvolta ao padrão.SIGKILLeSIGSTOPnão podem ser tratados.- O
signalpode valer uma vez só (glibc com-std=c23); para tratar o mesmo sinal várias vezes no Linux e no macOS, usesigaction. - No editor do guia:
raisee os tratadores funcionam; sinais de fora esigaction, não.