Pesquisar conteúdos

Encontre uma trilha ou aula pelo assunto.

Experimente pesquisar por “governança”, “ITIL” ou “planejamento”.

Memória física, endereços e espaço de memória de um processo

Entenda como processos enxergam endereços e como o sistema operacional separa e protege seus espaços de memória.
Dois espaços de processo enviam cartões por um mecanismo de tradução a gavetas distintas da memória física, com uma região compartilhada

O processo não trabalha diretamente com posições da RAM

Quando uma instrução lê uma variável, percorre um vetor ou retorna de uma função, ela usa um endereço virtual pertencente ao espaço de memória do processo. Esse número não identifica, por si só, um chip nem uma posição fixa da memória RAM. O processador e o sistema operacional mantêm uma tradução entre os endereços que o programa usa e os recursos físicos que sustentam os dados.

Essa separação entrega três propriedades importantes: cada processo recebe uma visão organizada da memória; um processo comum não pode acessar livremente o espaço de outro; e regiões podem ser compartilhadas de maneira explícita quando isso é desejado. Por isso, dois processos podem usar o mesmo valor de endereço virtual e ainda assim alcançar dados físicos diferentes.

A aula sobre programas, processos e contexto de execução apresentou a memória como parte da moldura de recursos de um processo. Agora abriremos essa moldura. A próxima aula explicará em profundidade paginação, tabelas de páginas, TLB, page fault e swap; aqui o objetivo é construir o modelo que torna esses mecanismos compreensíveis.

Memória física é um recurso finito administrado pelo sistema

A memória física é a capacidade endereçável oferecida pela RAM ao hardware e administrada pelo kernel. Ela é finita, volátil e não precisa formar uma única faixa uniforme: arquiteturas e máquinas com múltiplos soquetes podem organizar bancos com características e custos de acesso diferentes.

O sistema não entrega normalmente um intervalo físico contínuo para cada programa. A RAM é dividida em unidades administráveis, e diferentes partes físicas podem sustentar trechos consecutivos vistos pelo processo. Uma unidade física também pode mudar de função ao longo do tempo: guardar dado privado de uma aplicação, código de uma biblioteca, conteúdo em cache de um arquivo ou uma estrutura interna do kernel.

Isso impede uma equivalência simples como “o processo ocupa do byte X ao byte Y da RAM”. O que existe é um conjunto dinâmico de mapeamentos. Alguns possuem páginas residentes na RAM naquele instante; outros reservam endereços, representam arquivos ou ainda não exigiram materialização física. Memória física disponível, espaço virtual reservado e conteúdo residente são medidas relacionadas, mas diferentes.

Endereço virtual e endereço físico pertencem a mapas diferentes

O endereço presente na instrução executada por uma aplicação é virtual. Antes do acesso à RAM, a unidade de gerenciamento de memória do processador, ou MMU, consulta as estruturas preparadas pelo sistema operacional e obtém a correspondência física e as permissões.

Podemos resumir o caminho sem ainda abrir os detalhes da paginação:

instrução + endereço virtual
             ↓
 mapa do processo + permissões
             ↓
 endereço físico ou falha controlada

Se o endereço pertence a uma região válida e a operação é permitida, o acesso prossegue. Se a região é somente leitura e a instrução tenta escrever, ou se não existe mapeamento para aquele endereço, o hardware transfere o controle ao kernel. O sistema pode resolver certas situações previstas; quando o acesso é realmente inválido, a aplicação recebe uma exceção ou sinal e pode ser encerrada.

Dois processos usam posições virtuais equivalentes, passam por mapas independentes e chegam a quadros físicos diferentes, enquanto uma região compartilhada converge para o mesmo quadro
O número virtual só ganha significado junto ao espaço do processo. Mapeamentos privados preservam isolamento; um mapeamento explícito pode apontar para conteúdo compartilhado.

Essa tradução também explica por que um processo não pode passar um ponteiro comum para outro e esperar que ele funcione. O número será interpretado no mapa do destinatário. Para compartilhar dados, os participantes precisam de um mecanismo que estabeleça um mapeamento comum, uma interface de comunicação ou serialização.

Cada processo recebe uma visão privada

