Dia 5: Processos e Jobs no Linux — ps, top, kill e Sinais

No Linux, tudo o que corre é um processo — uma instância de um programa em execução, com o seu próprio espaço de memória, identificadores e prioridade. O kernel atribui a cada processo um PID (Process ID) único e gere o seu acesso ao CPU, memória e ficheiros. Saber listar, monitorizar, suspender, retomar e terminar processos é a base de qualquer administração de sistemas: é o que se faz quando um serviço fica sem resposta, quando o CPU está a 100% sem razão aparente, ou quando se precisa de lançar um job longa-duração em segundo plano.

Este artigo cobre os pilares do dia 5: listar processos com ps, monitorizar em tempo real com top e htop, contolar prioridades com nice e renice, jobs e controlo de sessão com jobs, fg, bg e nohup, e terminar processos com kill e sinais Unix. No fim sabe responder a um servidor lento sem o reiniciar — em vez de kill -9 à primeira queixa, sabe identificar o PID culpado, baixar-lhe a prioridade e só então terminá-lo de forma controlada.

O Que é um Processo? PID, PPID e Estados

Quando se executa um comando no terminal — digamos sleep 60 — o kernel cria um processo. Esse processo recebe um PID (Process IDentifier) único e incremental. O processo que o originou — neste caso a shell — é o seu PPID (Parent PID). Tudo descende de PID 1, que em distribuições modernas é o systemd (noutras era o init System V). Quando o PID 1 morre, o kernel entra em pânico.

Cada processo está, em cada instante, num destes estados (a coluna STAT do ps):

Estado Significado Quando
R (Running) Em execução ou na fila de espera para correr A usar CPU activamente
S (Sleep) Adormecido, à espera de um evento À espera de I/O, timer ou sinal
D (Uninterruptible) Sleep ininterruptível — não responde a sinais À espera de I/O de disco (ex.: fsck)
T (Stopped) Parado por um sinal Depois de Ctrl+Z ou kill -STOP
Z (Zombie) Terminou mas o pai ainda não leu o estado de saída Bug no processo pai — ver Erros Comuns

Existem ainda sufixos que se juntam ao estado principal: + significa processo em foreground (a correr no terminal activo), s indica session leader e l indica processo multi-threaded. Assim, Ss+ significa “session leader, sleeping, foreground” — o que é tipicamente a própria shell.

Para ver o PID da própria shell em uso:

echo $$
# Output: 12345 (o PID da shell actual)

# Confirmar que é real
ps -p $$ -o pid,ppid,comm
#   PID  PPID COMM
# 12345 12330 bash

# O PPID é o terminal ou processo que lançou esta shell

O /proc filesystem — visto no Dia 2 — expõe informação detalhada sobre cada processo em /proc/<PID>/. O ficheiro /proc/<PID>/status contém o estado, UID, memória e muito mais. Todas as ferramentas que vamos ver — ps, top, htop — lêem o /proc por baixo.

Listar Processos com ps

O ps é a ferramenta fundamental para listar processos num dado instante — um snapshot, não tempo real. Aceita opções em três estilos: Unix (com -), BSD (sem -) e GNU longas (com --). A combinação mais útil no dia-a-dia é ps aux (estilo BSD) — lista todos os processos do sistema, com utilizador, PID, CPU, memória e comando completo. A documentação oficial está em ps(1) — man7.org.

# Snapshot de todos os processos (estilo BSD)
ps aux

# Output (primeiras linhas):
# USER         PID %CPU %MEM    VSZ   RSS TTY   STAT START   TIME COMMAND
# root           1  0.0  0.1 169372 13248 ?     Ss   09:00   0:02 /sbin/init
# root           2  0.0  0.0      0     0 ?     S    09:00   0:00 [kthreadd]
# user        1234  0.5  2.3 850340 47000 ?     Sl   09:05   0:01 /usr/bin/python3 app.py
# user        1250  0.0  0.3  21456  6200 pts/0 R+   09:10   0:00 ps aux

Colunas-chave do ps aux:

Coluna Significado
USER Utilizador dono do processo
PID Identificador único do processo
%CPU Percentagem de CPU usada desde o arranque do processo
%MEM Percentagem de memória RAM física usada
VSZ Virtual Memory Size (KiB) — memória virtual total alocada
RSS Resident Set Size (KiB) — memória física efectivamente em uso
STAT Estado do processo (ver tabela acima)
START Hora em que o processo arrancou
TIME Tempo total de CPU acumulado (não tempo decorrido)
COMMAND Comando executado (com argumentos)

Outras variações essenciais:

# Árvore de processos — mostra hierarquia pai/filho
ps axjf
# ou pstree (pacote separado: apt install pstree / dnf install pstree)

