Assembly NumaBoa - Capítulo 4

LAYOUT E ACESSO À MEMÓRIA

Este capítulo é um elo importante entre a Organização de Sistemas e a Linguagem Assembly Básica, assuntos dos capítulos anteriores. Do ponto de vista da organização de sistemas, este capítulo cobre o endereçamento e a organização da memória, os modos de endereçamento da CPU e a representação de dados na memória. Do ponto de vista da programação em linguagem Assembly, este capítulo apresenta os conjuntos de registradores do 80x86, os modos de endereçamento do 80x86 e os tipos de dados compostos. Este é um capítulo vital. Se você não conseguir entender o material que ele contém, com certeza terá dificuldade em entender os capítulos seguintes. Portanto, nem é preciso dizer...

As CPUs 80x86 sob o ponto de vista do programador

Chegou a hora de discutir alguns processadores reais: os 8088/8086, os 80188/80186, o 80286 e os 80386/80486/80586/Pentium. No capítulo 3 forma mencionados muitos aspectos de hardware em sistemas de computadores. Apesar destes componentes de hardware afetarem o modo como se deve escrever software, há mais aspectos que devem ser considerados além de ciclos de barramento e pipelines.

Para um programador Assembly, o componente mais visível da CPU é o conjunto de registradores. Assim como os processadores hipotéticos x86, os chips 80x86 possuem um conjunto de registradores on-board. Para cada processador da família 80x86, este conjunto é na realidade um super conjunto. O melhor ponto para começar é o conjunto de registradores dos 8088, 8086, 80188 e 80186 porque estes quatro processadores possuem os mesmos registradores. No texto a seguir, o termo "8086" representa qualquer uma das quatro CPUs citadas.

Os projetistas da Intel classificaram os registradores do 8086 em três categorias: registradores de propósito geral, de segmento e miscelânea. Os registradores de propósito geral são os que podem aparecer como operandos em instruções aritméticas, lógicas e outras relacionadas. Apesar destes registradores serem de "propósito geral", cada um deles tem um uso especial próprio. O 8086 usa os registradores de segmento para acessar blocos de memória chamados de... segmentos. A última categoria são os registradores miscelânea. Há dois registradores especiais nesta classe que serão avaliados logo a seguir.

Os registradores de propósito geral do 8086

Existem oito registradores de 16 bits de propósito geral no 8086: ax, bx, cx, dx, si, di, bp e sp. Apesar de podermos usar estes registradores como quisermos, muitas instruções trabalham com mais eficiência ou até exigem um registrador específico deste grupo.

No registrador ax (o Acumulador) é onde ocorrem a maioria dos cálculos aritméticos e lógicos. Apesar de outros registradores poderem ser usados para estes cálculos, o registrador ax geralmente é mais eficiente. O registrador bx (Base) também possui algumas características especiais. Ele geralmente é usado para armazenar endereços indiretos. O registrador cx (o Contador), tem a função que o nome indica. Ele geralmente é usado para contar iterações de loops ou especificar o número de caracteres duma string. O registrador dx (Dados) tem dois usos especiais: armazenar o overflow de certas operações aritméticas e os endereços de E/S quando acessando dados no barramento do 80x86.

Os registradores si e di (Source Index = Índice da Fonte e Destination Index = Índice do Destino) também tê alguns usos especiais. Estes registradores podem ser usados como ponteiros (do tipo do registrador bx) para acessar indiretamente a memória. Também são usados com instruções string do 8086 quando strings de caracteres são processadas.

O registrador bp (Base Pointer = Ponteiro da Base) é semelhante ao registrador bx. Geralmente é usado para acessar parâmetros e variáveis locais num procedimento.

O registrador sp (Stack Pointer = Ponteiro da Pilha) tem um propósito muito especial - manter a pilha do programa. Normalmente este registrador seria usado para cálculos aritméticos. A operação adequada da maioria dos programas depende de uso criterioso deste registrador.

Registradores do 80x86

Fig.1 - Registradores de uso geral do 8086

Além dos oito registradores de 16 bits, as CPUs do 8086 também possuem oito registradores de 8 bits. A Intel chamou estes registradores de al, ah, bl, bh, cl, ch, dl e dh. Você deve ter notado a semelhança entre estes nomes e os dos registradores de 16 bits (para ser exato, ax, bx, cx e dx). Os registradores de 8 bits não são independentes. al representa "byte menos significante (low order) de ax". ah representa "byte mais significante (high order) de ax". Os nomes dos outros sete registradores de 8 bits significam a mesma coisa em relação a bx, cx e dx. A Fig.1 mostra o conjunto de registradores de propósito geral.

Note que os registradores de 8 bits não formam um conjunto independente de registradores - alterar al mudará o valor de ax, o mesmo acontecendo se alterarmos ah. O valor de al corresponde exatamente aos bits 0 a 7 de ax. O valor de ah corresponde aos bits 8 a 15 de ax. Portanto, qualquer modificação em al ou ah modificará o valor de ax. Do mesmo modo, modificando o valor de ax modificará tanto al quanto ah. Observe, no entanto, que modificar al não afeta o valor de ah e vice versa. Estas afirmações também são válidas para bx/bl/bh, cx/cl/ch e dx/dl/dh.

Os registradores si, di, bp e sp são de 16 bits totais. Não há como acessar bytes individuais nestes registradores como nos bytes menos e mais significativos de ax, bx, cx e dx.

Registradores de segmento no 8086

O 8086 possui quatro registradores de segmento especiais: cs, ds, es e ss. Os nomes correspondem a Code Segment (Segmento de Código), Data Segment (Segmento de Dados), Extra Segment (Segmento Extra) e Stack Segment (Segmento da Pilha). Estes registradores são todos de 16 bits e lidam com a seleção de blocos (segmentos) da memória principal. Um registrador de segmento (por exemplo o cs) aponta para o início de um segmento de memória.

Segmentos de memória no 8086 não podem ser maiores do que 65.536 bytes. Esta péssima "limitação de segmentos de 64 K" já infernizou a vida de muitos programadores. Veremos alguns problemas causados por esta limitação e algumas soluções mais adiante.

O registrador cs aponta para o segmento que contém a instrução de máquina em execução. Note que, apesar da limitação de segmentos de 64K, os programas do 8086 podem ser maiores do que 64K. Precisamos simplesmente multiplicar os segmentos de código na memória. Como podemos alterar o valor do registrador cs, podemos mudar para outro segmento de código quando a execução assim o exigir.

O registrador de segmento de dados, ds, geralmente aponta para variáveis globais do programa. Novamente estamos limitados a 65.536 bytes de dados em cada segmento de dados. Como sempre, podemos alterar o valor do registrador ds para acessar dados adicionais localizados em outros segmentos.

O registrador de segmento extra, es, é apenas o que o nome indica. Programas para o 8086 usam com frequência este registrador para ganhar acesso a segmentos quando for difícil ou impossível modificar outros registradores de segmento.

O registrador ss aponta para o segmento que contém a pilha (stack) do 8086. A pilha é onde o 8086 armazena informações importantes sobre o estado da máquina, endereços de retorno de subrotinas, parâmetros de procedimentos e variáveis locais. Em geral, não se modifica o registrador de segmento de pilha porque muitas coisas do sistema dependem dele.

Apesar de, teoricamente, ser possível armazenar dados nos registradores de segmento, a idéia não é das melhores. Estes registradores têm uma função muito especial - apontar para blocos de memória que podem ser acessados. Qualquer tentativa de usar estes registradores para outros propósitos pode resultar numa complicação considerável, principalmente se pretendermos trabalhar com CPUs melhores, como a 80386.

Registradores de propósito especial no 8086

Existem dois registradores de propósito especial na CPU 8086: o ponteiro de instruções (ip - instruction pointer) e o registrador de flags. Não acessamos estes registradores do mesmo modo que os outros registradores do 8086. Geralmente é a CPU que manipula estes registradores diretamente.

O registrador ip contém o endereço da instrução que está sendo executada. É um registrador de 16 bits que fornece um ponteiro para o segmento de código corrente (16 bits permitem selecionar qualquer uma das 65.536 posições da memória). Voltaremos a falar deste registrador quando analisarmos as instruções de transferência de controle.

Registrador de Flags

Fig.1a - Registrador de Flags do 8086

O registrador de flags é único diferente dos outros registradores do 8086. Os outros armazenam valores de 8 ou 16 bits. O registrador de flags é simplesmente uma coleção eclética de valores de 1 bit que ajudam a determinar o estado atual do processador. Apesar do resgistrador possuir 16 bits, o 8086 usa apenas nove deles. Dos usados, quatro são usados constantemente: zero, carry (o "vai um"), sinal e overflow. Estas flags são códigos de condição do 8086. O registrador de flags pode ser visto na Fig.1a.

Os registradores do 80286

O microprocessador 80286 possui uma capacidade visível ao programador que o destaca do 8086 - a operação em modo protegido. Não analisaremos o modo protegido do 80286 por uma série de razões. Em primeiro lugar, o modo protegido do 80286 foi muito mal projetado. Em segundo lugar, só tem importância para programadores que estejam escrevendo sistemas operacionais próprios ou programas de baixo nível para estes sistemas operacionais. Mesmo que se escreva software para sistemas operacionais de modo protegido, como UNIX ou OS/2, é melhor não usar o modo protegido do 80286. Ainda assim é interessante mostrar os registradores extras e as flags de status presentes no 80286, caso tenhamos que trabalhar com eles.

Existem três bits adicionais presentes no registrador de flags do 80286. O Nível de Privilégio de E/S é um valor de dois bits (bits 12 e 13). Estes especificam um dos quatro níveis diferentes de privilégio necessários para realizar operações de E/S. Estes dois bits geralmente contém 00b quando operando em modo real (o modo de emulação do 8086). A flag NT (nested task - tarefa aninhada) controla a operação de uma instrução de retorno de interrupção (IRET). A NT normalmente é zero para programas em modo real.

Além dos bits extras no registrador de flags, o 80286 tem também cinco registradores adicionais usados pelo sistema operacional para apoiar a administração da memória e processos múltiplos: o machine status word (msw - word de status da máquina), o global descriptor table register (gdtr - registrador da tabela de descritores globais), o local descriptor table register (ldtr - registrador de tabelas de descritores locais), o interrupt descriptor table register (idtr - registrador da tabela de descritores de interrupção) e o task register (tr - registrador de tarefas).

O único uso do modo protegido do 80286 por um programa é o acesso a mais do que 1 megabyte de RAM. Entretanto, como o 80286 está absolutamente obsoleto e como existem meios melhores de se acessar mais memória, não tem porque prolongar esta descrição.

Os registradores 80386/80486

O processador 80386 aumentou sensivelmente o conjunto de registradores do 8086. Além de todos os registradores do 80286 (e, portanto, do 8086), o 80386 adicionou vários registradores novos e ampliaou a definição dos registradores existentes. O 80486 não adicionou nenhum registrador novo ao 80386, mas definiu alguns bits em alguns registradores que ficaram indefinidos no 80386.

A mudança mais importante no 80386, do ponto de vista dos programadores, foi a introdução de um conjunto de registradores de 32 bits. Os registradores ax, bx, cx, dx, si, di, bp, sp, flags e ip foram todos ampliados para 32 bits. O 80386 chama estas novas versões de 32 bits de eax, ebx, ecx, edx, esi, edi, ebp, esp, eflags e eip para diferenciá-las das versões de 16 bits (que continuam disponíveis no 80386). Além dos registradores de 32 bits, o 80386 também fornece dois registradores de segmento de 16 bits novos, fs e gs, os quais permitem acessar concorrentemente seis segmentos diferentes de memória sem a necessidade de recarregar um registro de segmento. Note que todos os registradores de segmento no 80386 são de 16 bits, os quais não foram apliados para 32 bits como feito com os outros registradores.

