🧠FFVAcademy
💻

Como o computador roda seu código (do teclado ao pixel)

10 min de leitura·+50 XP

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.

💡
Saber onde você está na pilha é o que separa quem depura "colocando print" de quem encontra o problema em minutos. Cada bug mora em uma camada específica.

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:

TipoLatênciaTamanho típicoOnde fica
Registradores< 1 ns< 1 KBDentro da CPU
Cache L1~1 ns32–64 KBDentro do core
Cache L2~4 ns256–512 KBPor core
Cache L3~10 ns8–64 MBCompartilhado
RAM (DRAM)~100 ns8–64 GBPlaca-mãe
SSD NVMe~100 µs256 GB – 4 TBPeriférico
HDD~10 ms1–16 TBPerifé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 (Linux) mostra todas as syscalls de um processo: 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 terminar

Memó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)

Take-away: todo endereço que você vê em código, debugger, ou crash dump é virtual. O SO e a MMU cuidam da tradução. Isso é por design — isolamento e flexibilidade.
💡
Próximo passo: Linux no terminal — agora que você sabe o que roda por baixo, os comandos do terminal vão fazer muito mais sentido.
🧩

Quiz rápido

3 perguntas · Acerte tudo e ganhe o badge 🎯 Gabarito

Continue lendo