DevSecOpsARTIGO

Como o kernel gerencia sua memória – Parte 02

Esta é a segunda parte do artigo sobre gerenciamento da memória. A primeira está disponível aqui.

?

O Linux tem funções para ler e configurar cada flag em um PTE. Bit P diz ao processador se a página virtual está presente na memória física. Se for vazio (igual a 0), o acesso à página aciona uma page fault. Tenha em mente que quando o bit é zero, o kernel pode fazer o que ele quiser com os campos mantidos. A flag R/W significa read/write; se estiver vazia, a página é somente para leitura. A flag U/S significa user/supervisor; se estiver vazia, a página somente pode ser acessada pelo kernel. Essas flags são usadas para implementar a memória somente de leitura e o espaço de kernel protegido, como vimos antes.

Bits D e A são para dirty e accessed. Uma dirty page foi escrita, a accessed page foi escrita ou lida. Ambas as flags são “pegajosas”: o processador apenas as prepara, elas devem ser liberadas pelo kernel. Finalmente, o PTE armazena os endereços físicos iniciais que correspondem a essa página, alinhada a 4KB. Esse campo aparentemente inocente pode ser uma fonte de dor, pelo seu limite de memória física endereçável de 4 GB. Os outros campos PTE ficam para outro dia, pois são uma extensão do Endereço Físico.

Uma página virtual é a unidade de proteção de memória porque todos os seus bytes compartilham as flags U/S e R/W. No entanto, essa mesma memória física poderia ser mapeada por páginas diferentes, possivelmente com diferentes flags de proteção. Note que permissões de execução não aparecem em lugar nenhum no PTE. É por isso que a paginação clássica do x86 permite que o código na pilha seja executado, facilitando explorar o excesso de buffer da pilha (ainda é possível explorar pilhas não executáveis usando return-to-libc e outras técnicas). Essa falta de uma flag não executável no PTE ilustra uma questão maior: flags de permissão em um VMA podem ou não traduzir corretamente para a proteção do hardware. O kernel faz o que ele pode, mas no final das contas a arquitetura limita o que é possível.

A memória virtual não armazena nada, ela simplesmente mapeia o espaço de endereço de um programa para a memória física subjacente, que é acessada através do processador como um grande bloco chamado espaço de endereço físico. Apesar de operações de memória no barramento estarem de certa forma envolvidas, podemos ignorar isso aqui e pressupor que os endereços físicos vão de zero ao topo da disponibilidade da memória em incrementos em um byte. Esse espaço de endereço físico é quebrado pelo kernel em frames de páginas. O processador não sabe ou não se importa com os frames, e mesmo assim eles são cruciais para o kernel, porque o frame da página é a unidade do gerenciamento de memória. Tanto o Linux quanto o Windows usam frames de páginas de 4KB em modo 32-bit; aqui está um exemplo de uma máquina com 2GB de RAM:

No Linux, cada frame de página é rastreado por um descritor e várias flags. Juntos, esses descritores rastreiam toda a memória física no computador; o estado preciso de cada frame da página é sempre conhecido. A memória física é gerenciada com a técnica buddy memory allocation, assim o frame da página fica livre se estiver disponível para alocação através do sistema buddy. Um frame da página alocado pode ser anônimo, contendo dados de programas, ou pode estar no cache da página, mantendo os dados armazenados em arquivo ou em dispositivo de bloco. Existem outros usos exóticos para frames de páginas, mas os deixaremos de lado agora. O Windows tem um banco de dados análogo – Page Frame Number (PFN) para rastrear a memória física.

Vamos juntar áreas de memória virtual, entradas de tabelas de páginas, e frames de páginas para compreender como tudo isso funciona. Abaixo está um exemplo do heap do usuário:

Retângulos azuis representam páginas no alcance do VMA, enquanto as setas representam as entradas da tabelas de páginas mapeadas em frames de páginas.

Algumas páginas virtuais não têm setas; isso significa que seus PTEs correspondentes têm a flag atual livre. Isso poderia acontecer porque as páginas nunca foram tocadas ou porque seus conteúdos foram trocados. Em qualquer um dos casos, o acesso a essas páginas levará a falhas de página, apesar de elas estarem dentro do VMA. Parece estranho o VMA e as tabelas de páginas discordarem, mas muitas vezes isso acontece.

Um VMA é como um contrato entre seu programa e o kernel. Você pede para alguma coisa ser feita (alocação de memória, mapeamento de arquivo etc), o kernel diz “claro”, e cria ou atualiza o VMA apropriado. Mas ele não honra o pedido imediatamente, ele espera que a falha da página aconteça para que ele faça seu trabalho. O kernel é um saco preguiçoso e enganoso de lixo; esse é o principio fundamental da memória virtual. Isso se aplica na maior parte das situações, algumas familiares e outras surpreendentes, mas a regra é que os VMAs gravam o que foi acordado, enquanto os PTEs refletem o que de fato foi feito pelo kernel preguiçoso. Essas duas estruturas de dados juntas gerenciam a memória de um programa; ambas atuam na resolução das falhas de páginas, liberação de memória, troca de memória, e assim por diante. Vamos ver um simples caso de alocação de memória:

Quando o programa pede por mais memória através do chamada de sistema brk(), o kernel simplesmente atualiza a heap VMA e diz que está tudo certo. Nenhum frame de página é de fato alocado nesse ponto, e as novas páginas não estão presentes na memória física.

Uma vez que o programa tenta acessar as páginas, o processador da página falha e o do_page_fault() é chamado. Ele busca pelo VMA que está cobrindo o endereço virtual que falhou usando find_vma(). Caso encontradas, as permissões no VMA também são verificadas contra as tentativas de acesso (leitura ou escrita). Se não existe um VMA adequado, nenhum contrato cobre a tentativa de acesso da memória e o processo punido com uma Falha de Segmentação.

Quando um VMA é encontrado, o kernel deve manipular a falha olhando os conteúdos do PTE e o tipo de VMA. No nosso caso, o PTE mostra que a página não está presente. Na verdade, nosso PTE é completamente vazio (tudo zeros), o que no Linux significa que a página virtual nunca foi mapeada. Como esse é um VMA anônimo, temos simplesmente uma RAM que deve ser manipulada pelo do_anonymous_page(), que aloca o frame da página e gera um PTE para mapear a página virtual que falhou para o novo frame alocado.

As coisas poderiam ter sido diferentes. O PTE para uma página trocada, por exemplo, tem 0 na sua flag atual (Present?), mas não é nula. Em vez disso, ela armazena o local de troca, segurando os conteúdos da pagina, que devem ser lidos a partir do disco e carregados em um frame de página pelo do_swap_page() no qual é chamado major fault.

?

Texto original disponível em http://duartes.org/gustavo/blog/post/how-the-kernel-manages-your-memory

Matérias especiais e reportagens conduzidas internamente pela Redação iMasters. Acompanhe no Twitter @imasters e no Instagram/Threads @portalimasters

Ver perfil