# Só os meus processos nesta shell
ps ux

# Informação detalhada de um PID específico
ps -p 1234 -o pid,ppid,user,stat,%cpu,%mem,etime,comm

# Processos de um utilizador específico
ps -u www-data

# Processos ordenados por consumo de memória (top 10)
ps aux --sort=-%mem | head -11

# Processos ordenados por consumo de CPU (top 10)
ps aux --sort=-%cpu | head -11

A opção --forest (ou axjf no estilo BSD) é particularmente útil para entender a hierarquia de processos — por exemplo, confirmar que todos os workers de um serviço descendem do mesmo master. O comando pstree oferece a mesma visão de forma mais visual.

Monitorização em Tempo Real: top e htop

Enquanto o ps dá um snapshot estático, o top mostra os processos em tempo real, actualizados a cada 3 segundos por defeito. É a primeira ferramenta a abrir quando um servidor está lento. Documentação oficial: top(1) — man7.org.

# Abrir o top
top

# Output (resumo do cabeçalho):
# top - 09:30:00 up  3:20,  2 users,  load average: 0.45, 0.30, 0.20
# Tasks: 142 total,   1 running, 141 sleeping,   0 stopped,   0 zombie
# %Cpu(s):  5.0 us,  1.2 sy,  0.0 ni, 93.5 id,  0.3 wa,  0.0 hi,  0.0 si
# MiB Mem :   2048.0 total,    512.0 free,   1024.0 used,    512.0 buff/cache
# MiB Swap:   1024.0 total,   1024.0 free,      0.0 used.    800.0 avail Mem
#
#   PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
#  1234 user      20   0 850340   47000   8000 S   5.0  2.3   0:01.23 python3
#  1250 user      20   0   21456    6200   3100 R   0.3  0.3   0:00.01 top

O cabeçalho do top é tão importante quanto a lista de processos. Os campos-chave:

Campo Interpretação
load average Média de processos na fila de CPU em 1, 5 e 15 minutos. Acima do nº de cores = gargalo
%Cpu(s) us CPU em espaço de utilizador. sy = kernel. id = idle (livre)
%Cpu(s) wa I/O wait — processos à espera de disco. Valor alto = disco lento
MiB Mem Memória total, livre, usada e buff/cache. O buff/cache é recuperável
MiB Swap Swap em uso. Se crescer continuamente, há falta de RAM
PR / NI Prioridade de scheduling (kernel) e valor nice (definido pelo utilizador)

Atalhos essenciais dentro do top:

# Dentro do top:
P   # Ordenar por CPU (defeito)
M   # Ordenar por memória
N   # Ordenar por PID
T   # Ordenar por tempo de CPU
1   # Mostrar CPU por core (expandir)
k   # Matar um processo (pede PID e sinal)
r   # Renice (pede PID e novo valor nice)
q   # Sair

htop é uma alternativa moderna e mais intuitiva ao top: usa cores para indicar tipo de uso de CPU e memória, permite navegar com setas, matar processos com F9 e renice com F7/F8, e mostra a árvore de processos com F5. Não vem instalado por defeito em todas as distribuições — veja htop — Arch Wiki para detalhes.

# Instalar htop
# Debian/Ubuntu:
sudo apt install htop

# Fedora/RHEL/Rocky:
sudo dnf install htop

# Arch:
sudo pacman -S htop

# Executar
htop

Nota: O top está presente em praticamente todas as distribuições Linux — é uma ferramenta POSIX garantida. O htop é opcional. Em servidores minimalistas (containers, imagens cloud) pode não haver htop instalado — é prudente dominar ambos.

Prioridades: nice e renice

O nice value é um número de -20 a 19 que controla a prioridade de scheduling de um processo: -20 é a prioridade mais alta (mais CPU), 19 é a mais baixa. Por defeito, os processos arrancam com nice 0. Só o root pode baixar o nice (valores negativos); um utilizador normal só pode subi-lo (valor positivo). A regra mnemónica é: nice alto = processo “simpático” = cede CPU aos outros. Documentação: nice(1) — man7.org.

# Lançar um comando com nice +10 (baixa prioridade)
nice -n 10 tar czf backup.tar.gz /home/user/

# Lançar com nice -5 (alta prioridade — só root)
sudo nice -n -5 make -j$(nproc)

# Ver o nice actual de um processo
ps -p 1234 -o pid,ni,comm
#   PID  NI COMM
#  1234  10 tar

# Alterar o nice de um processo já a correr (renice)
renice 5 -p 1234

# Renice de todos os processos de um utilizador
sudo renice 10 -u www-data

