Versão desta resposta com um bom sumário e mais conteúdo .
Vou corrigir qualquer erro relatado. Se você quiser fazer grandes modificações ou adicionar um aspecto ausente, faça-as em suas próprias respostas para obter uma representação bem merecida. Pequenas edições podem ser mescladas diretamente no.
Código de amostra
Exemplo mínimo: https://github.com/cirosantilli/x86-bare-metal-examples/blob/5c672f73884a487414b3e21bd9e579c67cd77621/paging.S
Como tudo na programação, a única maneira de realmente entender isso é brincar com exemplos mínimos.
O que torna este um assunto "difícil" é que o exemplo mínimo é grande porque você precisa fazer seu próprio sistema operacional pequeno.
Manual da Intel
Embora seja impossível entender sem exemplos em mente, tente se familiarizar com os manuais o mais rápido possível.
A Intel descreve o paging no Manual da Intel Volume 3 System Programming Guide - 325384-056US Setembro de 2015, Capítulo 4 "Paging".
Especialmente interessante é a Figura 4-4 "Formatos de CR3 e entradas de estrutura de paginação com paginação de 32 bits", que fornece as principais estruturas de dados.
MMU
A paginação é feita pela unidade de gerenciamento de memória (MMU) da CPU. Como muitos outros (por exemplo, coprocessador x87 , APIC ), isso costumava ser por chip separado nos primeiros dias, que mais tarde foi integrado à CPU. Mas o termo ainda é usado.
Fatos gerais
Os endereços lógicos são os endereços de memória usados no código de terreno do usuário "normal" (por exemplo, o conteúdo de rsi
in mov eax, [rsi]
).
Primeiro, a segmentação os traduz em endereços lineares e, em seguida, a paginação traduz os endereços lineares em endereços físicos.
(logical) ------------------> (linear) ------------> (physical)
segmentation paging
Na maioria das vezes, podemos pensar em endereços físicos como a indexação de células de memória de hardware RAM reais, mas isso não é 100% verdadeiro devido a:
Paging só está disponível no modo protegido. O uso de paginação no modo protegido é opcional. A paginação está ativada se o PG
bit do cr0
registro estiver definido.
Paging vs segmentação
Uma das principais diferenças entre paginação e segmentação é que:
- paging divide a RAM em blocos de tamanhos iguais chamados de páginas
- a segmentação divide a memória em pedaços de tamanhos arbitrários
Esta é a principal vantagem da paginação, uma vez que pedaços de tamanhos iguais tornam as coisas mais gerenciáveis.
O paging se tornou tão mais popular que o suporte para segmentação foi abandonado no x86-64 no modo de 64 bits, o principal modo de operação para o novo software, onde só existe no modo de compatibilidade, que emula IA32.
Inscrição
Paging é usado para implementar espaços de endereços virtuais de processos no sistema operacional moderno. Com endereços virtuais, o sistema operacional pode caber dois ou mais processos simultâneos em uma única RAM de forma que:
- ambos os programas não precisam saber nada sobre o outro
- a memória de ambos os programas pode aumentar e diminuir conforme necessário
- a troca entre programas é muito rápida
- um programa nunca pode acessar a memória de outro processo
A paginação veio historicamente após a segmentação e a substituiu amplamente para a implementação de memória virtual em sistemas operacionais modernos como o Linux, uma vez que é mais fácil gerenciar os pedaços de memória de tamanho fixo das páginas em vez de segmentos de comprimento variável.
Implementação de hardware
Como a segmentação em modo protegido (onde a modificação de um registro de segmento dispara uma carga do GDT ou LDT), o hardware de paginação usa estruturas de dados na memória para fazer seu trabalho (tabelas de páginas, diretórios de páginas, etc.).
O formato dessas estruturas de dados é fixado pelo hardware , mas cabe ao sistema operacional configurar e gerenciar essas estruturas de dados na RAM corretamente e dizer ao hardware onde encontrá-las (via cr3
).
Algumas outras arquiteturas deixam a paginação quase completamente nas mãos do software, portanto, um erro TLB executa uma função fornecida pelo sistema operacional para percorrer as tabelas de página e inserir o novo mapeamento no TLB. Isso deixa os formatos de tabela de página a serem escolhidos pelo sistema operacional, mas torna improvável que o hardware seja capaz de sobrepor passeios de página com a execução fora de ordem de outras instruções, como o x86 pode .
Exemplo: esquema simplificado de paginação de nível único
Este é um exemplo de como a paginação opera em uma versão simplificada da arquitetura x86 para implementar um espaço de memória virtual.
Tabelas de página
O sistema operacional pode fornecer as seguintes tabelas de página:
Tabela de página fornecida ao processo 1 pelo sistema operacional:
RAM location physical address present
----------------- ----------------- --------
PT1 + 0 * L 0x00001 1
PT1 + 1 * L 0x00000 1
PT1 + 2 * L 0x00003 1
PT1 + 3 * L 0
... ...
PT1 + 0xFFFFF * L 0x00005 1
Tabela de página fornecida ao processo 2 pelo sistema operacional:
RAM location physical address present
----------------- ----------------- --------
PT2 + 0 * L 0x0000A 1
PT2 + 1 * L 0x0000B 1
PT2 + 2 * L 0
PT2 + 3 * L 0x00003 1
... ... ...
PT2 + 0xFFFFF * L 0x00004 1
Onde:
PT1
e PT2
: posição inicial das tabelas 1 e 2 na RAM.
Os valores da amostra: 0x00000000
, 0x12345678
, etc.
É o sistema operacional que decide esses valores.
L
: comprimento de uma entrada da tabela de página.
present
: indica que a página está presente na memória.
As tabelas de páginas estão localizadas na RAM. Eles podem, por exemplo, estar localizados como:
--------------> 0xFFFFFFFF
--------------> PT1 + 0xFFFFF * L
Page Table 1
--------------> PT1
--------------> PT2 + 0xFFFFF * L
Page Table 2
--------------> PT2
--------------> 0x0
As localizações iniciais na RAM para ambas as tabelas de página são arbitrárias e controladas pelo sistema operacional. Depende do sistema operacional garantir que eles não se sobreponham!
Cada processo não pode tocar em nenhuma tabela de página diretamente, embora possa fazer solicitações ao SO que faça com que as tabelas de página sejam modificadas, por exemplo, pedindo uma pilha maior ou segmentos de heap.
Uma página é um pedaço de 4 KB (12 bits) e, como os endereços têm 32 bits, apenas 20 bits (20 + 12 = 32, portanto 5 caracteres em notação hexadecimal) são necessários para identificar cada página. Este valor é fixado pelo hardware.
Entradas de tabela de página
Uma tabela de páginas é ... uma tabela de entradas da tabela de páginas!
O formato exato das entradas da tabela é fixado pelo hardware .
Neste exemplo simplificado, as entradas da tabela de páginas contêm apenas dois campos:
bits function
----- -----------------------------------------
20 physical address of the start of the page
1 present flag
portanto, neste exemplo, os designers de hardware poderiam ter escolhido L = 21
.
A maioria das entradas da tabela da página real possui outros campos.
Seria impraticável alinhar as coisas em 21 bits, pois a memória é endereçável por bytes e não por bits. Portanto, mesmo que apenas 21 bits sejam necessários neste caso, os projetistas de hardware provavelmente escolheriam L = 32
tornar o acesso mais rápido e apenas reservar os bits restantes para uso posterior. O valor real para L
em x86 é de 32 bits.
Tradução de endereços em esquema de nível único
Uma vez que as tabelas de página tenham sido configuradas pelo sistema operacional, a tradução de endereços entre os endereços linear e físico é feita pelo hardware .
Quando o sistema operacional deseja ativar o processo 1, ele define o cr3
como PT1
, o início da tabela do processo um.
Se o Processo 1 deseja acessar o endereço linear 0x00000001
, o circuito de hardware de paging faz automaticamente o seguinte para o SO:
dividir o endereço linear em duas partes:
| page (20 bits) | offset (12 bits) |
Portanto, neste caso, teríamos:
- página = 0x00000
- deslocamento = 0x001
olhe para a tabela 1 da página porque cr3
aponta para ela.
olhe a entrada 0x00000
porque essa é a parte da página.
O hardware sabe que essa entrada está localizada no endereço RAM PT1 + 0 * L = PT1
.
uma vez que está presente, o acesso é válido
pela tabela de páginas, a localização do número da página 0x00000
está em 0x00001 * 4K = 0x00001000
.
para encontrar o endereço físico final, só precisamos adicionar o deslocamento:
00001 000
+ 00000 001
-----------
00001 001
porque 00001
é o endereço físico da página pesquisada na tabela e 001
é o deslocamento.
Como o nome indica, o deslocamento é sempre simplesmente adicionado ao endereço físico da página.
o hardware então obtém a memória naquele local físico.
Da mesma forma, as seguintes traduções aconteceriam para o processo 1:
linear physical
--------- ---------
00000 002 00001 002
00000 003 00001 003
00000 FFF 00001 FFF
00001 000 00000 000
00001 001 00000 001
00001 FFF 00000 FFF
00002 000 00002 000
FFFFF 000 00005 000
Por exemplo, ao acessar o endereço 00001000
, a parte da página é que 00001
o hardware sabe que sua entrada na tabela da página está localizada no endereço RAM: PT1 + 1 * L
( 1
por causa da parte da página), e é onde ele a procurará.
Quando o sistema operacional deseja mudar para o processo 2, tudo o que precisa fazer é cr3
apontar para a página 2. É simples assim!
Agora, as seguintes traduções aconteceriam para o processo 2:
linear physical
--------- ---------
00000 002 00001 002
00000 003 00001 003
00000 FFF 00001 FFF
00001 000 00000 000
00001 001 00000 001
00001 FFF 00000 FFF
00003 000 00003 000
FFFFF 000 00004 000
O mesmo endereço linear se traduz em diferentes endereços físicos para diferentes processos , dependendo apenas do valor interno cr3
.
Dessa forma, todo programa pode esperar que seus dados comecem 0
e terminem em FFFFFFFF
, sem se preocupar com os endereços físicos exatos.
Falha de página
E se o Processo 1 tentar acessar um endereço dentro de uma página que não está presente?
O hardware notifica o software por meio de uma exceção de falha de página.
Em geral, cabe ao sistema operacional registrar um manipulador de exceções para decidir o que deve ser feito.
É possível que acessar uma página que não está na tabela seja um erro de programação:
int is[1];
is[2] = 1;
mas pode haver casos em que seja aceitável, por exemplo, no Linux quando:
o programa deseja aumentar sua pilha.
Ele apenas tenta acessar um determinado byte em um determinado intervalo possível e, se o sistema operacional estiver satisfeito, ele adiciona essa página ao espaço de endereço do processo.
a página foi trocada para o disco.
O sistema operacional precisará fazer algum trabalho por trás dos processos para colocar a página de volta na RAM.
O SO pode descobrir que este é o caso com base no conteúdo do resto da entrada da tabela de página, uma vez que se o sinalizador presente estiver desmarcado, as outras entradas da entrada da tabela de página são deixadas completamente para o SO fazer o que ele deseja.
No Linux, por exemplo, quando presente = 0:
se todos os campos da entrada da tabela da página forem 0, endereço inválido.
caso contrário, a página foi trocada para o disco e os valores reais desses campos codificam a posição da página no disco.
Em qualquer caso, o SO precisa saber qual endereço gerou a falha de página para poder lidar com o problema. É por isso que os simpáticos desenvolvedores do IA32 definem o valor de cr2
para esse endereço sempre que ocorre uma falha de página. O manipulador de exceções pode então apenas olhar cr2
para obter o endereço.
Simplificações
Simplificações da realidade que tornam este exemplo mais fácil de entender:
todos os circuitos de paging reais usam paging de vários níveis para economizar espaço, mas isso mostrou um esquema simples de nível único.
as tabelas de páginas continham apenas dois campos: um endereço de 20 bits e um sinalizador de presente de 1 bit.
As tabelas de página real contêm um total de 12 campos e, portanto, outros recursos que foram omitidos.
Exemplo: esquema de paginação multinível
O problema com um esquema de paginação de nível único é que ocuparia muita RAM: 4G / 4K = 1 milhão de entradas por processo. Se cada entrada tiver 4 bytes de comprimento, isso daria 4 MB por processo , o que é demais até para um computador desktop: ps -A | wc -l
diz que estou executando 244 processos agora, então isso ocuparia cerca de 1 GB da minha RAM!
Por esse motivo, os desenvolvedores x86 decidiram usar um esquema de vários níveis que reduz o uso de RAM.
A desvantagem desse sistema é que ele tem um tempo de acesso um pouco maior.
No esquema simples de paginação de 3 níveis usado para processadores de 32 bits sem PAE, os 32 bits de endereço são divididos da seguinte forma:
| directory (10 bits) | table (10 bits) | offset (12 bits) |
Cada processo deve ter um e apenas um diretório de página associado a ele, portanto, conterá pelo menos 2^10 = 1K
entradas de diretório de página, muito melhor do que o mínimo de 1M necessário em um esquema de nível único.
As tabelas de página são alocadas apenas conforme necessário pelo sistema operacional. Cada tabela de 2^10 = 1K
página tem entradas de diretório de página
Os diretórios da página contêm ... entradas do diretório da página! As entradas do diretório de páginas são iguais às entradas da tabela de páginas, exceto que apontam para endereços RAM de tabelas de páginas em vez de endereços físicos de tabelas . Como esses endereços têm apenas 20 bits de largura, as tabelas de páginas devem estar no início das páginas de 4 KB.
cr3
agora aponta para a localização na RAM do diretório da página do processo atual em vez das tabelas de página.
As entradas das tabelas de página não mudam em nada a partir de um esquema de nível único.
As tabelas de páginas mudam de um esquema de nível único porque:
- cada processo pode ter até 1K tabelas de página, uma por entrada de diretório de página.
- cada tabela de página contém exatamente 1 mil entradas em vez de 1 milhão de entradas.
A razão para usar 10 bits nos primeiros dois níveis (e não, digamos 12 | 8 | 12
) é que cada entrada da Tabela de Página tem 4 bytes de comprimento. Então, as 2 ^ 10 entradas de diretórios de páginas e tabelas de páginas se encaixarão perfeitamente em páginas de 4 KB. Isso significa que é mais rápido e simples alocar e desalocar páginas para esse propósito.
Tradução de endereços em esquema de vários níveis
Diretório de páginas fornecido ao processo 1 pelo sistema operacional:
RAM location physical address present
--------------- ----------------- --------
PD1 + 0 * L 0x10000 1
PD1 + 1 * L 0
PD1 + 2 * L 0x80000 1
PD1 + 3 * L 0
... ...
PD1 + 0x3FF * L 0
Tabelas de páginas fornecidas ao processo 1 pelo sistema operacional em PT1 = 0x10000000
( 0x10000
* 4K):
RAM location physical address present
--------------- ----------------- --------
PT1 + 0 * L 0x00001 1
PT1 + 1 * L 0
PT1 + 2 * L 0x0000D 1
... ...
PT1 + 0x3FF * L 0x00005 1
Tabelas de páginas fornecidas ao processo 1 pelo sistema operacional em PT2 = 0x80000000
( 0x80000
* 4K):
RAM location physical address present
--------------- ----------------- --------
PT2 + 0 * L 0x0000A 1
PT2 + 1 * L 0x0000C 1
PT2 + 2 * L 0
... ...
PT2 + 0x3FF * L 0x00003 1
Onde:
PD1
: posição inicial do diretório da página do processo 1 na RAM.
PT1
e PT2
: posição inicial da tabela de páginas 1 e tabela de páginas 2 para o processo 1 na RAM.
Portanto, neste exemplo, o diretório da página e a tabela da página podem ser armazenados na RAM da seguinte forma:
----------------> 0xFFFFFFFF
----------------> PT2 + 0x3FF * L
Page Table 1
----------------> PT2
----------------> PD1 + 0x3FF * L
Page Directory 1
----------------> PD1
----------------> PT1 + 0x3FF * L
Page Table 2
----------------> PT1
----------------> 0x0
Vamos traduzir o endereço linear 0x00801004
passo a passo.
Supomos que cr3 = PD1
, ou seja, ele aponta para o diretório da página que acabamos de descrever.
Em binário, o endereço linear é:
0 0 8 0 1 0 0 4
0000 0000 1000 0000 0001 0000 0000 0100
Agrupando como 10 | 10 | 12
dá:
0000000010 0000000001 000000000100
0x2 0x1 0x4
que dá:
- entrada do diretório da página = 0x2
- entrada da tabela da página = 0x1
- deslocamento = 0x4
Portanto, o hardware procura a entrada 2 do diretório da página.
A tabela do diretório da página informa que a tabela da página está localizada em 0x80000 * 4K = 0x80000000
. Este é o primeiro acesso à RAM do processo.
Como a entrada da tabela de página é 0x1
, o hardware examina a entrada 1 da tabela de página em 0x80000000
, que informa que a página física está localizada no endereço 0x0000C * 4K = 0x0000C000
. Este é o segundo acesso RAM do processo.
Por fim, o hardware de paging adiciona o deslocamento e o endereço final é 0x0000C004
.
Outros exemplos de endereços traduzidos são:
linear 10 10 12 split physical
-------- --------------- ----------
00000001 000 000 001 00001001
00001001 000 001 001 page fault
003FF001 000 3FF 001 00005001
00400000 001 000 000 page fault
00800001 002 000 001 0000A001
00801008 002 001 008 0000C008
00802008 002 002 008 page fault
00B00001 003 000 000 page fault
As falhas de página ocorrem se uma entrada de diretório de página ou uma entrada de tabela de página não estiver presente.
Se o sistema operacional quiser executar outro processo simultaneamente, ele fornecerá ao segundo processo um diretório de página separado e vinculará esse diretório a tabelas de página separadas.
Arquiteturas de 64 bits
64 bits ainda é muito endereço para os tamanhos de RAM atuais, portanto, a maioria das arquiteturas usará menos bits.
x86_64 usa 48 bits (256 TiB), e o PAE do modo legado já permite endereços de 52 bits (4 PiB).
12 desses 48 bits já estão reservados para o deslocamento, que deixa 36 bits.
Se for adotada uma abordagem de 2 níveis, a melhor divisão seria de dois níveis de 18 bits.
Mas isso significaria que o diretório da página teria 2^18 = 256K
entradas, o que consumiria muita RAM: quase uma paginação de nível único para arquiteturas de 32 bits!
Portanto, as arquiteturas de 64 bits criam ainda mais níveis de página, normalmente 3 ou 4.
O x86_64 usa 4 níveis em um 9 | 9 | 9 | 12
esquema, de modo que o nível superior ocupa apenas 2^9
as entradas de nível superior.
PAE
Extensão do endereço físico.
Com 32 bits, apenas 4 GB de RAM podem ser endereçados.
Isso começou a se tornar uma limitação para servidores grandes, então a Intel introduziu o mecanismo PAE no Pentium Pro.
Para aliviar o problema, a Intel adicionou 4 novas linhas de endereço, para que 64 GB pudessem ser endereçados.
A estrutura da tabela da página também é alterada se o PAE estiver ativado. A maneira exata como ela é alterada depende se o PSE está ativado ou desativado.
PAE é ativado e desativado através do PAE
bit de cr4
.
Mesmo que a memória total endereçável seja de 64 GB, os processos individuais ainda são capazes de usar até 4 GB. O sistema operacional pode, no entanto, colocar processos diferentes em blocos de 4 GB diferentes.
PSE
Extensão do tamanho da página.
Permite que as páginas tenham 4M (ou 2M se o PAE estiver ativado) em vez de 4K.
PSE é ativado e desativado através do PAE
bit de cr4
.
Esquemas de tabela de página PAE e PSE
Se PAE e PSE estiverem ativos, diferentes esquemas de nível de paginação serão usados:
sem PAE e sem PSE: 10 | 10 | 12
não PAE e PSE: 10 | 22
.
22 é o deslocamento dentro da página 4Mb, uma vez que o endereço de 22 bits é 4Mb.
PAE e sem PSE: 2 | 9 | 9 | 12
O motivo do projeto pelo qual 9 é usado duas vezes em vez de 10 é que agora as entradas não cabem mais em 32 bits, que foram todos preenchidos por 20 bits de endereço e 12 bits de sinalização significativos ou reservados.
O motivo é que 20 bits não são mais suficientes para representar o endereço das tabelas de página: 24 bits agora são necessários por causa dos 4 fios extras adicionados ao processador.
Portanto, os designers decidiram aumentar o tamanho das entradas para 64 bits e, para fazê-las caber em uma única tabela de página, é necessário reduzir o número de entradas para 2 ^ 9 em vez de 2 ^ 10.
O 2 inicial é um novo nível de página denominado Page Directory Pointer Table (PDPT), uma vez que aponta para os diretórios de página e preenche o endereço linear de 32 bits. Os PDPTs também têm 64 bits de largura.
cr3
agora aponta para PDPTs que devem estar no punho 4 GB de memória e alinhados em múltiplos de 32 bits para eficiência de endereçamento. Isso significa que agora cr3
tem 27 bits significativos em vez de 20: 2 ^ 5 para os 32 múltiplos * 2 ^ 27 para completar os 2 ^ 32 dos primeiros 4 GB.
PAE e PSE: 2 | 9 | 21
Os designers decidiram manter um campo de 9 bits de largura para caber em uma única página.
Isso deixa 23 bits. Deixando 2 para o PDPT manter as coisas uniformes com o caso PAE sem PSE, deixa 21 para deslocamento, o que significa que as páginas têm 2M de largura em vez de 4M.
TLB
O Translation Lookahead Buffer (TLB) é um cache para endereços de paginação.
Por ser um cache, ele compartilha muitos dos problemas de design do cache da CPU, como o nível de associatividade.
Esta seção deve descrever um TLB totalmente associativo simplificado com 4 entradas de endereço único. Observe que, como outros caches, os TLBs reais geralmente não são totalmente associativos.
Operação basica
Depois que ocorre uma tradução entre o endereço linear e o endereço físico, ele é armazenado no TLB. Por exemplo, um TLB de 4 entradas começa no seguinte estado:
valid linear physical
------ ------- ---------
> 0 00000 00000
0 00000 00000
0 00000 00000
0 00000 00000
O >
indica a entrada atual a ser substituída.
e depois que um endereço linear de página 00003
é traduzido para um endereço físico 00005
, o TLB se torna:
valid linear physical
------ ------- ---------
1 00003 00005
> 0 00000 00000
0 00000 00000
0 00000 00000
e após uma segunda tradução de 00007
para 00009
ele se torna:
valid linear physical
------ ------- ---------
1 00003 00005
1 00007 00009
> 0 00000 00000
0 00000 00000
Agora, se 00003
precisar ser traduzido novamente, o hardware primeiro procura o TLB e descobre seu endereço com um único acesso à RAM 00003 --> 00005
.
Claro, 00000
não está no TLB, pois nenhuma entrada válida contém 00000
uma chave.
Política de substituição
Quando o TLB é preenchido, os endereços mais antigos são substituídos. Assim como para o cache da CPU, a política de substituição é uma operação potencialmente complexa, mas uma heurística simples e razoável é remover a entrada menos usada recentemente (LRU).
Com LRU, a partir do estado:
valid linear physical
------ ------- ---------
> 1 00003 00005
1 00007 00009
1 00009 00001
1 0000B 00003
adicionar 0000D -> 0000A
daria:
valid linear physical
------ ------- ---------
1 0000D 0000A
> 1 00007 00009
1 00009 00001
1 0000B 00003
CAM
Usar o TLB torna a tradução mais rápida, porque a tradução inicial leva um acesso por nível de TLB , o que significa 2 em um esquema simples de 32 bits, mas 3 ou 4 em arquiteturas de 64 bits.
O TLB é geralmente implementado como um tipo caro de RAM chamada memória endereçável por conteúdo (CAM). O CAM implementa um mapa associativo no hardware, ou seja, uma estrutura que dada uma chave (endereço linear), recupera um valor.
Os mapeamentos também podem ser implementados em endereços de RAM, mas os mapeamentos de CAM podem exigir muito menos entradas do que um mapeamento de RAM.
Por exemplo, um mapa no qual:
- ambas as chaves e valores têm 20 bits (no caso de esquemas de paginação simples)
- no máximo 4 valores precisam ser armazenados de cada vez
pode ser armazenado em um TLB com 4 entradas:
linear physical
------- ---------
00000 00001
00001 00010
00010 00011
FFFFF 00000
No entanto, para implementar isso com RAM, seria necessário ter 2 ^ 20 endereços :
linear physical
------- ---------
00000 00001
00001 00010
00010 00011
... (from 00011 to FFFFE)
FFFFF 00000
o que seria ainda mais caro do que usar um TLB.
Invalidando entradas
Quando cr3
muda, todas as entradas de TLB são invalidadas, porque uma nova tabela de página para um novo processo será usada, então é improvável que qualquer uma das entradas antigas tenha algum significado.
O x86 também oferece a invlpg
instrução que invalida explicitamente uma única entrada TLB. Outras arquiteturas oferecem ainda mais instruções para entradas TLB invalidadas, como invalidar todas as entradas em um determinado intervalo.
Algumas CPUs x86 vão além dos requisitos da especificação x86 e fornecem mais coerência do que ela garante, entre modificar uma entrada da tabela de páginas e usá-la, quando ainda não estava armazenada em cache no TLB . Aparentemente, o Windows 9x confiava nisso para correção, mas as CPUs AMD modernas não oferecem passeios de página coerentes. CPUs da Intel sim, embora tenham que detectar especulações incorretas para isso. Tirar vantagem disso é provavelmente uma má ideia, já que provavelmente não há muito a ganhar, e há um grande risco de causar problemas sutis e sensíveis ao tempo que serão difíceis de depurar.
Uso do kernel Linux
O kernel do Linux faz uso extensivo dos recursos de paginação do x86 para permitir mudanças rápidas de processo com pequena fragmentação de dados.
Em v4.2
, olhe em arch/x86/
:
include/asm/pgtable*
include/asm/page*
mm/pgtable*
mm/page*
Parece não haver estruturas definidas para representar as páginas, apenas macros: include/asm/page_types.h
é especialmente interessante. Excerto:
#define _PAGE_BIT_PRESENT 0 /* is present */
#define _PAGE_BIT_RW 1 /* writeable */
#define _PAGE_BIT_USER 2 /* userspace addressable */
#define _PAGE_BIT_PWT 3 /* page write through */
arch/x86/include/uapi/asm/processor-flags.h
define CR0
, e em particular a PG
posição do bit:
#define X86_CR0_PG_BIT 31 /* Paging */
Bibliografia
Livre:
Não livre: