Sinais são um mecanismo fundamental de comunicação entre processos no Linux. Eles permitem que o kernel ou outros processos notifiquem um processo sobre eventos, como solicitações de encerramento, mudanças de terminal ou erros. Para quem escreve shell scripts, entender sinais é crucial para controlar processos de forma eficaz, seja para encerrá-los, recarregá-los ou lidar com interrupções.

Nesta aula, vamos mergulhar no universo dos sinais: como enviá-los com comandos como kill e killall, quais são os sinais mais importantes que você deve conhecer, e como fazer seus scripts tratarem sinais para se comportarem de maneira previsível e amigável.

kill e killall

O comando kill é a ferramenta padrão para enviar sinais a processos. Apesar do nome, ele não se limita a matar processos; você pode enviar qualquer sinal, inclusive aqueles que não encerram o processo, como HUP (para recarregar configurações) ou USR1 (para ações personalizadas). A sintaxe básica é:

kill [-sinal] PID

Se nenhum sinal for especificado, o padrão é TERM (15), que solicita a terminação do processo. Você pode usar o número do sinal ou seu nome precedido de -. Por exemplo, kill -9 1234 envia o sinal KILL (9) ao processo de PID 1234.

Já o comando killall permite enviar sinais a processos pelo nome, em vez do PID. Isso é útil quando você não sabe o PID exato ou quando deseja afetar todos os processos com aquele nome. Por exemplo, killall -9 firefox envia KILL a todos os processos chamados firefox. Cuidado, pois isso pode encerrar várias instâncias de uma vez.

Exemplo prático:

# Enviar TERM para o processo 1234
kill 1234

# Enviar KILL para o processo 1234
kill -9 1234

# Enviar HUP para o processo 1234
kill -HUP 1234

# Matar todos os processos chamados 'meu_script.sh'
killall meu_script.sh

# Enviar TERM para todos os processos do usuário
pkill -u usuario

Além de kill e killall, existem outras ferramentas como pkill e pgrep, que oferecem mais flexibilidade na seleção de processos por nome, usuário ou outros critérios.

Sinais comuns (TERM, KILL, HUP)

Existem muitos sinais no Linux, mas alguns aparecem com mais frequência no dia a dia. Vamos explorar os três mais importantes: TERM, KILL e HUP.

TERM (15) — É o sinal padrão enviado pelo comando kill sem argumentos. Ele solicita que o processo seja encerrado de forma graciosa, permitindo que o processo execute rotinas de limpeza (como fechar arquivos, liberar recursos). A maioria dos programas responde a esse sinal encerrando-se normalmente. Se o processo não terminar, você pode precisar de um sinal mais forte.

KILL (9) — Este sinal é especial: ele não pode ser capturado, ignorado ou tratado pelo processo. O kernel o força a encerrar o processo imediatamente. Isso é útil quando um processo está travado ou não responde ao TERM. No entanto, por ser tão abrupto, pode causar perda de dados ou corrupção de arquivos, pois não há chance de limpeza.

HUP (1) — Originalmente, era enviado quando o terminal de controle era fechado (hang up), mas hoje é frequentemente usado para fazer um daemon recarregar suas configurações sem interromper o serviço. Por exemplo, muitos servidores web (como Nginx) recarregam suas configurações ao receber HUP.

Outros sinais comuns incluem INT (2), gerado por Ctrl+C no terminal; QUIT (3), que gera um core dump; e STOP (19) e CONT (18), que pausam e retomam processos.

Você pode listar todos os sinais disponíveis no seu sistema com kill -l. A tabela a seguir resume os mais usados:

SinalNúmeroDescrição
HUP1Encerrar ou recarregar configurações
INT2Interromper (Ctrl+C)
QUIT3Sair e gerar core dump
KILL9Matar imediatamente (não pode ser capturado)
TERM15Terminar graciosamente (padrão do kill)
STOP19Pausar o processo
CONT18Continuar processo pausado

Enviando sinais

Além dos comandos kill e killall, você pode enviar sinais de outras maneiras, como pelo teclado (Ctrl+C envia INT) ou programaticamente dentro de scripts usando o comando kill com o PID do processo.

Para descobrir o PID de um processo, você pode usar pgrep ou pidof. Por exemplo:

# Descobrir o PID do processo 'meu_servico'
pgrep meu_servico
# ou
pidof meu_servico

Em scripts, é comum combinar pgrep com kill para enviar sinais a processos específicos. Exemplo:

