Trilha 08 · Sistema operacional

Processos e estados

Um processo é um programa em execução — com sua própria memória, registradores e estado. O SO mantém um PCB (Process Control Block) para cada processo e os movimenta entre estados conforme eventos acontecem.

① Intuição

Processo ≠ programa

Um programa é um arquivo em disco (um conjunto de instruções). Um processo é esse programa em execução — com memória alocada, registradores, arquivos abertos, e um estado que muda ao longo do tempo. O mesmo programa pode gerar múltiplos processos (duas abas do Chrome são dois processos distintos).

O SO mantém um PCB (Process Control Block) para cada processo: uma estrutura de dados no kernel que contém o PID, o estado atual, os valores dos registradores (quando o processo não está executando), a tabela de páginas, a lista de arquivos abertos e muito mais.

Thread vs. processo: um processo pode ter múltiplas threads — fluxos de execução que compartilham o mesmo espaço de endereçamento (memória, arquivos abertos). Criar uma thread é mais barato que criar um processo (sem cópia de page table), mas um bug em uma thread pode corromper dados de outra. Processos são isolados pelo SO.
② Visualização interativa

Gerenciador de processos

Selecione um processo e use os botões para acionar transições de estado: fork (criar filho), escalonar (dar CPU), bloquear (aguardar I/O), desbloquear (I/O concluído) e terminar.

NEW
READY
RUNNING
BLOCKED
TERMINATED
PCB — PROCESS CONTROL BLOCK
PID: 1
Nome: chrome
Estado: RUNNING
Prioridade:2 (menor = maior prio)
CPU usado: 1240 ms
Memória: 320 MB

CPU está executando este processo agora (só 1 por core de CPU).

LOG DO KERNEL
Sistema iniciado. Chrome está em execução.
MÁQUINA DE ESTADOS DE PROCESSO
NEWREADYRUNNINGBLOCKEDTERMINATED
③ Explicação técnica

fork() e exec()

// fork() — criando um processo filho em C

int main() {
  printf("PID do pai: %d\n", getpid());  // ex: 1234

  pid_t pid = fork();  // aqui o universo se bifurca

  if (pid == 0) {
    // FILHO: pid retornado é 0
    printf("Sou o filho! PID=%d, pai=%d\n", getpid(), getppid());
    exec("/bin/ls");  // substitui imagem do processo por ls
  } else {
    // PAI: pid = PID do filho (ex: 1235)
    printf("Filho criado com PID=%d\n", pid);
    wait(NULL);  // aguarda filho terminar (evita processo zumbi)
    printf("Filho terminou.\n");
  }
  return 0;
}

// Output:
// PID do pai: 1234
// Filho criado com PID=1235
// Sou o filho! PID=1235, pai=1234
// Filho terminou.

O que acontece num context switch

// Context switch: o que o kernel salva/restaura
// Quando o escalonador troca de processo A para B:

// 1. Salva contexto de A no PCB de A:
pcb_A.registers = {rax, rbx, rcx, …, rsp, rbp, rip, rflags};
pcb_A.fpu_state  = save_fpu();   // registradores FP/SSE/AVX
pcb_A.cr3        = current_cr3;  // ponteiro para page table de A

// 2. Carrega contexto de B do PCB de B:
restore_regs(pcb_B.registers);
restore_fpu(pcb_B.fpu_state);
write_cr3(pcb_B.cr3);   // troca o espaço de endereçamento!
// Escrever em CR3 invalida o TLB inteiro → overhead adicional

// 3. CPU retoma B de onde parou (RIP aponta para próxima instrução)

// Custo de um context switch: ~1–10 μs
// Envolve: salvar ~100 bytes de registradores, trocar CR3,
// invalidar TLB (ou usar ASID para evitar), atualizar estruturas do kernel.
// Por isso goroutines (Go) e fibers são mais leves que threads OS.
Processo zumbi (zombie): quando um filho termina antes do pai chamar wait(), o processo filho fica em estado "terminado" mas seu PCB permanece na tabela de processos — o SO precisa manter o código de saída disponível para o pai. Isso é um processo zumbi. Se o pai nunca chama wait(), o processo init (PID 1) adota os filhos órfãos e faz o wait() por eles.
④ Projeto para programar

Criando e gerenciando processos

Mini projeto: escreva um programa C que usa fork() para criar 3 filhos em paralelo. Cada filho dorme um número diferente de segundos (sleep(n)) e imprime seu PID ao terminar. O pai deve usar wait() em loop até todos os filhos terminarem. Observe que a ordem de término não é determinística.

Projeto principal: implemente um mini shell: leia comandos do stdin (ls, echo, etc.), faça fork(), execute o comando com execvp() no filho, e o pai aguarda com waitpid(). Adicione suporte a pipes (|): use pipe() para conectar stdout de um processo ao stdin do próximo.

Desafio extra: use clone() diretamente (Linux) com diferentes flags de namespace para criar um processo isolado: CLONE_NEWPID (novo namespace de PID — o filho vê apenas seus filhos), CLONE_NEWNET (sem rede), CLONE_NEWMNT (filesystem privado). Isso é a base de como containers Docker funcionam.

⑤ Exercícios rápidos

Teste sua intuição

Qual é a diferença entre programa e processo?
O que significa um processo estar no estado BLOQUEADO?
O que fork() faz e o que cada processo recebe como retorno?
⑥ Aplicações no mundo real

Onde você encontra isso

🌐

Multi-process browsers

Chrome usa um processo por aba (site isolation). Se uma aba travar, não derruba as outras. O processo de aba roda com sandbox restrito (sem acesso a arquivos do SO direto). A comunicação entre processos é feita via IPC (Inter-Process Communication) com pipes e shared memory.

🐳

Containers e Docker

Docker usa namespaces Linux (PID, net, mnt, user) para isolar processos. Um container é um processo (ou grupo de processos) com uma visão restrita do sistema. cgroups (control groups) limitam CPU, memória e I/O. Não há hypervisor — é o mesmo kernel Linux, mas com fronteiras virtuais criadas pelo SO.

📊

Gerenciadores de processos

top, htop e ps leem /proc/ — um sistema de arquivos virtual do Linux onde cada diretório /proc/PID/ expõe o PCB do processo como arquivos legíveis: status (estado, memória), maps (VMAs), fd/ (file descriptors abertos), cmdline (comando original).

← Trilha: Sistema operacional Próxima: Escalonamento →