O 80386 não fez qualquer alteração nos bits do registrador de flags. Este registrador foi ampliado para 32 bits (o registrador eflags) e os bits 16 e 17 foram definidos. O bit 16 é a flag de retomada de debug (RF - Resume Flag) usada com o conjunto de registradores de debug do 80386. O bit 17 é a flag do modo virtual do 8086 (VM - Virtual Mode) que determina se o processador está operando no modo virtual-86 (que simula um 8086) ou no modo protegido padrão. O 80486 adiciona um terceiro bit na posição 18 do registrador de flags - a flag de checagem de alinhamento. Junto com o registrador de controle zero (CR0 - Control Register 0), esta flag força uma armadilha (o programa é abortado) sempre que o processador acessar dados não alinhados (isto é, um word num endereço ímpar ou um double word num endereço que não seja múltiplo de quatro).

O 80386 adicionou quatro registradores de controle: CR0 a CR3. Estes registradores ampliam o registrador msw do 80286 (o 80386 emula o registrador msw do 80286 para manter a compatibilidade, mas a informação, na realidade, aparece nos registradores CRx). No 80386 e no 80486 estes registradores controlam funções como a administração de páginas de memória, habilitar/desabilitar/operar a cache (apenas o 80486), operação em modo protegido e mais.

Modelo 32 bits

Fig.2 - Modelo de programação para o 80386/486/Pentium

Os 80386/486 também adicionam oito registradores de debug. Programas como o Codeview ou o Turbo Debugger podem usar estes registradores para colocar pontos de parada (breakpoints) quando se está tentando localizar erros num programa. Apesar de não usarmos estes registradores em programas aplicativos, o uso de debuggers reduz o tempo que se gasta eliminando erros dos nossos programas. É claro que programas deste tipo funcionam só a partir do 80386.

Finalmente, os processadores 80386/486 adicionaram um conjunto de registradores de teste que testam se o processador está operando adequadamente quando o sistema é ligado. A Intel colocou estes resgistradores no chip para permitir o teste logo após a manufatura dos mesmos, mas é claro que os projetistas de sistemas podem tirar vantagem destes registradores para fazer testes de power-on.

Na maioria das vezes, programadores de Assembly não precisam se preocupar com os registradores extras adicionados nos processadores 80386/486/Pentium. Entretanto, a ampliação para 32 bits e os registros de segmento extras são bastante úteis. Para o programador de aplicações, o modelo de programação para o 80386/486/Pentium tem o aspecto mostrado na Fig.2.

Organização da Memória Física no 80x86

No capítulo 3 analisamos a organização básicas de um sistema de computador com a Arquitetura Von Neumann (VNA). Numa máquina VNA típica, a CPU se conecta à memória através do barramento. O 80x86 seleciona alguns elementos de memória em particular usando um número binário no barramento de endereços. Outro modo de ver a memória é como um array de bytes. Uma estrutura de dados em Pascal que, a grosso modo, corresponde à memória seria:

Memoria : array [0..MaxRAM] of byte;

O valor no barramento de endereços que corresponde ao índice fornecido a este array. Por exemplo, escrever dados na memória é equivalente a:

Memoria [endereco] := Valor_para_Escrever;

Ler dados na memória é equivalente a:

Valor_Lido := Memoria [endereco];

CPUs 80x86 diferentes possuem barramentos de endereços diferentes que controlam o número máximo de elementos no array Memoria. Entretanto, independentemente do número de linhas de endereço do barramento, a maioria dos sistemas de computador não possuem um byte de memória para cada localização endereçável. Por exemplo, os processadores 80386 possuem 32 linhas de endereço que permitem até quatro gigabytes de memória. São raros os sistemas 80386 que possuem quatro gigabytes de memória. Geralmente, os sistemas baseados no 80x86 possuem 256 megabytes.

