Signals: SIGTERM, SIGKILL, SIGHUP na prática
1. Introdução aos Signals no Bash
Signals são mecanismos de comunicação assíncrona entre processos no Linux/Unix. Eles permitem que o sistema operacional ou um processo notifique outro sobre eventos específicos, como solicitações de encerramento, interrupções ou mudanças de estado. No Bash, entender signals é essencial para controlar processos de forma eficiente, especialmente em scripts que precisam lidar com terminação limpa, recarga de configurações ou recuperação de falhas.
Os três signals mais comuns na prática com Bash são:
- SIGTERM (15): Solicitação educada de encerramento
- SIGKILL (9): Terminação forçada e inegável
- SIGHUP (1): Notificação de desconexão do terminal
No Bash interativo, signals como SIGINT (Ctrl+C) são tratados automaticamente. Já em scripts, você pode capturar e responder a esses sinais usando o comando trap, permitindo controle granular sobre o comportamento dos seus processos.
2. SIGTERM: O Pedido de Encerramento Padrão
SIGTERM é o signal padrão enviado pelo comando kill. Seu comportamento esperado é que o processo realize uma terminação limpa: feche arquivos abertos, libere recursos e finalize operações pendentes.
# Enviando SIGTERM para um processo
kill 1234 # Equivalente a kill -15 1234
kill -15 1234 # Forma explícita
kill -TERM 1234 # Usando nome do signal
Em scripts, você pode capturar SIGTERM para executar ações de limpeza antes de encerrar:
#!/bin/bash
# Script que faz limpeza ao receber SIGTERM
cleanup() {
echo "[$(date)] Recebido SIGTERM. Limpando..."
rm -f /tmp/meu_arquivo_temporario
echo "Arquivos temporários removidos. Encerrando."
exit 0
}
trap cleanup SIGTERM
echo "Processo rodando. PID: $$"
while true; do
echo "Trabalhando..."
sleep 2
done
Execute este script e envie SIGTERM de outro terminal:
kill -TERM <PID_DO_SCRIPT>
3. SIGKILL: A Força Bruta
SIGKILL é o signal mais agressivo. Diferente de SIGTERM, ele não pode ser ignorado, capturado ou tratado pelo processo. O kernel do sistema operacional remove o processo imediatamente, sem qualquer chance de limpeza.
# Enviando SIGKILL
kill -9 1234
kill -KILL 1234
Quando usar SIGKILL: Apenas como último recurso, quando um processo não responde a SIGTERM ou está congelado. Os riscos incluem:
- Perda de dados não salvos
- Arquivos temporários órfãos
- Corrupção de sistemas de arquivos (especialmente em bancos de dados)
- Recursos do sistema (sockets, semáforos) não liberados
# Exemplo: tentar SIGTERM primeiro, depois SIGKILL
for signal in TERM KILL; do
if kill -"$signal" 1234 2>/dev/null; then
echo "Signal $signal enviado com sucesso"
sleep 2
fi
done
4. SIGHUP: Recarregar ou Desconectar
Originalmente, SIGHUP era enviado quando a linha telefônica (hangup) era desconectada. Hoje, indica que o terminal de controle foi fechado. Seu comportamento padrão é encerrar o processo, mas muitos daemons o interpretam como "recarregue suas configurações".
# Enviando SIGHUP para recarregar configurações
kill -HUP $(pgrep nginx) # Recarrega configurações do Nginx
kill -1 $(pgrep sshd) # Recarrega configurações do SSH
Para proteger processos de SIGHUP ao sair do terminal, use nohup ou disown:
# Usando nohup para ignorar SIGHUP
nohup ./meu_script.sh &
nohup python3 servidor.py > log.txt 2>&1 &
# Usando disown em processos já em execução
./meu_script.sh &
disown %1 # Remove job da tabela de jobs do shell
O comando disown remove o processo da lista de jobs do shell, impedindo que SIGHUP seja enviado quando o terminal for fechado.
5. Manipulando Signals com trap
O comando trap é a ferramenta central para manipular signals em scripts Bash. Sua sintaxe básica é:
trap 'comando' SIGNAL
trap 'comando' SIGNAL1 SIGNAL2 # Múltiplos signals
trap '' SIGNAL # Ignorar signal (com limitações)
trap - SIGNAL # Restaurar comportamento padrão
Exemplo prático com múltiplos signals:
#!/bin/bash
# Script robusto com tratamento de signals
arquivo_temp="/tmp/dados_${$}.tmp"
cleanup() {
local signal=$1
echo "[$(date)] Signal $signal recebido. Executando limpeza..."
rm -f "$arquivo_temp"
echo "Arquivo $arquivo_temp removido."
exit 0
}
trap 'cleanup SIGTERM' SIGTERM
trap 'cleanup SIGINT' SIGINT
trap 'cleanup SIGHUP' SIGHUP
echo "Iniciando processamento. PID: $$"
echo "Dados importantes" > "$arquivo_temp"
# Simula trabalho longo
for i in {1..30}; do
echo "Processando etapa $i..."
sleep 1
done
cleanup "normal"
Ignorando signals: Você pode ignorar signals com trap '' SIGNAL, mas isso não funciona para SIGKILL e SIGSTOP:
trap '' SIGTERM # Ignora SIGTERM (mas não funciona para SIGKILL)
6. Signals e Gerenciamento de Processos no Terminal
Signals são fundamentais para gerenciar jobs em background. Além de SIGTERM, SIGKILL e SIGHUP, outros signals importantes incluem SIGSTOP (pausa) e SIGCONT (continua):
# Iniciar processo em background
sleep 100 &
# Pausar o processo
kill -STOP %1
# Verificar status (deve mostrar "Stopped")
jobs
# Continuar o processo
kill -CONT %1
# Encerrar o processo
kill %1
Exemplo prático de pausa e retomada:
#!/bin/bash
# Demonstração de pausa/continuação
echo "Iniciando processo de backup..."
dd if=/dev/zero of=/tmp/teste.img bs=1M count=1000 &
pid=$!
echo "Backup rodando. PID: $pid"
sleep 2
echo "Pausando backup..."
kill -STOP $pid
sleep 3
echo "Retomando backup..."
kill -CONT $pid
wait $pid
echo "Backup concluído."
7. Boas Práticas e Casos Reais
Prefira SIGTERM sobre SIGKILL: Em scripts de shutdown, sempre tente SIGTERM primeiro com um timeout:
#!/bin/bash
# Script de shutdown seguro
graceful_shutdown() {
local pid=$1
local timeout=10
kill -TERM "$pid" 2>/dev/null
# Aguarda o processo encerrar
for ((i=0; i<timeout; i++)); do
if ! kill -0 "$pid" 2>/dev/null; then
echo "Processo $pid encerrado com SIGTERM"
return 0
fi
sleep 1
done
# Timeout: usa SIGKILL
echo "Timeout. Forçando SIGKILL no processo $pid"
kill -KILL "$pid" 2>/dev/null
}
# Uso
graceful_shutdown 1234
Combinando signals com find e xargs:
# Encerrar todos os processos python de um usuário específico
pgrep -u usuario python3 | xargs -r kill -TERM
# Enviar SIGHUP para todos os processos nginx
pgrep nginx | xargs -r kill -HUP
Debugging com logging de signals:
#!/bin/bash
# Script com logging de signals para debugging
log_signal() {
local signal=$1
echo "$(date '+%Y-%m-%d %H:%M:%S') - Signal $signal recebido pelo PID $$" >> /tmp/signal_log.txt
}
trap 'log_signal SIGTERM; exit 0' SIGTERM
trap 'log_signal SIGINT; exit 0' SIGINT
trap 'log_signal SIGHUP' SIGHUP
echo "Processo em execução. PID: $$"
while true; do
sleep 5
done
Para verificar signals recebidos em tempo real, use strace:
strace -e trace=signal -p <PID>
Dominar signals no Bash é essencial para criar scripts robustos e confiáveis. Lembre-se: SIGTERM é para encerramento limpo, SIGHUP para recarga de configurações, e SIGKILL apenas quando não houver alternativa.
Referências
- GNU Bash Manual: Signals — Documentação oficial do Bash sobre signals, incluindo
trape comportamento de signals em scripts. - Linux man page: signal(7) — Página de manual completa sobre signals no Linux, com lista completa e descrições detalhadas.
- Linux man page: kill(1) — Manual do comando
kill, incluindo todas as opções e signals disponíveis. - Linux man page: trap(1p) — Especificação POSIX do comando
trappara shells, com exemplos de uso. - Advanced Bash-Scripting Guide: Signals — Guia avançado de scripting Bash com seção dedicada a signals e tratamento de interrupções.
- Linux Journal: Understanding and Using Linux Signals — Artigo técnico detalhado sobre signals no Linux, com exemplos práticos em C e shell.
- IBM Developer: Signal handling in shell scripts — Tutorial da IBM sobre tratamento de signals em scripts shell, com casos de uso reais.