Como o computador roda seu código (do teclado ao pixel)
Você abre o editor, escreve código, aperta Run — e algo acontece. Mas o quê exatamente? Entre o seu texto e o pixel na tela existem cinco camadas de abstração que a maioria dos programadores nunca explora. Esse artigo desfaz a magia de dentro pra fora.
A pilha completa: 5 camadas que ninguém te ensina
Todo computador moderno funciona em camadas de abstração empilhadas. Cada camada oferece uma interface para a camada acima e esconde a complexidade da camada abaixo:
Hardware (transistores, voltagem, física)
↑ ↓
Microarquitetura (pipeline de CPU, cache, branch prediction)
↑ ↓
Sistema Operacional (kernel: processos, memória, I/O)
↑ ↓
Runtime/Interpretador (VM da JVM, CPython, Node.js V8)
↑ ↓
Seu código (Python, JavaScript, Java, Go...)Quando você escreve print("olá"), o interpretador Python traduz para bytecode, o bytecode vira instruções de CPU, as instruções acessam memória e fazem chamadas de sistema para I/O. São literalmente bilhões de operações em frações de segundo.
CPU: o único lugar onde código realmente executa
A CPU executa um ciclo infinito: Fetch → Decode → Execute → Write Back. Nada mais acontece sem esse ciclo.
Fetch
Busca a próxima instrução na memória, no endereço apontado pelo Instruction Pointer (IP/PC). A instrução pode ser ADD, MOV, JMP, CALL, SYSCALL...
Decode
Decodifica os bits da instrução: qual operação é, quais registradores ou endereços de memória usa, qual é o tamanho.
Execute
A ALU (Arithmetic Logic Unit) executa: soma, subtrai, compara, move dados. Operações de memória acionam a MMU (Memory Management Unit).
Write Back
Escreve o resultado de volta no registrador ou memória. O IP avança para a próxima instrução (ou salta se foi um JMP).
CPUs modernas executam várias instruções ao mesmo tempo via pipeline e execução fora de ordem (out-of-order execution). Enquanto uma instrução está na fase Execute, a próxima já está em Decode e a seguinte em Fetch — isso é paralelismo implícito que você nunca precisa gerenciar, mas que explica por que CPUs modernas processam bilhões de instruções por segundo.
Memória: RAM, cache e a hierarquia que define performance
Acesso à memória é o maior gargalo de performance em software moderno. Por quê? Porque existe uma hierarquia de memória com velocidades e tamanhos radicalmente diferentes:
| Tipo | Latência | Tamanho típico | Onde fica |
|---|---|---|---|
| Registradores | < 1 ns | < 1 KB | Dentro da CPU |
| Cache L1 | ~1 ns | 32–64 KB | Dentro do core |
| Cache L2 | ~4 ns | 256–512 KB | Por core |
| Cache L3 | ~10 ns | 8–64 MB | Compartilhado |
| RAM (DRAM) | ~100 ns | 8–64 GB | Placa-mãe |
| SSD NVMe | ~100 µs | 256 GB – 4 TB | Periférico |
| HDD | ~10 ms | 1–16 TB | Periférico |
A RAM é 100× mais lenta que o cache L1. Por isso a CPU tem cache hierárquico: tenta L1 primeiro, depois L2, L3 e só então vai à RAM. Se seu código acessa memória de forma previsível e sequencial (array de structs, por exemplo), o prefetcher da CPU carrega os dados antes de você pedir — isso é cache-friendly. Padrões aleatórios (linked lists, hash maps com muita colisão) causam cache misses e destroem performance.
# Cache-friendly: acesso sequencial, o prefetcher adora soma = sum(arr[i] for i in range(len(arr))) # Cache-unfriendly: saltos aleatórios em memória soma = sum(arr[random_indices[i]] for i in range(n)) # Em arrays grandes, a versão sequencial pode ser # 10-100x mais rápida — mesma operação, só o padrão muda
Sistema Operacional: o árbitro de todos os recursos
O SO (kernel) é o programa mais privilegiado do sistema. Ele controla tudo que o hardware oferece e decide quem pode usar o quê. Sua interface com o mundo é via chamadas de sistema (syscalls).
Quando seu código faz open("arquivo.txt"), print("olá"), ou requests.get(url), não está acessando hardware diretamente. Está pedindo permissão ao SO via syscall. O processo entra em modo kernel brevemente, o SO executa a operação com privilégio total, e devolve o resultado para o processo em mode usuário.
# O que seu Python faz:
with open("dados.txt", "r") as f:
content = f.read()
# O que acontece por baixo (strace mostra isso):
# openat(AT_FDCWD, "dados.txt", O_RDONLY) = 3 ← syscall, retorna fd=3
# fstat(3, {st_mode=S_IFREG, st_size=1234}) = 0 ← syscall
# read(3, "conteúdo...", 4096) = 1234 ← syscall, lê até 4096 bytes
# close(3) = 0 ← syscall
# Cada linha acima é uma troca user-space ↔ kernel-space
# Custo: ~100-1000 ns por syscall (mais que uma instrução, menos que I/O real)strace python script.py. É a ferramenta certa para entender por que um processo está lento, travado, ou o que está acessando em disco/rede.Processos: o container de execução do SO
Um processo é a abstração do SO para um programa em execução. Não confunda com o arquivo executável — o executável é estático (bytes em disco); o processo é dinâmico (em execução, com memória, estado, recursos).
Cada processo recebe do SO:
→ PID (Process ID) — número único de identificação
→ Espaço de endereçamento virtual — memória privada (stack, heap, código, dados)
→ File descriptors — tabela de arquivos/sockets abertos (0=stdin, 1=stdout, 2=stderr)
→ Variáveis de ambiente — copiadas do processo pai
→ Permissões — UID/GID que determinam o que pode acessar
Como processos são criados? No Linux, quase sempre via fork + exec: fork() cria um clone do processo pai (copy-on-write), e exec() substitui o espaço de memória pelo novo programa. É assim que o shell cria processos filhos para rodar comandos.
# Quando você roda:
$ python script.py
# O shell (bash/zsh) faz:
pid = fork() # cria clone do shell
if pid == 0: # filho executa:
exec("python", ["python", "script.py"]) # substitui shell por python
else: # pai (shell) aguarda:
waitpid(pid) # bloqueia até filho terminarMemória virtual: por que 0x7fff... não é RAM física
Quando um programa acessa memória no endereço 0x7fff5fbff8a0, esse não é o endereço físico na RAM. É um endereço virtual — cada processo vê um espaço de endereços completamente privado e contíguo, como se tivesse a memória toda para si.
A MMU (Memory Management Unit), em hardware dentro da CPU, traduz endereços virtuais em físicos a cada acesso. A tabela de tradução (page table) fica em RAM e é gerenciada pelo SO. O TLB (Translation Lookaside Buffer) é um cache dessa tradução dentro da CPU.
POR QUE MEMÓRIA VIRTUAL EXISTE
→ Isolamento: processo A não pode acessar memória do processo B — segfault se tentar
→ Swap: SO pode mover páginas para disco quando RAM está cheia, transparentemente
→ mmap: arquivos podem ser mapeados para memória — acessar bytes do arquivo como se fossem RAM
→ Shared memory: dois processos podem mapear a mesma página física (IPC eficiente)
→ Lazy allocation: malloc(1GB) não aloca fisicamente — só reserva; SO aloca on demand (page fault)
Quiz rápido
3 perguntas · Acerte tudo e ganhe o badge 🎯 Gabarito