Em sistemas com memória virtual e isolamento de processos, cada processo possui seu próprio espaço de endereçamento de usuário. Threads do mesmo processo normalmente compartilham esse espaço, embora mantenham pilhas e contexto de execução próprios. Processos diferentes possuem mapas independentes.

Imagine duas instâncias do serviço da plataforma de estudos. Ambas foram construídas a partir do mesmo executável e podem carregar uma função no mesmo endereço virtual. Ainda assim, a variável que conta requisições de uma instância não é a variável da outra. Os mapas podem apontar para quadros físicos distintos.

Ao mesmo tempo, o sistema pode mapear páginas de código da mesma biblioteca como compartilháveis, pois o conteúdo é idêntico e protegido contra escrita. Compartilhar a sustentação física não significa remover o isolamento: cada processo continua vendo uma região em seu mapa e as permissões continuam sendo verificadas.

Essa combinação — privado por padrão e compartilhado de forma controlada — sustenta proteção e eficiência. Ela não é uma barreira absoluta contra falhas no kernel, dispositivos com acesso direto à memória ou vulnerabilidades de hardware, mas define a fronteira normal entre aplicações em modo usuário.

O espaço do processo é formado por regiões

Um espaço de endereçamento não é um grande bloco inteiramente ocupado. Ele costuma ser esparso: contém intervalos mapeados separados por lacunas inválidas ou reservadas. Cada região possui limites, permissões e uma origem, como o executável, uma biblioteca, um arquivo mapeado ou memória anônima.

Uma representação didática comum inclui:

  • código executável: instruções do programa, em geral legíveis e executáveis, mas não graváveis;
  • dados somente leitura: constantes e outras estruturas protegidas contra alteração;
  • dados inicializados e BSS: variáveis globais e estáticas graváveis; BSS representa dados inicializados conceitualmente com zero;
  • heap: memória obtida dinamicamente por alocadores durante a execução;
  • mapeamentos: bibliotecas, arquivos, memória compartilhada e regiões anônimas;
  • pilhas: chamadas, variáveis automáticas e dados de controle de cada thread;
  • regiões especiais: áreas fornecidas pelo sistema ou pelo runtime para finalidades específicas.
Mapa vertical conceitual de um processo com código, dados, heap, lacunas, arquivos e bibliotecas mapeados e pilhas de threads, cada região com permissões distintas
O desenho é conceitual, não uma planta universal. Ordem, tamanho, direção de crescimento e regiões especiais variam conforme arquitetura, ABI, sistema, executável e runtime.

Essa lista ajuda a raciocinar, mas não deve ser decorada como layout obrigatório. Heap e pilha não precisam crescer em direções opostas; um alocador pode obter grandes regiões por mapeamentos separados; um runtime gerenciado pode organizar objetos de outra maneira; e mecanismos de segurança podem randomizar posições.

No Linux, o kernel descreve intervalos do espaço de usuário por Virtual Memory Areas (VMAs). Cada VMA reúne uma faixa virtualmente contígua com atributos compatíveis. Acesso fora das áreas válidas é inválido, salvo comportamentos específicos como expansão permitida de uma pilha adjacente. Esse é um detalhe de implementação útil, não o nome universal da abstração.

Pilha e heap respondem a necessidades diferentes

A pilha de uma thread sustenta sua cadeia de chamadas: endereços de retorno, contexto salvo e, conforme compilador e ABI, argumentos e variáveis automáticas. Cada thread precisa de uma pilha própria para que suas chamadas avancem independentemente.

O heap é uma região lógica usada para alocações cuja duração ou tamanho não cabe naturalmente na disciplina de chamadas. Um alocador administra blocos e solicita mais espaço ao sistema quando necessário. Objetos no heap podem ser compartilhados por threads do mesmo processo, portanto continuam sujeitos aos problemas de concorrência e sincronização.

“Pilha é rápida e heap é lento” é uma simplificação ruim. Ajustar um ponteiro de pilha costuma ser barato, mas o custo observado depende de cache, inicialização, alocador, coleta de lixo, padrão de acesso e duração do objeto. A diferença estrutural mais útil é de organização e tempo de vida, não uma promessa universal de desempenho.