O primeiro megabyte de memória, do endereço zero até 0FFFFFh, é especial no 80x86. Isto corresponde ao espaço de endereços total dos microprocessadores 8088, 8086, 80186, 80188. A maioria dos programas DOS limitam seus endereços de programa e dados para localizações neste intervalo. Endereços limitados a esta faixa são chamados de endereços reais, devido ao modo real do 80x86.

Segmentos no 80x86

Não é possível discutir o endereçamento de memória da família de processadores 80x86 sem antes analisar a segmentação. Entre outras coisas, a segmentação proporciona um mecanismo de gerenciamento de memória muito poderoso. Ela permite que programadores dividam seus programas em módulos que operem de forma independente. Segmentos proporcionam um modo de implementar com facilidade programas orientados a objeto. Segmentos permitem que dois processos compartilhem dados com facilidade. No final das contas, a segmentação é realmente uma capacidade interessante. Por outro lado, se perguntarmos a dez programadores o que eles pensam da segmentação, pelo menos nove vão reclamar, dizendo que é horrorosa. Porque este tipo de opinião?

Bem, acontece que a segmentação também tem uma característica elegante: ela permite ampliar o endereçamento do processador. No caso do 8086, a segmentação permitiu que os projetistas da Intel ampliassem a memória endereçável máxima de 64 K para 1 Mega. Uau, isto soa bem. Então, porque é que todo mundo reclama? Bem, uma pequena aula de história será necessária para entender o que é que deu errado.

Em 1976, quando a Intel começou a projetar o processador 8086, a memória era muito cara. Computadores pessoais, como eram na época, tinham tipicamente 4000 bytes de memória. Quando a IBM lançou o PC, cinco anos mais tarde, 64K era uma memória considerável, um megabyte era uma quantidade astronômica. Os projetistas da Intel perceberam que 64K de memória continuaria sendo uma quantidade grande durante a vida do 8086. O único erro que fizeram foi subestimar a sobrevida do 8086. Imaginaram que duraria cerca de cinco anos, como o processador anterior, o 8080. Na época, eles tinham planos para uma porção de outros processadores, e "86" não era o sufixo dos nomes deles. A Intel imaginou que estava sossegada, segura de que um megabyte seria mais do que suficiente até que lançassem algo melhor.

Infelizmente a Intel não contava com o sucesso do IBM PC e da enorme quantidade de software que apareceria para ele. Ao redor de 1983 já estava muito claro que a Intel não poderia abandonar a arquitetura 80x86. Estava preso a ele, mas aí o pessoal começou a esbarrar no limite de 1 megabyte do 8086. Então a Intel nos deu o 80286. Este processador podia endereçar até 16 megabytes de memória. Com certeza, mais do que suficiente. O único problema era que todo o maravilhoso software escrito para o IBM PC tinha sido escrito de uma forma que não poderia aproveitar qualquer memória acima de um megabyte.

Acabou ficando claro que o máximo de memória endereçável não era a queixa principal de todos. O problema real era que o 8086 era um processador de 16 bits, com registradores de 16 bits e endereços de 16 bits. Isto limitava o processador a endereçar pedaços de memória de 64K. O uso esperto da segmentação pela Intel ampliava isto para um megabyte mas, endereçar mais do que 64K por vez exigia algum esforço. Endereçar mais dos que 256K de uma vez exigia muito mais esforço.

Apesar do que você possa ter ouvido, a segmentação não é ruim. De fato, ela é um esquema de gerenciamento de memória realmente bom. O que é ruim é a implementação de segmentação feita pela Intel em 1976 que é usada até hoje. Não podemos culpar a Intel por isto - ela resolveu o problema nos anos 80 com o lançamento do 80386. O verdadeiro culpado é o MS-DOS que força os programadores a continuar usando o estilo de segmentação de 1976. Felizmente os sistemas operacionais mais novos, como Linux, UNIX, Windows 9x, Windows NT e OS/2 não sofrem dos mesmos males que o MS-DOS. Além disso, os usuários finais parecem mais propensos a mudar para estes sistemas operacionais mais novos de modo que os programadores podem aproveitar as vantagens das novas características da família 80x86.

Endereçamento segmentado

Fig.3 - Endereçamento por valores
de segmento e de deslocamento

Terminada a aula de história, provavelmente seja uma boa idéia explicar o que vem a ser a segmentação. Considere a concepção atual de memória: ela se parece com um array linear de bytes. Um único índice (endereço) seleciona algum byte em particular deste array. Chamemos este tipo de endereçamento de endereçamento linear ou plano. O endereçamento segmentado usa dois componentes para especificar uma localização na memória: um valor de segmento e um deslocamento dentro deste segmento. Na forma ideal, os valores do segmento e do deslocamento são independentes. O melhor jeito de descrever o endereçamento segmentado é com um array bidimensional. O segmento fornece um dos índices e o deslocamento o outro (veja na Fig.3).

Agora você deve estar se perguntado porque tornar este processo mais complexo. Endereços lineares parecem funcionar bem, para que se incomodar com este esquema de endereçamento de duas dimensões? Bem, vamos analisar o modo como costumamos escrever um programa. Se tivéssemos que escrever, digamos, uma rotina para calcular o seno de x e precisássemos de algumas variáveis temporárias, provavelmente não usaríamos variáveis globais. Pelo contrário, provavelmente usaríamos variáveis locais dentro da função seno. Num sentido amplo, esta é uma das características da segmentação - a possibilidade de ligar blocos de variáveis (um segmento) a um determinado trecho de código. Poderíamos, por exemplo, ter um segmento contendo as variáveis locais para seno, um segmento para raiz quadrada, um segmento para DRAWWindow, etc. Uma vez que as variáveis para a rotina do seno aparecem no segmento do seno, é pouco provável que elas afetem as variáveis que pertencem à rotina de raiz quadrada. De fato, no 80286 e posteriores, operando em modo protegido, a CPU pode prevenir uma rotina de modificar acidentalmente as variáveis de um outro segmento.

Um endereço segmentado completo contém um componente segmento e um componente deslocamento. Neste texto escreveremos endereços segmentados como segmento:deslocamento. Do 8086 até o 80286, estes dois valores são constantes de 16 bits. No 80386 e posteriores, o deslocamento pode ser uma constante de 16 ou de 32 bits.

O tamanho do deslocamento limita o tamanho máximo do segmento. No 8086 com deslocamentos de 16 bits, um segmento não pode ser maior do que 64K; pode ser menor (e a maioria dos segmentos são), mas nunca maior. Os processadores 80386 e posteriores permitem deslocamentos de 32 bits com segmentos de até 4 giagbytes.

A porção do segmento é de 16 bits em todos os processadores 80x86. Isto permite que um programa possa ter até 65.536 segmentos diferentes. A maioria dos programas possui menos do que 16 segmentos (mais ou menos) de modo que isto não é uma limitação prática.

Endereço físico

Fig.4 - Endereço físico (linear)

É claro que, apesar do fato de que a família 80x86 usar endereçamento segmentado, a memória (física) conectada à CPU continua sendo um array linear de bytes. Existe uma função que transforma o valor do segmento num endereço da memória física. O processador, então, soma o deslocamento a este endereço físico para obter o endereço real de dados na memória. Neste texto vamos nos referir aos endereços de programas como endereços segmentados ou endereços lógicos. O endereço linear que aparece no barramento de endereços é o endereço físico (veja a Fig.4).

No 8086, 8088, 80186 e 80188 ( e outros processadores operando em modo real), a função que mapeia um segmento para um endereço físico é muito simples. A CPU multiplica o valor do segmento por 16 (10h) e soma a porção do deslocamento. Por exemplo, considere o seguinte endereço segmentado 1000:1F00. Para transformá-lo num endereço físico basta multiplicar o valor do segmento (1000h) por 16 (ou 10h). Esta multiplicação é muito simples, basta adicionar um zero ao número: 1000h x 10h = 10000h. Adicionando o deslocamento obtemos 10000h + 1F00h = 11F00h. Portanto, 11F00h é o endereço físico que corresponde ao endereço segmentado 1000:1F00.

