OS Dev na prática para x86 – 3. Layout da Memória em 16 bits

By Vovô

Bom, como dito anteriormente, durante o boot, estaremos trabalhando como os antigos processadores de 16 bits, e nesse caso, a memória é acessada de uma maneira específica e tem bastante peculiaridades.
Antes de iniciarmos, eu esqueci de cobrar no primeiro artigo, mas você também tem que saber hexadecimal pra usar esses posts x]
Para começar, quando estamos em 16 bits, teremos acesso a registradores de 16 bits e os mais sábios saberão que 16 bits é suficiente pra armazenar 65536 valores diferentes, e por um acaso do destino, 65536 é exatamente 64 KB, ou seja, com um registrador de 16 bits, podemos endereçar 64KB de memória (cada valor indica a posição de um byte na memória). Como 64 KB é pouquissima memória (até pra um 286), os engenheiros de hardware utilizaram uma avançada técnica onde dois registradores seriam utilizados para acessar a memória. Num mundo perfeito, 16 bits + 16 bits dariam 32 bits, e com 32 bits teríamos 4 GB de valores possíveis (e conseqüentemente poderíamos endereçar 4GB de memória), mas quem já teve um 286 sabe que as coisas não são bem assim.
O que ocorre na verdade é que a memória foi dividida em vários segmentos de 64 KB, ou seja, você utilizará um dos registradores de segmento pra indicar em qual segmento vc irá estar trabalhando. Existem vários registradores de segmento (CS, DS, ES, FS, GS e SS), e como são todos de 16 bits, você poderá utilizá-los para endereçar 65536 segmentos.
Aqui usarei a notação [x:y] para indicar segmento e offset, respectivamente. Eu ainda não tinha falado sobre offset. Como cada segmento tem 64 KB, pra obter um byte específico no seu segmento, por exemplo, o quinto byte no segmento 3, o segmento será 3 (óbvio) e o offset será 4 (lembrando que o zero é o primeiro byte). O offset é também chamado de deslocamento.

Endereçamento no segmento 2:

Byte [Seg:Off] [Seg:Off] hexadecimal
Byte 0 [2:0] [0x2:0x0]
Byte 1 [2:1] [0x2:0x1]
Byte 10 [2:10] [0x2:0x0A]
Byte 256 [2:256] [0x2:0x100]
Byte 512 [2:512] [0x2:0x200]
Byte 4096 [2:4096] [0x2:0x1000]
Byte 32768 [2:32768] [0x2:0x8000]
Byte 65535 [2:65535] [0x2:0xFFFF] (este é o último byte do segmento)

Bom, é assim que acessamos os bytes do segmento 2, mas como eu disse anteriormente, você não terá 4GB de endereços lineares mesmo tendo 64KB de segmentos e cada um com 64KB bytes (e 64KB*64KB=4GB) e isso porque cada segmento inicia em intervalos de 16 bytes (???).
Antes de entrarmos em detalhes, você precisa saber que um endereço linear corresponde a posição do byte na memória iniciando com zero, ou seja, o byte 0 está no endereço linear 0, o byte 7878 está no endereço linear 7878, o byte 1048576 está no endereço linear 1MB (pois 1048576 = 1MB), e assim em diante

Para entender melhor:

Endereço Linear Segmento:Offset
0 [0:0]
16 [0:16] e [1:0]
32 [2:0], [1:16] e [0:32]
48 [3:0], [2:16], [1:32] e [0:48]
64 [4:0], [3:16], [2:32], [1:48] e [0:64]
80 [5:0], [4:16], [3:32], [2:48], [1:64] e [0:80]
[...] [...]

Então como você viu, os segmentos se sobrepõem a cada 16 bytes, ou seja, o byte zero do segmento 1 é o byte 16 do segmento zero, e assim em diante então na verdade, quando acessamos o byte 4 do segmento 2, podemos acessá-lo de três maneiras: [2:4], [1:20] e [0:36]. Estes três endereços vão te levar ao byte de endereço linear 36.
É meio estranho, mas uma vez que você entenda esse conceito, a memória será um livro aberto pra você =]
Como eu disse anteriormente, nada de 4GB de memória, pois como você agora sabe, os segmentos se sobrepõem, então até onde podemos endereçar a memória com segmentos em modo de 16 bits? Bom, para isso, podemos deduzir uma fórmula genérica que nos dê o endereço linear de um endereço composto de segmento e offset. Não é tão difícil e no fim você chegará na seguinte expressão:

endereço linear = segmento * 16 + offset

E para obter o contrário:

segmento = endereço linear / 16
offset = RESTO_DA_DIVISAO(endereço linear / 16)

Mas o que nos interessa agora é o primeiro caso. Conhecendo a fórmula, e sabendo que num registrador de 16 bits o máximo valor armazenável será 65535, poderemos obter o endereço linear do último segmento:

endereço linear = segmento *16 + offset
endereço linear = 65535 * 16 + 0
endereço linear = 1048560

Como os mais sábios notarão, estamos a 16 bytes do primeiro MB de memória. Se acessarmos o byte 16 e 17 desse segmento teremos, respectivamente, o byte 1MB e o byte (1MB + 1 byte) da memória, e se acessarmos o último byte do segmento, teremos o byte 1114095 (aprox. 1,062 MB). Então com esse modelo de segmentação da memória, podemos acessar um pouco mais de 1MB de memória. Esse “pouco mais de” trouxe problemas para placas-mãe antigas onde o bus de acesso à memória era de no 20 bits, pois com 20 bits você endereça 1MB (!). Então nesses computadores, havia a impossibilidade física de acessar mais de 1 MB de memória, mesmo com a possibilidade do acesso lógico. Para contornar este problema, surgiu um mecanismo que permitia fazer um wrap-around da memória após 1 MB, ou seja, ao acessar o endereço 1MB, esse endereço seria traduzido para o byte 0, o byte 1MB+1 seria traduzido parao byte 1, e assim em diante, então para tudo o que fosse acessado acima de 1MB, o computador realizaria a seguinte operação:

se endereço-linear > 1MB, então

endereço final = endereço linear – 1MB


Na verdade o que ocorria é um right shift de 20 bits, mas o resultado é o mesmo. Isso tudo é transparente (invisível) aos programas, tanto o sistema de segmento:offset quanto esse wrap-around a partir do endereço de 1MB.
Bom, por fim, agora que você já conhece melhor a memória, vamos ver o que há em especial nesse primeiro byte:

Endereço Descrição
[0:0x0000] Tabela de interrupções
[0:0x7C00] MBR ou Setor de boot
[0xA000:0] Memória de vídeo para modo gráfico
[0xB800:0] memória de vídeo para modo texto
[0xF000:0] BIOS

Bom, é isso. Até o próximo post sobre o disco.

Deixe uma resposta