Trilha 08 · Sistema operacional

Memória virtual

Cada processo acha que tem toda a memória para si — mas o SO mapeia endereços virtuais para endereços físicos via page table. Isso permite isolamento, compartilhamento e mais memória do que a RAM física disponível.

① Intuição

O endereço que você vê é uma ilusão

Quando você escreve int* p = malloc(4);, o compilador gera código que acessa endereços virtuais — não endereços físicos de RAM. A CPU traduz cada endereço virtual para físico em tempo de execução, usando uma estrutura chamada page table.

A memória é dividida em páginas (tipicamente 4 KB). A page table mapeia páginas virtuais para frames físicos. Se uma página virtual não está em RAM (não está mapeada), a CPU gera um page fault — o kernel é chamado, carrega a página do disco, atualiza a page table, e o processo continua como se nada tivesse acontecido.

Por que memória virtual? (1) Isolamento: cada processo tem seu próprio espaço de endereçamento — um processo não pode acessar a memória de outro. (2) Mais memória que a RAM: páginas que não cabem em RAM ficam no swap (disco). (3) Compartilhamento eficiente: biblioteca libc.so é mapeada para múltiplos processos compartilhando os mesmos frames físicos (somente leitura).
② Visualização interativa

Simulador de tradução de endereços

Clique em qualquer página virtual para simular o acesso. O sistema mostra se foi TLB hit (rápido), TLB miss com page table lookup, ou page fault com carregamento da página e substituição LRU.

ESPAÇO VIRTUAL (16 páginas)
Clique para acessar uma página
presenteswap/disco
RAM FÍSICA (10 frames)
F0VP00x00000
F1VP40x01000
F2livre0x02000
F3VP10x03000
F4livre0x04000
F5livre0x05000
F6livre0x06000
F7VP20x07000
F8livre0x08000
F9livre0x09000
TLB (4 entradas)
0:VP0F0
1:VP1F3
TLB = cache da page table. Hit: ~1 ciclo. Miss: ~100 ciclos (RAM).

Clique em uma página virtual para simular o acesso. O sistema mostrará se foi TLB hit, TLB miss ou page fault — e como o kernel resolve cada caso.

③ Explicação técnica

Paginação em 4 níveis (x86-64)

// Tradução de endereço virtual → físico (x86-64, 4KB páginas)

// Endereço virtual de 48 bits:
// bits[47:39] = índice no PML4  (512 entradas)
// bits[38:30] = índice no PDPT  (512 entradas)
// bits[29:21] = índice no PD    (512 entradas)
// bits[20:12] = índice no PT    (512 entradas)
// bits[11:0]  = offset na página (0–4095)

// Tradução de 0x7fff12345678:
const vaddr = 0x7fff12345678n;
const pml4_idx = (vaddr >> 39n) & 0x1FFn;  // → 0xFF
const pdpt_idx = (vaddr >> 30n) & 0x1FFn;  // → 0x1FC
const pd_idx   = (vaddr >> 21n) & 0x1FFn;  // → 0x091
const pt_idx   = (vaddr >> 12n) & 0x1FFn;  // → 0x145
const offset   = vaddr & 0xFFFn;             // → 0x678

// Sem TLB: 4 acessos à RAM (PML4→PDPT→PD→PT) + 1 acesso ao dado = 5 acessos
// Com TLB hit: PTE em cache → apenas 1 acesso ao dado
// Por isso o TLB é crucial: 99%+ de hit rate em workloads típicos

Algoritmos de substituição de páginas

// Algoritmos de substituição de páginas

// Sequência de acesso: 7 0 1 2 0 3 0 4 2 3 0 3 2
// 3 frames de RAM disponíveis

// FIFO (First In, First Out):
// Substitui a página mais antiga. Simples mas ineficaz.
// Paradoxo de Bélády: mais frames → mais page faults com FIFO!
// 9 page faults para a sequência acima.

// LRU (Least Recently Used):
// Substitui a página usada menos recentemente.
// Usa pilha de páginas ou contador de acesso.
// 8 page faults — melhor que FIFO, mas overhead de rastreamento.

// OPT (Ótimo — só teórico):
// Substitui a página que será usada mais tarde no futuro.
// 6 page faults — limite inferior, impossível na prática.

// Linux usa uma aproximação LRU com 2 listas:
// active list (quente) e inactive list (fria).
// O bit "accessed" da PTE é limpo periodicamente;
// se a página não foi acessada → move para inactive → candidata a swap.
Thrashing: quando o conjunto de trabalho (working set) de um processo não cabe na RAM disponível, o SO fica em loop: carrega página A → precisa de B → expulsa A → precisa de A novamente. Utilização de CPU cai para quase zero enquanto o disco trabalha 100%. Solução: suspender processos (reduzir o grau de multiprogramação) até que a RAM seja suficiente para os processos restantes. O Linux usa o OOM killer quando a RAM fica criticamente baixa — mata processos para liberar memória.
④ Projeto para programar

Explorando memória virtual

Mini projeto: em C, leia /proc/self/maps e imprima todas as VMAs (Virtual Memory Areas) do seu próprio processo: endereço de início/fim, permissões, nome do arquivo mapeado. Identifique: stack, heap, código do programa, libc.so, vDSO.

Projeto principal: implemente o algoritmo de substituição de páginas LRU em Python: classe PageCache(capacity) com método access(page) que retorna True se page fault e False se hit. Use uma deque para rastrear ordem de uso. Teste com a sequência do código acima e valide o número de page faults.

Desafio extra: use mmap() + madvise(MADV_SEQUENTIAL) para mapear um arquivo grande e processar sequencialmente. Compare com madvise(MADV_RANDOM). Monitore page faults com perf stat -e page-faults ./prog. Observe como a dica de acesso muda o número de page faults e a velocidade.

⑤ Exercícios rápidos

Teste sua intuição

O que é o TLB e por que ele é essencial para o desempenho da paginação?
O que é um page fault e o que o kernel faz quando ocorre?
O que é thrashing e por que a utilização de CPU cai durante o thrashing?
⑥ Aplicações no mundo real

Onde você encontra isso

💾

Swap e zRAM

Linux swap armazena páginas em disco quando a RAM está cheia. Android e dispositivos embarcados usam zRAM: uma partição de swap comprimida na própria RAM. zRAM comprime páginas frias com LZ4 ou zstd, efetivamente aumentando a capacidade útil da RAM em 2–3×. Um Android com 4GB de RAM + zRAM comporta mais apps abertos.

🛡️

ASLR (Address Space Layout Randomization)

ASLR randomiza os endereços base da stack, heap e bibliotecas a cada execução. Um exploit que hardcode o endereço de system() em libc falha porque o endereço virtual muda. O SO gera o offset aleatório no mmap — a page table mapeia o mesmo código físico para endereços virtuais diferentes em cada processo.

🚀

Huge pages e TLB pressure

Com 4KB de páginas e 16GB de RAM, a page table pode ter 4 milhões de entradas. O TLB cobre apenas ~1500 entradas — com acesso aleatório, muitos TLB misses. Huge pages (2MB ou 1GB) reduzem o número de entradas de page table e TLB pressure: bancos de dados (PostgreSQL, Oracle) e aplicações HPC habilitam huge pages explicitamente para ganhos de 5–30%.

← Anterior: Escalonamento Próxima: Sistema de arquivos →