Alerta: um erro muito comum quando se efetua este cálculo é esquecer que se está trabalhando em hexadecimal, e não no sistema decimal. É impressionante o número de pessoas que somam 9 + 1 e obtém 10h ao invés de 0Ah, que é o correto.

A Intel, quando projetou os processadores 80286 e posteriores, não ampliou o endereçamento adicionando novos bits nos registradores de segmento. Ao invés disso, mudou a função que a CPU usa para transformar um endereço lógico num endereço físico. Se escrevermos um código que dependa da função "multiplique por 16 e some o deslocamento", o programa rodará apenas em processadores 80x86 operando no modo real e estaremos limitados a um megabyte de memória.

Tabelas

Fig.5 - Tabelas para o endereço físico (linear)

Nos processadores 80286 e posteriores, a Intel introduziu segmentos em modo protegido. Entre as mudanças efetuadas, a Intel reformou completamente o algoritmo para mapear segmentos para o espaço de endereços lineares. Ao invés de usar uma função (como a que multiplica o valor do segmento por 10h), os processadores em modo protegido consultam uma tabela para calcular o endereço físico. Eles usam o valor do segmento como um índice de array. O conteúdo do elemento do array selecionado fornece (entre outras coisas) o endereço inicial do segmento. A CPU soma este valor com o deslocamento para obter o endereço físico (veja a Fig.5).

Note que seus aplicativos não podem modificar diretamente a tabela de descritores de segmentos (a tabela de consulta). O sistema em modo protegido de operação (UNIX, Linux, Windows, etc) é responsável por esta operação.

Os melhores programas nunca dependem de um segmento localizado num determinado ponto em particular da memória. Deve-se deixar para o sistema operacional a tarefa de colocar os programas na memória e não se deve gerar qualquer endereço de segmento por conta própria.

Endereços normalizados no 80x86

Quando se opera em modo real surgem problemas interessantes. Pode-se referir um único objeto na memória usando vários endereços diferentes. Considere o endereço dos exemplos anteriores, 1000:1F00. Existem vários endereços de memória diferentes que correspondem ao mesmo endereço físico. Por exemplo, 11F0:0, 1100:F00 e até 1080:1700 correspondem ao memso endereço físico 11F00h. Quando se trabalha com certos tipos de dados, especialmente quando se compara ponteiros, é conveniente que endereços segmentados apontem para objetos diferentes na memória quando suas representações em bits forem diferentes. Claramente, este não é sempre o caso num processador 80x86 em modo real.

Felizmente existe um modo fácil de evitar este problema. Se precisarmos comparar dois endereços para verificar se são iguais ou diferentes, podemos usar endereços normalizados. Endereços normalizados assumem uma forma especial que os tornam únicos, isto é, a não ser que dois valores segmentados normalizados sejam exatamente iguais, eles não apontam para o mesmo objeto na memória.

Existem várias maneiras diferentes (na verdade, 16) de criar endereços normalizados. Por convenção, a maioria dos programadores (e das linguagens de alto nível) definem um endereço normalizado da seguinte forma:

Ponteiros normalizados que estejam nesta forma são muito fáceis de serem convertidos para endereços físicos. Tudo que precisamos fazer é anexar o dígito hexadecimal referente ao deslocamento ao valor do segmento. A forma normalizada de 1000:1F00 é 11F0:0. Podemos obter o endereço físico anexando o deslocamento (zero) ao fim de 11F0 para obter 11F00.

É muito fácil transformar um valor segmentado arbitrário num endereço normalizado. Primeiro transformamos o endereço segmentado num endereço físico usando a função "multiplica por 16 e soma o deslocamento". Depois inserimos os dois pontos entre os últimos dois dígitos do resultado de cinco dígitos:

1000:1F00 => 11F00 => 11F0:0