Estouro de pilha também não significa que toda a RAM acabou. Recursão profunda ou uma variável automática excessiva pode ultrapassar os limites ou a expansão admitida para aquela pilha, enquanto o restante do processo e do sistema ainda possui memória.

Permissões acompanham os intervalos de memória

Mapeamentos podem permitir leitura, escrita e execução em combinações controladas. Separar permissões reduz o impacto de erros: código pode ser executável sem ser gravável; constantes podem ser legíveis sem aceitar alteração; lacunas e páginas de guarda podem permanecer inacessíveis.

Considere três operações:

  1. ler dado de uma região r-- é compatível com o mapa;
  2. escrever nessa mesma região viola sua proteção;
  3. saltar para uma região rw- e tentar executar bytes também pode ser proibido.

Em sistemas semelhantes a Unix, uma violação pode resultar em SIGSEGV ou SIGBUS, conforme o caso. No Windows, a aplicação pode receber uma exceção de violação de acesso. “Segmentation fault” é, portanto, um efeito visível de acesso inválido; não prova sozinho que o defeito foi “na pilha” nem que existe segmentação clássica de hardware.

Uma região válida também não autoriza qualquer acesso a qualquer momento. O endereço precisa estar mapeado, a permissão deve admitir a operação e o objeto precisa continuar vivo segundo o contrato do programa. Usar memória já liberada pode atingir uma faixa ainda mapeada e corromper dados silenciosamente, sem falha imediata.

Reservado, comprometido e residente não são a mesma medida

Ferramentas mostram números diferentes porque respondem a perguntas distintas:

  • tamanho virtual: quanto do espaço de endereçamento está reservado ou mapeado segundo a definição da plataforma;
  • memória comprometida: quanto possui garantia de sustentação segundo a política do sistema;
  • conjunto residente ou working set: quais partes estão atualmente na RAM;
  • memória privada: parte atribuída exclusivamente ao processo segundo a métrica da ferramenta;
  • memória compartilhada: conteúdo que pode sustentar mapas de mais de um processo.

Reservar uma faixa virtual não implica ocupar imediatamente o mesmo volume de RAM. Mapear um arquivo também não significa que todo seu conteúdo foi lido. E somar o residente de todos os processos pode contar páginas compartilhadas várias vezes.

No cenário do servidor, um processo pode exibir um espaço virtual grande e um conjunto residente moderado sem estar vazando memória. O diagnóstico precisa observar tendência, carga, páginas privadas, limites, eventos do sistema e comportamento do alocador. A aula de monitoramento voltará a essas métricas; a próxima explicará por que uma página pode não estar residente.

Quatro cartões separam espaço virtual reservado, sustentação comprometida, conjunto residente na RAM e páginas compartilhadas entre processos
As métricas se sobrepõem, mas não são intercambiáveis. Um número grande de endereço virtual não equivale ao mesmo consumo físico exclusivo.

Um mapa real pode ser observado com segurança

No Linux, /proc/<PID>/maps expõe as regiões atualmente mapeadas e suas permissões. Para observar o próprio comando de leitura sem alterar o sistema:

head -n 12 /proc/self/maps

Uma linha tem esta forma geral:

faixa-virtual  permissões  deslocamento  dispositivo  inode  origem

Procure intervalos em hexadecimal; combinações como r-xp ou rw-p; caminhos do executável e das bibliotecas; e identificadores como [heap] ou [stack]. O sufixo p indica mapeamento privado no formato dessa interface, enquanto s indica compartilhado. Ausência de nome pode representar memória anônima.

O resultado depende do processo, arquitetura, distribuição, bibliotecas, runtime e randomização. Além disso, permissão para inspecionar outro PID pode ser limitada. O arquivo /proc/<PID>/smaps acrescenta métricas por mapeamento, porém custa mais para ler e também está sujeito a controles de acesso.

No PowerShell em Windows, uma visão resumida e não destrutiva pode ser obtida assim:

Get-Process |
  Sort-Object WorkingSet64 -Descending |
  Select-Object -First 5 ProcessName, WorkingSet64, VirtualMemorySize64, PrivateMemorySize64