#!/bin/bash
# Envia TERM para todos os processos chamados 'meu_daemon'
for pid in $(pgrep meu_daemon); do
    kill "$pid"
done

Também é possível enviar sinais para grupos de processos usando kill com PID negativo. Por exemplo, kill -TERM -1234 envia o sinal para todos os processos no grupo de ID 1234. Isso é útil ao gerenciar processos filhos.

Outra forma é usar trap dentro do próprio script para enviar sinais a si mesmo, mas isso será abordado na próxima seção sobre tratamento.

Tratamento

Tratar sinais significa definir ações personalizadas quando seu script recebe um sinal. Isso é feito com o comando interno trap. A sintaxe básica é:

trap 'comandos' SINAL

Quando o sinal é recebido, os comandos são executados. Você pode usar isso para limpar arquivos temporários, salvar estado ou simplesmente ignorar o sinal.

Exemplo: um script que cria um arquivo temporário e o remove ao receber TERM ou INT:

#!/bin/bash
temp_file="/tmp/meu_script.$$"
trap 'rm -f "$temp_file"; echo "Script interrompido"; exit 1' TERM INT

echo "Criando arquivo temporário: $temp_file"
touch "$temp_file"

# Simula um trabalho longo
while true; do
    sleep 1
done

Nesse script, se você enviar TERM (por exemplo, com kill) ou pressionar Ctrl+C, o trap remove o arquivo e sai com código 1.

Você também pode ignorar um sinal usando trap '' SINAL. Isso é útil para evitar que Ctrl+C interrompa seu script em momentos críticos. No entanto, lembre-se de que KILL não pode ser capturado ou ignorado.

Outra técnica é usar trap para recarregar configurações quando recebe HUP, como fazem muitos daemons. Exemplo:

#!/bin/bash
config_file="/etc/meu_servico.conf"
reload_config() {
    echo "Recarregando configurações..."
    # Lê o arquivo de configuração
}

trap reload_config HUP

# Loop principal do serviço
while true; do
    sleep 10
done

Assim, ao enviar HUP para o processo, ele executa a função reload_config sem interromper o serviço.

Além de trap, você pode usar kill -l para listar sinais e kill -0 para verificar se um processo existe (sem enviar sinal). Essas ferramentas são úteis no dia a dia do administrador de sistemas.

Boas Práticas e Observações Finais

Ao trabalhar com sinais, siga estas boas práticas:

  • Use TERM como sinal padrão para encerrar processos, reservando KILL apenas para casos extremos.
  • Sempre limpe recursos (arquivos temporários, locks) em traps de sinais para evitar lixo no sistema.
  • Documente os sinais que seu script trata para outros usuários.
  • Teste o tratamento de sinais em um ambiente controlado.

Lembre-se de que KILL é um sinal que não pode ser capturado, então seu script não pode se proteger contra ele. Por isso, é importante que seus scripts sejam capazes de lidar com TERM e INT adequadamente.

Com esse conhecimento, você está pronto para controlar processos de forma mais refinada em seus scripts.

Referências

Exercícios

  1. Escreva um comando para enviar o sinal TERM ao processo com PID 1234.
  2. ✓ Resposta:
    kill 1234
  3. Qual sinal não pode ser capturado ou ignorado por um processo? Dê o nome e o número.
  4. ✓ Resposta: O sinal KILL (9). Ele é enviado diretamente ao kernel e não pode ser capturado, ignorado ou tratado pelo processo.
  5. Como você faria para que um script execute uma função de limpeza quando receber o sinal INT (Ctrl+C)? Escreva o trecho de código.
  6. ✓ Resposta:
    trap 'limpar' INT
  7. Explique a diferença entre os sinais TERM e KILL.
  8. ✓ Resposta: TERM (15) é o sinal padrão para terminar um processo de forma graciosa, permitindo que ele execute rotinas de limpeza. KILL (9) é um sinal que não pode ser capturado ou ignorado, forçando o encerramento imediato pelo kernel, sem chance de limpeza.
  9. Escreva um script que ignore o sinal INT (Ctrl+C) e continue rodando, mas ao receber TERM, imprima uma mensagem e saia com código 0.
  10. ✓ Resposta:
    #!/bin/bash
    trap '' INT
    trap 'echo "Recebi TERM, saindo..."; exit 0' TERM
    
    while true; do
        sleep 1
    done