🧠FFVAcademy
⚙️

Processos, jobs, sinais: como o SO organiza execução

11 min de leitura·+55 XP
Pré-requisitos (0/1)0%

Recomendamos completar os pré-requisitos antes de seguir, mas nada te impede de continuar.

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 = filtro

Criando 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 falhar

Foreground, 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 manager

Sinais: 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.

SinalNúmeroAção padrãoUso
SIGHUP1TerminarRecarregar config (nginx, sshd)
SIGINT2TerminarCtrl+C — interrupção do usuário
SIGQUIT3Core dumpCtrl+\ — quit com dump
SIGTERM15TerminarPedido gracioso de encerramento
SIGKILL9Terminar (kernel)Forçado, não pode ser capturado
SIGSTOP19Parar (kernel)Pausa, não pode ser capturado
SIGCONT18ContinuarRetoma processo pausado
SIGUSR110TerminarDefinido pelo app (log rotate, etc)
SIGUSR212TerminarDefinido 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ível

Capturando 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
    pass
💡
Containers 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.
🧩

Quiz rápido

3 perguntas · Acerte tudo e ganhe o badge 🎯 Gabarito

Continue lendo