Essas colunas não reproduzem exatamente as categorias do Linux. Use-as para comparar processos dentro da mesma plataforma, não para criar equivalências diretas entre ferramentas.

ASLR muda posições sem mudar o programa

A randomização do espaço de endereçamento, ou ASLR, varia posições de componentes como executável, bibliotecas, heap e pilhas entre execuções, conforme suporte e configuração. Isso dificulta ataques que dependem de endereços previsíveis.

Por esse motivo, capturar um endereço em uma execução e tratá-lo como identidade permanente de uma função ou objeto é incorreto. Depuradores combinam endereços com módulos, símbolos e deslocamentos. Logs úteis preservam contexto suficiente para simbolização posterior.

ASLR não corrige acesso fora dos limites, uso após liberação ou permissões excessivas. É uma camada de mitigação. O isolamento continua dependendo da tradução e da proteção de memória, além da correção do kernel e do hardware.

Espaço de 64 bits não significa RAM ilimitada

Um endereço de 64 bits permite representar uma faixa numérica enorme, mas processadores e sistemas normalmente implementam apenas parte desse espaço. Limites do sistema, arquitetura, processo, cgroups, quotas, compromisso e memória física continuam existindo.

Também é possível que um processo de 32 bits execute em um sistema de 64 bits e mantenha um espaço bem menor que o oferecido a processos nativos. Logo, “o computador tem 16 GB de RAM”, “o sistema é de 64 bits” e “este processo pode reservar determinada faixa” são afirmações sobre dimensões distintas.

O benefício central de um espaço amplo é acomodar mapas esparsos, arquivos, bibliotecas, regiões de crescimento e separações de segurança com menos disputa por intervalos. Isso não garante que cada endereço tenha sustentação física disponível.

Erros comuns

  • “O ponteiro contém um endereço da RAM.” Em aplicações comuns, ele contém um endereço virtual interpretado no espaço do processo.
  • “Endereços iguais indicam o mesmo objeto.” Somente dentro do mesmo espaço e momento; processos diferentes podem mapear o valor para conteúdos distintos.
  • “Toda memória virtual está no disco.” Virtual descreve o espaço e a tradução; páginas podem estar na RAM, ter origem em arquivo, ser anônimas ou ainda não estar materializadas.
  • “Reservar 2 GB consome imediatamente 2 GB de RAM.” Reserva de endereço, compromisso e residência são estados diferentes e dependem da plataforma.
  • “Heap é toda memória dinâmica e uma única faixa contínua.” Alocadores e runtimes podem usar múltiplos mapeamentos.
  • “Cada processo possui uma única pilha.” Cada thread precisa sustentar sua cadeia de execução e normalmente possui pilha própria.
  • “Memória compartilhada remove as proteções.” O compartilhamento é um mapeamento controlado e pode ter permissões diferentes por participante.
  • “Falha de segmentação significa falta de RAM.” Normalmente sinaliza um acesso inválido; exaustão possui outros caminhos e diagnósticos.
  • “O layout do diagrama é universal.” Ordem, endereços e crescimento variam entre plataformas e execuções.
  • “RSS é o consumo exclusivo do processo.” Páginas residentes podem ser compartilhadas e métricas possuem definições específicas.

O que você deve guardar

Memória física é um recurso finito do sistema. Processos comuns operam sobre endereços virtuais, e a tradução mantida pelo hardware e pelo sistema operacional conecta esses endereços à sustentação física. O mesmo valor virtual pode significar coisas diferentes em processos diferentes.

O espaço de um processo é privado, esparso e dividido em regiões com limites, origem e permissões. Código, dados, heap, mapeamentos e pilhas formam um modelo útil, mas não uma planta universal. Threads compartilham o mapa do processo e mantêm pilhas próprias.

Reserva virtual, compromisso, residência e memória privada respondem a perguntas distintas. Ferramentas como /proc/<PID>/maps ajudam a ver o mapa, desde que seus números sejam interpretados no contexto da plataforma.

Na próxima aula, veremos como paginação, tabelas de páginas, TLB, page faults e swap implementam e tornam dinâmica essa relação entre endereços e memória física.

Referências