- Explica a estrutura de memória de processos no Linux no nível do funcionamento real, detalhando passo a passo a relação entre espaço de endereços virtual e memória física
- Explica concretamente como os processos possuem e acessam memória com foco em mecanismos centrais como tabelas de páginas, VMA, mmap, page fault e CoW
- Apresenta como observar o estado da memória por processo por meio do sistema de arquivos
/proc e o papel de ferramentas avançadas de diagnóstico como pagemap e kpageflags
- Aborda otimização de desempenho e técnicas de dirty tracking em espaço de usuário com recursos recentes do kernel como Transparent Huge Pages (THP), userfaultfd e PAGEMAP_SCAN
- Também explica princípios de projeto do kernel ligados a segurança e desempenho, como PTI contra Meltdown, flush de TLB e a política W^X, oferecendo uma visão geral do gerenciamento de memória no Linux
Estrutura básica da memória de processos
- Quando um programa é executado, parece que existe uma memória contínua enorme, mas na prática o kernel Linux a organiza dinamicamente em unidades de página
- A CPU consulta a tabela de páginas para converter endereços virtuais em frames físicos
- Se não houver mapeamento, ocorre um page fault, e o kernel aloca uma nova página ou retorna um erro
- Se faltar RAM física, o kernel move páginas não utilizadas para o disco ou remove páginas de arquivo para liberar espaço
/proc é um sistema de arquivos virtual montado pelo kernel na memória, que expõe o estado de processos e do kernel como arquivos
Espaço de endereços e VMA
- Cada processo possui um objeto de espaço de endereços, composto internamente por várias VMAs (Virtual Memory Areas)
- Uma VMA é um intervalo contínuo de endereços com as mesmas permissões (R/W/X) e o mesmo backend (memória anônima ou arquivo)
- A tabela de páginas é a estrutura consultada pelo hardware e armazena as informações de mapeamento (PTE) entre páginas virtuais e físicas
- Mudanças no espaço de endereços são feitas com três chamadas de sistema
mmap: cria uma nova região
mprotect: altera permissões
munmap: remove o mapeamento
- A página tem 4 KiB como unidade básica, e alguns sistemas também suportam páginas grandes de 2 MiB e 1 GiB
Vendo a composição da memória com /proc/self/maps
- É possível verificar o mapa de memória do processo com o comando
cat /proc/self/maps
- São exibidos código, dados e bss do executável, heap, mapeamentos anônimos, bibliotecas compartilhadas, stack etc.
- As regiões
[vdso] e [vvar] são código e dados para chamadas de sistema rápidas mapeados pelo kernel
Como mmap funciona
mmap não faz a alocação real de memória; ele registra uma promessa sobre o espaço de endereços
- As páginas só são alocadas no primeiro acesso
- Em mapeamentos de arquivo,
offset deve estar alinhado à página, e acessos além do fim do arquivo geram SIGBUS
MAP_SHARED reflete diretamente no arquivo, enquanto MAP_PRIVATE cria páginas independentes por copy-on-write (CoW)
MAP_FIXED_NOREPLACE garante segurança ao falhar se já existir um mapeamento no endereço especificado
Primeiro acesso e page fault
- No primeiro acesso a um novo mapeamento, se a CPU não encontrar a entrada na tabela de páginas, ocorre um page fault
- O kernel verifica validade do endereço, permissões de acesso e existência
- Em mapeamento anônimo, aloca uma nova página zerada; em mapeamento de arquivo, lê a partir do page cache
- minor fault ocorre quando os dados já estão na RAM, enquanto major fault exige I/O de disco
- A stack é protegida por uma guard page, então acessos muito abaixo provocam
SIGSEGV
fork() e o Copy-on-Write de MAP_PRIVATE
- Ao executar
fork, pai e filho compartilham as mesmas páginas físicas, marcadas como somente leitura
- Só no momento da escrita uma nova página é copiada para manter a independência
- Mapeamentos de arquivo com
MAP_PRIVATE funcionam pelo mesmo princípio
- Opções relacionadas
vfork: compartilha o espaço de endereços do pai
clone(CLONE_VM): cria threads
MADV_DONTFORK, MADV_WIPEONFORK: excluem o mapeamento do processo filho ou o reinicializam com zero
Mudança de permissões e invalidação de TLB
- Ao alterar permissões de páginas com
mprotect, o kernel faz divisão de VMA e modificação da tabela de páginas e depois executa a invalidação de TLB
- Pela política W^X, uma página não pode ser gravável e executável ao mesmo tempo
- A TLB (Translation Lookaside Buffer) é um cache de traduções recentes de endereços, e sua invalidação causa uma pequena latência
Observação detalhada com /proc
- Com
/proc/<pid>/maps, smaps e smaps_rollup, é possível verificar permissões por região, RSS e uso de HugePage
/proc/<pid>/pagemap fornece estado por página (presença, swap, PFN etc.), mas o PFN não é exposto a usuários comuns
/proc/kpagecount e /proc/kpageflags mostram a contagem de mapeamentos por PFN e propriedades da página (anônima, de arquivo, dirty etc.)
mincore e SEEK_DATA/SEEK_HOLE permitem identificar intervalos de dados e buracos em arquivos esparsos
- É possível implementar dirty tracking em espaço de usuário combinando
PAGEMAP_SCAN e userfaultfd
Transparent Huge Pages (THP) e mTHP
- THP agrupa automaticamente memória acessada com frequência em páginas grandes (como 2 MiB) para melhorar a eficiência da TLB
- A thread
khugepaged mescla páginas adjacentes
- mTHP suporta páginas grandes variáveis (folio) em vários tamanhos, como 16 KiB e 64 KiB
- É possível verificar o uso em
AnonHugePages e FilePmdMapped de /proc/self/smaps
- As configurações globais do sistema ficam em
/sys/kernel/mm/transparent_hugepage/
MADV_HUGEPAGE e MADV_NOHUGEPAGE permitem controle por região
Dirty tracking em espaço de usuário
- Com
userfaultfd e PAGEMAP_SCAN, é possível copiar apenas as páginas alteradas
- O kernel executa a varredura e a proteção contra escrita em uma única operação atômica
- Isso é eficiente em snapshots, live migration e cenários semelhantes
Mecanismo de flush de TLB
- No x86, a invalidação da TLB acontece de duas formas
INVLPG: invalida uma única página
- recarregar a raiz da tabela de páginas para fazer flush completo
PCID e INVPCID fazem a gestão de tags de TLB por processo, reduzindo flushes desnecessários
tlb_single_page_flush_ceiling é o limite usado pelo kernel para escolher entre flush por página e flush completo
Mitigação de Meltdown: Page Table Isolation (PTI)
- Meltdown é uma vulnerabilidade na qual dados do kernel podem ser expostos pelo cache durante execução especulativa
- O Linux usa PTI (Page Table Isolation) para separar os espaços de endereços de usuário e kernel
- Na entrada, troca
CR3 para usar uma tabela de páginas exclusiva do kernel
- Usa
PCID para minimizar flushes de TLB
- Isso vem ativado por padrão, e pode ser desabilitado com
nopti
Procedimento seguro do kernel para mudar mapeamentos
- Ao alterar um mapeamento, a ordem é
- tratar as regras de cache
- modificar a tabela de páginas
- invalidar a TLB
- Mapeamentos internos do kernel (
vmap, vmalloc) também sincronizam cache e TLB antes e depois de I/O
- Em algumas arquiteturas, é necessário fazer flush do cache de instruções após copiar código
Estrutura de stack e chamadas no x86
- No modo 64 bits, são usados os registradores RIP, RSP e RBP, e a stack cresce para baixo
- Conforme a ABI System V AMD64, os argumentos são passados em RDI, RSI, RDX, RCX, R8 e R9, e o valor de retorno fica em RAX
- O modo de usuário roda em ring 3, o kernel em ring 0, e system calls e interrupções fazem a transição por meio de gates
Situações de erro e diagnóstico
mmap → EINVAL: erro de alinhamento do offset do arquivo
mmap → ENOMEM: falta de espaço virtual ou limite de overcommit
- Acesso a mapeamento de arquivo →
SIGBUS: acesso além do EOF
mprotect(PROT_EXEC) → EACCES: montagem noexec ou política W^X
- Aumento de RSS após
fork(): cópia de páginas por CoW
- Sobrescrever mapeamento existente com
MAP_FIXED → MAP_FIXED_NOREPLACE é recomendado
Checklist prático
- Para garantir memória imediatamente:
mmap + PROT_READ|PROT_WRITE + MAP_PRIVATE|MAP_ANONYMOUS
- Ao gerar código: manter W^X e usar
mprotect(PROT_READ|PROT_EXEC)
- Ao mapear arquivos: alinhar
offset à página e evitar acesso além do EOF
- Se houver muitos page faults: usar
MADV_WILLNEED ou fazer acesso prévio
- Para analisar uso de memória:
/proc/<pid>/smaps_rollup → /proc/<pid>/maps
- Em
fork de processos grandes: considerar CoW e usar exec no filho
- Em ambientes sensíveis a latência: observar THP/mTHP,
mlock e o comportamento da TLB
1 comentários
Comentários do Hacker News
Gosto muito desses textos explicativos curtos
Mesmo quando já conheço o conteúdo, ajuda poder revisá-lo mais uma vez enquanto leio
Quando vejo uma expressão como “mmap, without the fog”, fico com a sensação de que foi um texto coescrito por LLM, e isso me deixa incomodado e irritado sem motivo
E ainda tem uma expressão estranha como “without the fog”, então passa a impressão de que o ChatGPT ajudou a escrever
Ao ver a conversa sobre instruction pipelining, bate uma vontade de voltar à época de arquiteturas simples como a 6502
Naquele tempo, tudo funcionava “como era”, sem mapeamentos complexos nem proxies
Se houvesse interconexões rápidas o bastante, talvez desse até para sonhar de novo com esse tipo de simplicidade
Mas problemas como Meltdown e Spectre mostram claramente que houve um preço a pagar pelo aumento da complexidade
Agora que a lei de Moore parece estar chegando ao limite, fica a dúvida se esse trade-off de complexidade ainda é mesmo a melhor escolha
Não acho que simplicidade seja necessariamente melhor
Aqui aparece que o site foi bloqueado como um domínio perigoso ou inseguro
Pelo resultado da verificação no VirusTotal, não há problema algum
Fiquei curioso sobre o que significa dizer que o relatório de erro é apenas “ruído”