Cenário típico: um backup noturno que não deve competir com o serviço em produção. Em vez de o deixar a nice 0, arranca-se com nice -n 19 — só usa CPU quando ninguém mais precisa. Inversamente, um processo crítico de monitorização pode ser renice para -5 para garantir que responde mesmo sob carga.

Atenção: O nice value não é uma percentagem de CPU nem um limite (limit). Define apenas a prioridade relativa. Um processo a nice 19 ainda pode usar 100% de CPU se não houver mais nada a pedir o CPU. Para limitar de facto o consumo, usa-se o systemd-run com CPUQuota=50% ou cgroups — fora do âmbito deste artigo.

Jobs e Controlo de Sessão — bg, fg, nohup

Quando se executa um comando no terminal, ele corre em foreground — a shell fica bloqueada até o comando terminar. Mas é possível enviar o comando para background (continua a correr, a shell fica livre), trazê-lo de volta para foreground, suspender e retomar. Este mecanismo chama-se job control e é uma funcionalidade da shell (bash, zsh).

# Lançar um comando em background directamente (com &)
sleep 300 &
# [1] 13456   — o [1] é o job number, 13456 é o PID

# Continuar a trabalhar na shell...
echo "A shell está livre"

# Ver os jobs activos
jobs
# [1]+  Running                 sleep 300 &

# Suspender um processo em foreground com Ctrl+Z
# (envia SIGSTOP ao processo)
sleep 60
# ^Z
# [2]+  Stopped                 sleep 60

# Retomar em background
bg %2
# [2]+ sleep 60 &

# Trazer um job de background para foreground
fg %1
# sleep 300

# Enviar sinal a um job por número
kill %1     # termina o job 1

O problema do job control é que, quando a shell termina — por exemplo, ao fechar o terminal ou uma sessão SSH — todos os jobs recebem SIGHUP (Signal HangUP) e morrem. Para evitar isto, usa-se o nohup (No HangUP) ou disown.

# nohup: ignora SIGHUP, output vai para nohup.out
nohup ./script-longo.sh &
# nohup: ignoring input and appending output to 'nohup.out'

# Combinar nohup + nice para um backup em background
nohup nice -n 10 tar czf /backup/home.tar.gz /home/ &

# Disown: remover um job já a correr do controlo da shell
./script.sh &
disown %1
# Agora o processo sobrevive ao fecho do terminal

# Melhor prática moderna: systemd-run para serviços temporários
systemd-run --user --unit=backup-home \
  /usr/bin/tar czf /backup/home.tar.gz /home/

Nota: O nohup e disown resolvem o problema de um processo morrer ao fechar o terminal, mas em servidores modernos a abordagem correcta é usar systemd-run ou criar uma unit de systemd. O systemd gere reinícios, logs e dependências — algo que nohup não faz. Veja systemd — Arch Wiki para aprofundar.

kill e Sinais Unix — TERM, KILL, HUP e Outros

O kill não mata — envia sinais. O nome é enganador. Um sinal é uma notificação assíncrona enviada pelo kernel a um processo: “alguma coisa aconteceu, reage”. O processo pode capturar o sinal, ignorá-lo (alguns) ou usar o comportamento por defeito. Quando se faz Ctrl+C no terminal, a shell envia SIGINT (Signal Interrupt) ao processo em foreground. Documentação: signal(7) — man7.org e kill(1) — man7.org.

Os sinais mais importantes no dia-a-dia:

Sinal Número Acção
SIGTERM 15 Pedido de terminação graciosa — defeito do kill. O processo pode capturar e fazer cleanup
SIGKILL 9 Terminação imediata — não pode ser capturado nem ignorado. Último recurso
SIGINT 2 Interrompido pelo utilizador (Ctrl+C)
SIGHUP 1 Hangup — terminal fechado. Muitos daemons usam-no para recarregar config
SIGSTOP 19 Pausa o processo — não pode ser ignorado (equivalente a Ctrl+Z)
SIGCONT 18 Retoma um processo pausado
SIGUSR1 / SIGUSR2 10 / 12 Sinais definidos pela aplicação — muitos daemons fazem reload ou dump de estado
# Terminação graciosa (SIGTERM = 15) — DEFEITO
kill 1234
# ou explicitamente:
kill -15 1234
kill -TERM 1234

# Forçar terminação imediata (SIGKILL = 9) — último recurso
kill -9 1234
kill -KILL 1234

# Pedir a um daemon para recarregar configuração (SIGHUP)
kill -HUP $(cat /var/run/nginx.pid)

# Pausar e retomar um processo
kill -STOP 1234
kill -CONT 1234

# Listar todos os sinais disponíveis
kill -l
#  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
# ...