Note que esta discussão se refere somente aos processadores 80x86 operando em modo real. No modo protegido não existe uma correspondência direta entre endereços segmentados e endereços físicos, de modo que esta técnica não funciona. Entretanto, este texto trata principalmente de programas que rodam em modo real e, por isso, ponteiros normalizados são citados com frequência.

Registradores de segmento no 80x86

Quando a Intel projetou o 8086 em 1976 a memória era um bem precioso. Ela projetou seu conjunto de instruções de modo que cada instrução usasse o mínimo de bytes possíveis. Isto fazia com que os programas fossem menores e que os sistemas de computador que usassem processadores Intel usassem menos memória. Desta forma, a produção destes sistemas de computador também era mais barata. É claro que o preço da memória caiu tanto que deixou de ser a preocupação que era naquela época. Uma coisa que a Intel queria evitar era anexar um endereço de 32 bits (segmento:deslocamento) no final de instruções que referenciassem a memória. Conseguiram reduzir os endereços para 16 bits (apenas o deslocamento) determinando quais os segmentos da memória uma instrução poderia acessar.

Os processadores 8086 até 80286 possuem quatro registradores de segmento: cs, ds, ss e es. Os processadores 80386 e posteriores possuem estes mesmos registradores além de fs e gs. O registrador cs (sgmento de código) aponta para o segmento que contém o código que está sendo executado. A CPU sempre busca as instruções nos endereços indicados por cs:ip. Como padrão, a CPU espera acessar a maioria das variáveis no segmento de dados. Determinadas variáveis e outras operações ocorrem no segmento da pilha. Acessando dados nestas áreas específicas, o valor do segmento é desnecessário. Para acessar dados em um dos segmentos extras (es, fs ou gs), é necessário apenas um único byte para escolher o registrador de segmento apropriado e umas poucas instruções de transferência permitem especificar um endereço segmentado de 32 bits completo.

Isto pode parecer um tanto limitante. Afinal de contas, com apenas quatro registradores de segmento no 8086, pode-se endereçar no máximo 256 Kb (64K por segmento) e não os megabytes prometidos. Entretanto, pode-se alterar os registradores de segmento através de programas de modo que seja possível endereçar qualquer byte mudando apenas o valor de um registrador de segmento.

É claro que uma porção de instruções são necessárias para mudar o valor de um dos registradores de segmento do 80x86. Estas instruções consomem memória e tempo para serem executadas. Portanto, economizar dois bytes por acesso à memória não valem a pena se acessarmos dados o tempo todo em segmentos diferentes. Felizmente, a maioria de acessos consecutivos à memória ocorrem dentro do mesmo segmento, ou seja, carregar registradores de segmento não é algo feito com frequência.

Comentários

Finalmente o assunto é sobre processadores "de verdade". Começamos novamente com os mais antigos, apontando as suas características principais. Não é que eu adore velharias, acontece que este é o caminho das pedras. Os processadores mais antigos são mais simples, portanto, mais fáceis de entender. Tendo uma base, fica fácil "construir" processadores mais complexos no nosso entendimento :)

Hoje em dia nem se vê mais máquinas de 16 bits - já estamos entrando na era dos 64 bits. Hoje em dia excepcionalmente se trabalha em modo real. Mas, e se quisermos desenvolver um sistema operacional ou coisa que o valha - vamos trabalhar em modo real! Onde achar material de consulta? Aqui, hehehe... pelo menos é um bom começo.



| AAAA | Página Inicial | Mapa do Site | Novidades | Busca | Indique esta página | Mestre da Teia | Voltar |
| Localizador || @ Info NumaBoa > Assembly NumaBoa > Laboratório cap.3 > Memória > Memória II
Autoria: Randall Hyde - Art of Assembly Language Programming. Tradução: vovó Vicki

webdesign sobMedida by vickiSoft - /informatica/assembly/cap4_1.php (25.01.04) versão 1.0 de 26.01.04
Licença Creative Commons 1998-2006 Aldeia NumaBoa
Exceto onde especificamente declarado, todo material deste site é disponibilizado de acordo com a Licença Creative Commons.