Processos, jobs, sinais: como o SO organiza execução
Todo programa que roda é um processo. Entender como o SO cria, organiza e termina processos — e como você se comunica com eles via sinais — é essencial para depurar, operar e escrever software que se comporta corretamente em produção.
Processos: identidade e estado
Cada processo tem um PID (Process ID) único, e um PPID (Parent PID) — todo processo tem um pai. O processo 1 (init/systemd) é o ancestral de todos.
# Listar processos
ps aux # todos os processos do sistema
ps aux | grep python # filtrar por nome
ps -ef --forest # árvore de processos (mostra hierarquia pai-filho)
pstree # árvore visual bonita
pstree -p # com PIDs
# ps aux output:
# USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
# fernando 1234 2.1 0.5 12345 4096 pts/0 S+ 10:00 0:01 python app.py
# └── estado:
# R = running (na CPU agora)
# S = sleeping (esperando I/O ou sinal)
# D = uninterruptible sleep (I/O crítico, não pode ser interrompido)
# Z = zombie (terminou, aguardando pai coletar)
# T = stopped (SIGSTOP aplicado)
# + = foreground
# Monitorar em tempo real
top # monitor clássico (q para sair)
htop # versão moderna e interativa (F10 para sair)
# Atalhos no htop: F5 = árvore, F9 = kill, F4 = filtroCriando processos: fork + exec
No Linux, há exatamente uma forma de criar um processo: fork(). Ele cria uma cópia quase idêntica do processo atual. Depois, o filho geralmente chama exec() para substituir sua imagem pelo novo programa.
# O modelo fork+exec em shell (o que acontece quando você roda um comando):
$ python script.py
# O shell:
# 1. fork() → cria filho (clone do shell, mesmo código, mesma memória por CoW)
# 2. No filho: exec("python", ["python", "script.py"]) → substituí espaço de memória
# 3. No pai: wait(filho) → bloqueia até filho terminar
# 4. Filho termina → pai lê exit code → shell pronto para próximo comando
# Copy-on-Write (CoW): fork() não copia memória imediatamente
# O filho só recebe uma cópia real de uma página quando a modifica
# Por isso fork() é rápido mesmo para processos com GB de memória O exit code de um processo (0 = sucesso, ≠0 = erro) é como o processo comunica o resultado. Scripts shell dependem disso: && executa o próximo só se o anterior retornou 0; || executa só se retornou ≠0.
echo $? # exit code do último comando
npm install && npm test # test só roda se install OK
npm install || exit 1 # aborta se install falharForeground, background e jobs
# Foreground (padrão): comando ocupa o terminal
python servidor.py # terminal bloqueado até o processo terminar
# Ctrl+C → envia SIGINT (interrompe)
# Ctrl+Z → envia SIGTSTP (pausa, processo vai para background stopped)
# Background: adiciona & no final
python servidor.py & # roda em background, retorna PID
# [1] 12345 ← job number e PID
# Gerenciar jobs do shell atual
jobs # lista jobs em background
jobs -l # com PIDs
fg %1 # traz job 1 para foreground
bg %1 # resume job 1 pausado em background
disown %1 # remove da lista de jobs (não recebe SIGHUP ao fechar terminal)
# Para processos persistirem além do terminal:
nohup python servidor.py & # imune a SIGHUP, saída vai para nohup.out
nohup python servidor.py > app.log 2>&1 & # com log específico
# Solução profissional: systemd ou supervisor
# Não use nohup em produção — use um process managerSinais: comunicação com processos
Sinais são notificações assíncronas enviadas a processos. O processo pode capturar a maioria e decidir o que fazer — exceto SIGKILL e SIGSTOP, que são tratados pelo kernel.
| Sinal | Número | Ação padrão | Uso |
|---|---|---|---|
| SIGHUP | 1 | Terminar | Recarregar config (nginx, sshd) |
| SIGINT | 2 | Terminar | Ctrl+C — interrupção do usuário |
| SIGQUIT | 3 | Core dump | Ctrl+\ — quit com dump |
| SIGTERM | 15 | Terminar | Pedido gracioso de encerramento |
| SIGKILL | 9 | Terminar (kernel) | Forçado, não pode ser capturado |
| SIGSTOP | 19 | Parar (kernel) | Pausa, não pode ser capturado |
| SIGCONT | 18 | Continuar | Retoma processo pausado |
| SIGUSR1 | 10 | Terminar | Definido pelo app (log rotate, etc) |
| SIGUSR2 | 12 | Terminar | Definido pelo app |
# Enviar sinais
kill PID # SIGTERM (padrão) — pedido gracioso
kill -15 PID # SIGTERM explícito
kill -9 PID # SIGKILL — força (último recurso)
kill -1 PID # SIGHUP — nginx usa isso para reload sem downtime
kill -USR1 PID # SIGUSR1 — log rotate em nginx/apache
# Por nome do processo
killall nginx # SIGTERM para todos os processos chamados nginx
killall -9 python # SIGKILL para todos os pythons
pkill -f "python app" # por padrão no nome completo do comando
# SIGKILL é o ÚLTIMO recurso — não permite cleanup:
# → conexões de banco de dados ficam abertas
# → buffers de arquivo não são flushed
# → dados em memória são perdidos
# → state corrompido é possívelCapturando sinais em Python (graceful shutdown)
import signal
import sys
def graceful_shutdown(signum, frame):
print(f"\nRecebeu sinal {signum}. Encerrando graciosamente...")
# 1. Parar de aceitar novas requisições
# 2. Aguardar requisições em andamento terminarem
# 3. Fechar conexões com banco de dados
# 4. Flush de logs
sys.exit(0)
# Registra handlers
signal.signal(signal.SIGTERM, graceful_shutdown)
signal.signal(signal.SIGINT, graceful_shutdown)
# Loop principal
print(f"Servidor rodando PID={os.getpid()}")
while True:
# ... processar requisições
passContainers Docker enviam SIGTERM quando você faz docker stop. Se o processo não tratar SIGTERM, Docker espera 10 segundos e envia SIGKILL. Aplicar graceful shutdown reduz dados corrompidos e conexões zumbis.
Depurando processos problemáticos
# Processo consome muita CPU?
top # ordena por CPU (pressione P)
htop # interativo, F5 para árvore
# Processo usa muita memória?
ps aux --sort=-%mem | head -10 # top 10 por memória
cat /proc/PID/status | grep VmRSS # RAM usada pelo processo
# Processo travado (D state = uninterruptible sleep)?
# Geralmente esperando I/O de disco com problema
# Não responde a sinais, nem SIGKILL — precisa resolver o I/O ou reiniciar o sistema
# O que um processo está fazendo?
strace -p PID # syscalls em tempo real
lsof -p PID # arquivos e sockets abertos
cat /proc/PID/cmdline | tr '' ' ' # comando completo
# Processos zombie acumulando?
# Indica bug no processo pai (não está fazendo wait)
ps aux | grep Z # lista zombies
# Zombies não usam CPU/memória, só PID
# Corrigir: fix no pai, ou matar o pai (zombies são adotados pelo init que faz wait)Fluxo correto para encerrar um processo em produção: kill PID (SIGTERM) → aguardar 5-10s → se ainda vivo: kill -9 PID (SIGKILL). Nunca vá direto para -9 sem tentar SIGTERM primeiro.
Próximo: SSH e chaves — como acessar máquinas remotas com segurança, sem nunca mais digitar senha.
Próximos passos sugeridos
Discussão
Carregando…