No coração do seu smartphone Android está o kernel Linux, um sistema operacional multitarefa moderno. Seu trabalho é gerenciar os recursos de computação em seu telefone, incluindo a CPU, a GPU, a tela, o armazenamento, a rede e assim por diante. Também é responsável pela memória de acesso aleatório (RAM). Os aplicativos, os serviços em segundo plano e até o próprio Android precisam de acesso à RAM. Como o Linux particiona essa memória e a aloca é vital para que seu smartphone funcione sem problemas. É aí que entra a memória virtual.
O que é Memória Virtual?
Como uma atualização rápida, os programas (aplicativos) consistem em código e dados. O código é carregado na memória quando você inicia um aplicativo. O código começa em um determinado ponto e progride uma instrução por vez. Os dados são lidos do armazenamento, recuperados pela rede, gerados ou uma combinação dos três. Cada local na memória que armazena código ou dados é conhecido por seu endereço. Assim como um endereço postal que identifica exclusivamente um edifício, um endereço de memória identifica exclusivamente um local na RAM.
A memória virtual mapeia os dados do aplicativo para um espaço na RAM física do seu telefone.
O problema é que os aplicativos não sabem onde serão carregados na RAM. Portanto, se o programa espera que o endereço 12048, por exemplo, seja usado como um contador, ele deve ser esse endereço exato. Mas o aplicativo pode ser carregado em outro lugar na memória e o endereço 12048 pode ser usado por outro aplicativo.
A solução é dar a todos os aplicativos endereços virtuais, que começam em 0 e vão até 4GB (ou mais em alguns casos). Assim, cada aplicativo pode usar qualquer endereço que precisar, incluindo 12048. Cada aplicativo tem seu próprio espaço de endereço exclusivo, mas virtual, e nunca precisa se preocupar com o que outros aplicativos estão fazendo. Esses endereços virtuais são mapeados para endereços físicos reais em algum lugar da RAM. É o trabalho do kernel Linux gerenciar todo o mapeamento dos endereços virtuais para endereços físicos.
Por que a Memória Virtual é útil?
A Memória Virtual é uma representação digital da memória física implementada para que cada aplicativo tenha seu próprio espaço de endereço privado. Isso significa que os aplicativos podem ser gerenciados e executados independentemente uns dos outros, pois cada aplicativo é autossuficiente em memória.
Este é o bloco de construção fundamental de todos os sistemas operacionais multitarefa, incluindo o Android. Como os aplicativos são executados em seu próprio espaço de endereço, o Android pode começar a executar um aplicativo, pausá-lo, alternar para outro aplicativo, executá-lo e assim por diante. Sem memória virtual, estaríamos presos executando apenas um aplicativo por vez.
Sem memória virtual, estaríamos presos executando apenas um aplicativo por vez.
Ele também permite que o Android use espaço de troca ou zRAM e, portanto, aumente o número de aplicativos que podem permanecer na memória antes de serem eliminados para dar espaço para um novo aplicativo. Você pode ler mais sobre como o zRAM afeta a multitarefa do smartphone no link abaixo.
Consulte Mais informação: Quanta RAM o seu telefone Android realmente precisa?
Esse é o básico da memória virtual coberto, então vamos entender exatamente como tudo funciona sob o capô.
Memória virtual e páginas
Para auxiliar no mapeamento do virtual para o físico, ambos os espaços de endereçamento são divididos em seções, chamadas de páginas. As páginas no espaço virtual e físico precisam ter o mesmo tamanho e geralmente têm 4K de comprimento. Para diferenciar as páginas virtuais das físicas, estas últimas são chamadas de quadros de página e não apenas páginas. Aqui está um diagrama simplificado mostrando o mapeamento de 64 K de espaço virtual para 32 K de RAM física.
Gary Sims / Autoridade Android
A página zero (de 0 a 4095) na memória virtual (VM) é mapeada para o quadro de página dois (8192 a 12287) na memória física. A página um (4096 a 8191) na VM é mapeada para o quadro de página 1 (também 4096 a 8191), a página dois é mapeada para o quadro de página cinco e assim por diante.
Uma coisa a notar é que nem todas as páginas virtuais precisam ser mapeadas. Como cada aplicativo recebe um amplo espaço de endereço, haverá lacunas que não precisam ser mapeadas. Às vezes, essas lacunas podem ter gigabytes de tamanho.
Se um aplicativo deseja acessar o endereço virtual 3101 (que está na página zero), ele é traduzido para um endereço na memória física no quadro de página dois, especificamente o endereço físico 11293.
A Unidade de Gerenciamento de Memória (MMU) está aqui para ajudar
Os processadores modernos têm um hardware dedicado que lida com o mapeamento entre a VM e a memória física. É chamado de Unidade de Gerenciamento de Memória (MMU). A MMU contém uma tabela que mapeia páginas para quadros de página. Isso significa que o SO não precisa fazer a tradução, ela acontece automaticamente na CPU, o que é muito mais rápido e eficiente. A CPU sabe que os aplicativos estão tentando acessar endereços virtuais e os traduz automaticamente em endereços físicos. O trabalho do SO é gerenciar as tabelas que são usadas pela MMU.
Como a MMU traduz os endereços?
Gary Sims / Autoridade Android
A MMU usa a tabela de páginas configurada pelo SO para converter endereços virtuais em endereços físicos. Continuando com nosso exemplo de endereço 3101, que é 0000 1100 0001 1101 em binário, a MMU o traduz para 11293 (ou 0010 1100 0001 1101). Ele faz assim:
- Os primeiros quatro bits (0000) são o número da página virtual. Ele é usado para pesquisar o número do quadro de página na tabela.
- A entrada para a página zero é o quadro de página dois, ou 0010 em binário.
- Os bits 0010 são usados para criar os primeiros quatro bits do endereço físico.
- Os doze bits restantes, chamados de deslocamento, são copiados diretamente para o endereço físico.
A única diferença entre 3101 e 11293 é que os primeiros quatro bits foram alterados para representar a página na memória física, em vez da página na memória virtual. A vantagem de usar páginas é que o próximo endereço, 3102, usa o mesmo quadro de página que 3101. Apenas o deslocamento muda, então quando os endereços ficam dentro da página 4K a MMU tem facilidade para fazer as traduções. Na verdade, o MMU usa um cache chamado Translation Lookaside Buffer (TLB) para acelerar as traduções.
Buffer de tradução Lookaside explicado
As caixas vermelhas destacam o TLB no Arm Cortex-X1
O Translation Lookaside Buffer (TLB) é um cache de traduções recentes realizadas pela MMU. Antes de um endereço ser traduzido, a MMU verifica se a tradução de página para página já está armazenada em cache no TLB. Se a pesquisa de página solicitada estiver disponível (um hit), a tradução do endereço estará imediatamente disponível.
Cada entrada TLB normalmente contém não apenas a página e os quadros de página, mas também atributos como tipo de memória, políticas de cache, permissões de acesso e assim por diante. Se o TLB não contiver uma entrada válida para o endereço virtual (falta), a MMU será forçada a procurar o quadro de página na tabela de página. Como a tabela de páginas está na memória, isso significa que a MMU precisa acessar a memória novamente para resolver o acesso contínuo à memória. O hardware dedicado dentro da MMU permite que ela leia a tabela de tradução na memória rapidamente. Uma vez que a nova tradução é realizada, ela pode ser armazenada em cache para possível reutilização futura.
Olhando para trás: A história do Android — a evolução do maior sistema operacional móvel do mundo
É tão simples assim?
Em um nível, as traduções realizadas pela MMU parecem bastante simples. Faça uma pesquisa e copie alguns bits. No entanto, existem alguns problemas que complicam as coisas.
Meus exemplos lidam com 64 K de memória, mas no mundo real os aplicativos podem usar centenas de megabytes, até mesmo um gigabyte ou mais de RAM. Uma tabela de páginas completa de 32 bits tem cerca de 4 MB de tamanho (incluindo quadros, ausente/presente, modificado e outros sinalizadores). Cada aplicativo precisa de sua própria tabela de páginas. Se você tiver 100 tarefas em execução (incluindo aplicativos, serviços em segundo plano e serviços Android), serão 400 MB de RAM apenas para armazenar as tabelas de páginas.
Para diferenciar as páginas virtuais das físicas, estas últimas são chamadas de quadros de página.
As coisas pioram se você passar de 32 bits, as tabelas de páginas devem ficar na RAM o tempo todo e não podem ser trocadas ou compactadas. Além disso, a tabela de páginas precisa de uma entrada para cada página, mesmo que não esteja sendo usada e não tenha um quadro de página correspondente.
A solução para esses problemas é usar uma tabela de páginas multinível. Em nosso exemplo de trabalho acima, vimos que quatro bits foram usados como números de página. É possível dividir a tabela em várias partes. Os dois primeiros bits podem ser usados como referência a outra tabela que contém a tabela de páginas para todos os endereços que começam com esses dois bits. Portanto, haveria uma tabela de páginas para todos os endereços começando com 00, outra para 01 e 10 e, finalmente, 11. Portanto, agora há quatro tabelas de páginas, além de uma tabela de nível superior.
Verificação de saída: Os melhores telefones com 16 GB de RAM
As tabelas de nível superior devem permanecer na memória, mas as outras quatro podem ser trocadas, se necessário. Da mesma forma, se não houver endereços começando com 11, nenhuma tabela de páginas será necessária. Em uma implementação do mundo real, essas tabelas podem ter quatro ou cinco níveis de profundidade. Cada tabela aponta para outra, de acordo com os bits relevantes no endereço.
Acima está um diagrama da documentação do RISC-V mostrando como essa arquitetura implementa o endereçamento virtual de 48 bits. Cada entrada de tabela de página (PTE) tem alguns sinalizadores no espaço que seriam usados pelo deslocamento. Os bits de permissão, R, W e X, indicam se a página é legível, gravável e executável, respectivamente. Quando todos os três são zero, o PTE é um ponteiro para o próximo nível da tabela de páginas; caso contrário, é um PTE folha e a pesquisa pode ser realizada.
Como o Android lida com uma falha de página
Quando o MMU e o OS estão em perfeita harmonia, tudo está bem. Mas pode haver erros. O que acontece quando a MMU tenta procurar um endereço virtual e não consegue encontrá-lo na tabela de páginas?
Isso é conhecido como falta de página. E existem três tipos de falha de página:
- Falha de página difícil — O quadro de página não está na memória e precisa ser carregado do swap ou do zRAM.
- Falha de página suave — Se a página for carregada na memória no momento em que a falha for gerada, mas não estiver marcada na unidade de gerenciamento de memória como sendo carregada na memória, ela será chamada de falha de página secundária ou suave. O manipulador de falhas de página no sistema operacional precisa fazer a entrada para essa página no MMU. Isso pode acontecer se a memória for compartilhada por aplicativos diferentes e a página já tiver sido trazida para a memória, ou quando um aplicativo tiver solicitado uma nova memória e tiver sido alocada preguiçosamente, aguardando o acesso da primeira página.
- Falha de página inválida — O programa está tentando acessar a memória que não está em seu espaço de endereço. Isso leva a uma falha de segmentação ou violação de acesso. Isso pode acontecer se o programa tentar gravar na memória somente leitura, ou deferir um ponteiro nulo ou devido a estouros de buffer.
Os benefícios da memória virtual
Como descobrimos, a Memória Virtual é uma maneira de mapear a memória física para que os aplicativos possam usar a RAM de forma independente, sem se preocupar com como outros aplicativos estão usando a memória. Ele permite que o Android execute várias tarefas e use a troca.
Sem memória virtual, nossos telefones seriam limitados a executar um aplicativo por vez, os aplicativos não poderiam ser trocados e qualquer tentativa de manter mais de um aplicativo por vez na memória precisaria de alguma programação sofisticada.