A regra de ouro é sempre tentar SIGTERM primeiro. O SIGKILL (-9) não dá ao processo qualquer hipótese de fazer cleanup: ficheiros temporários ficam órfãos, ligações de base de dados podem ficar abertas, buffers não são flushed. Usar kill -9 como primeiro instinto é um erro comum de administradores inexperientes.

Atenção: Um processo no estado D (Uninterruptible Sleep) não morre nem com kill -9. Está bloqueado no kernel à espera de I/O. A única forma de o remover é resolver o problema de I/O subjacente — normalmente um disco que deixou de responder.

pgrep e pkill — Encontrar e Terminar por Nome

Descobrir o PID com ps aux | grep nginx funciona, mas é frágil: o próprio grep aparece nos resultados, e o parsing de ps é sensível a colunas. O pgrep e pkill resolvem isto de forma limpa. Documentação: pgrep(1) — man7.org e pkill(1) — man7.org.

# Encontrar PIDs por nome de processo
pgrep nginx
# 1234
# 1235
# 1236

# Ver nome e PID juntos
pgrep -a nginx
# 1234 nginx: master process /usr/sbin/nginx
# 1235 nginx: worker process

# Procurar por utilizador específico
pgrep -u www-data nginx

# Terminar todos os processos com nome "nginx"
sudo pkill nginx

# Enviar sinal específico
sudo pkill -HUP nginx      # reload
sudo pkill -TERM nginx     # terminação graciosa (defeito)
sudo pkill -9 nginx        # força bruta

# Por utilizador
sudo pkill -u www-data

# Padrão exacto (-x) em vez de substring
pkill -x sleep    # só "sleep", não "sleep_helper"

O pkill é conveniente mas perigoso: pkill ssh pode matar a própria sessão SSH em uso. Sempre que possível, usar pgrep -a primeiro para confirmar a lista de PIDs antes de pkill.

Erros Comuns

Erros recorrentes no trabalho com processos, e como os evitar:

1. Usar kill -9 como primeiro instinto

O SIGKILL não permite cleanup. Primeiro kill PID (SIGTERM), esperar 5-10 segundos, e só se o processo persistir usar kill -9 PID. Para serviços geridos pelo systemd, systemctl stop servico é sempre preferível a kill manual.

2. pkill com nome parcial mata processo errado

pkill python mata também python3, python3.11 e qualquer processo com “python” no nome. Usar -x para match exacto, ou confirmar com pgrep -a antes.

3. Processo zombie (Z) que não desaparece

Um processo zombie já terminou mas o pai não leu o seu estado de saída. Não adianta kill -9 no zombie — já está morto. A solução é kill o processo pai (PPID). Para identificar o pai: ps -o ppid= -p <PID_zombie>.

# Encontrar zombies
ps aux | awk '$8 ~ /Z/ {print}'

# Para cada zombie, descobrir o pai
ps -o pid,ppid,comm -p 9999
#   PID  PPID COMM
#  9999  8888 [defunct]

# Matar o pai (cuidado: pode ser um daemon importante)
sudo kill 8888

4. Fechar terminal e o processo morre

Esquecer o nohup ou disown e o processo recebe SIGHUP ao fechar o SSH. Para tarefas longas, usar nohup ... & ou, melhor ainda, tmux / screen para manter a sessão.

5. Confundir %CPU do ps com tempo real

O %CPU do ps é a média desde o arranque do processo — um processo que começou há 5 minutos a 50% e passou a 0% mostra ~25%. Para CPU instantâneo, usar top ou htop.

Checklist Rápido

Antes de avançar para o próximo dia, confirmar que se consegue fazer o seguinte — cada item tem um link para a secção relevante do artigo:

  1. Explicar a diferença entre PID e PPID e identificar o estado de um processo na coluna STAT do ps (R, S, D, T, Z)
  2. Listar todos os processos do sistema com ps aux e identificar os 10 que mais consomem memória ou CPU
  3. Abrir o top, interpretar o load average e o I/O wait, e saber ordenar por memória com M
  4. Instalar e usar o htop — navegar, ver árvore de processos com F5 e matar com F9
  5. Lançar um processo com nice -n 10 e alterar a prioridade de um processo a correr com renice
  6. Suspender um processo com Ctrl+Z, mandar para background com bg e trazê-lo de volta com fg
  7. Garantir que um processo sobrevive ao fecho do terminal com nohup ou disown
  8. Terminar um processo com kill PID (SIGTERM) e só recorrer a kill -9 se o SIGTERM falhar
  9. Usar pgrep -a para encontrar processos por nome e pkill para os terminar — validando antes
  10. Resolver um processo zombie matando o pai (PPID), não o próprio zombie

Artigos Relacionados