A arquitetura “maluca” dos PCs

Uma das grandes dificuldades de desenvolver hardware e software de baixo nível (sistemas operacionais) para o PC-AT é a arquitetura dessa “Advanced Technology” (o AT, depois do PC). Hoje os PCs tinham que ser conhecidos como PC-PT (Patched Tecnology ou “tecnologia remendada”, não confundir com o partido, senão eu estaria usando um Mac aqui!) .Os PCs são monstrengos que carregam características legadas desde seu surgimento, em 1981.

Eis alguns exemplos…

Acesso à memória:

Até o início dos anos 80 os microcomputadores que reinavam eram os de 8 bits, baseados em processadores da Zilog (Z-80) ou MOS Technology (6502). Em 1981 a IBM lançou o PC original, totalmente “open source”, digamos assim, como o primeiro computador pessoal (Personal Computer, ou PC) com arquitetura de 16 bits, baseado no processador da Intel 8088… Isso foi novidade porque:

  • Era um computador da IBM, ora bolas;
  • Tinha duas vezes mais bits!!!
  • Processadores Intel não eram considerados tão bons assim;
  • Finalmente existia um padrão a ser seguido…

A terceira afirmação vem do fato de que, diz a lenda, a Zilog foi uma empresa formada de engenheiros dissidentes da Intel — os mesmos do projeto do 8080. Eles melhoraram um bocado os barramentos (nada daquela história de multiplexar dados com endereços!), algumas instruções de manipulação de 16 bits foram adicionadas e o conjunto de instruções ficou mais interessante: Pelo menos no contexto dos mnemônicos, nada da diferença entre MVI e MOV, por exemplo.

Também diz a lenda que a escolha pelo processador da Intel para os PCs deveu-se ao aconselhamento da Microsoft (maldição!), que tinha sido contratada para desenvolver o PC-DOS (mais tarde renomeado de MS-DOS)… A ideia original da IBM era adotar o CP/M como padrão, mas a Digital Research, dona do sistema, não conseguiu entregar no prazo o CP/M-86… Não é à toa que o MS-DOS lembre muito o CP/M… Suspeito, mas não tenho como confirmar, que a IBM, antes de adotar os processadores Intel, tinha a intenção de criar o PC com base nos MC68000 — o que seria uma jogada de mestre, já que esse processador já era usado em alguns mainframes!

Outra grande vantagem é que, mesmo sendo um microcomputador com arquitetura de 16 bits, o barramento de endereços permitia o uso de 20 bits, ou 1 MiB de memória acessível. Na época, os computadores de 8 bits tinham, no máximo, 64 KiB de memória (alguns usavam o esquema de chaveamento de bancos de memória para usar mais, mas era raro!)… Eu mesmo já tive um Apple ][+ com 48 KiB de RAM!

Acontece que para conseguir um endereço de 20 bits usando registradores de 16 bits foi necessário adotar um esquema estranho, típico das gambiarras da Intel… A memória era dividida em blocos de 64 KiB, mas, cada bloco começa num endereço físico múltiplo de 16. Ou seja, existem “segmentos” de memória com 64 KiB de tamanho… O endereço físico é, então, calculado com base em dois registradores: Um “seletor” de segmento e um offset:

\displaystyle Addr_{fisico}=(segmento\,shl\,4)+offset

Tanto “segmento” quando “offset” têm 16 bits de tamanho. Isso cria um problema: Se ambos forem 0xffff, o endereço físico resultante terá 21 bits de tamanho: (0xffff\,shl\,4)+0xffff=0x1ffef. Repare no ‘1’, mais à esquerda!

A intel resolveu incorporar esse “problema” na nova geração de processadores, o 80286 e o “bug” virou “feature”… Surge também o PC-AT que, para manter compatibilidade com o velho PC, incorporou um sinal chamado “Gate A20“. O “gate” aqui é uma porta lógica AND que mascara o bit A20 do barramento de endereços. Ou seja, se o sinal do “Gate A20” estiver desabilitado, o bit A20 será automaticamente zerado, senão a porta deixará passar o A20 fornecido pelo processador.

Numa época onde temos capacidade de memória acima de 8 GiB o Gate A20 perdeu sua serventia, não é? Infelizmente não! Ele ainda existe!

Outro problema com o esquema segmento:offset é que é possível codificar o mesmo endereço físico de memória de várias formas possíveis. Tomemos o exemplo do endereço físico de 20 bits 0x00123… Ele pode ser escrito como 0x0012:0x0003, 0x0011:0x0013, 0x0010:0x0023, … 0x0000:0x0123. Isso, para quem está aprendendo assembly é uma confusão dos diabos!

No modo protegido isso foi corrigido mudando-se a semântica do que seria um “segmento”. A parte “segmetno” virou um “seletor” de uma tabela que descreve o bloco de memória acessível pelo offset (mais uma gambiarra da Intel).

Diversos “modos” de operação diferentes:

Seu processador pode funcionar em 4 modos diferentes:

  • Modo real: Igualzinho ao velho PC com o 8088, mas usando os registradores e instruções estendidas (EAX, EBX, …). Ainda é possível acessar apenas 1 MiB de memória física (mais 64 KiB se o gate A20 estiver habilitado);
  • Modo protegido (32 bits): O esquema segmento:offset é completamente diferente. Pode-se acessar até 4 GiB de memória física (ou 64 GiB de memória virtual). Existem privilégios e isolamento de código e dados entre processos. Suporta multithreading com auxílio do processador;
  • Modo protegido (32 bits com extensões de 64 bits): Mesma coisa que o modo de 32 bits acima, mas permite acessar mais memória (até 256 TiB de memória física, ou 4 PiB de memória virtual). Estende os registradores para 64 bits (RAX, RBX, …). Adiciona mais registradores… No entanto, continua sendo um modo de 32 bits, chamado pela Intel de IA-32e;
  • Modo Virtual 8086: Emula o modo real dentro do modo protegido;
  • Modo SMM (System Management Mode): Usado para diagnósticos e é um modo completamente diferente dos acima.

Nos modos protegidos os registradores seletores de segmento indicam uma entrada numa tabela que contém a descrição do bloco de memória que pode ser endereçado… É como se você dissesse: “use o segmento nº 1” e, na primeira entrada da tabela, temos: “este segmento começa em 0x00000000, tem 4 GiB de tamanho, só pode ser lido e pode conter instruções executáveis, e só pode ser acessado por processos com privilégio 0″… Se um processo com privilégio 3 (o menor possível) tentar acessar um dado deste segmento, acontece um erro. Se qualquer processo tentar escrever nesse segmento, acontece um erro. Se usarmos um offset que não esteja dentro da faixa de endereços permitidos para esse segmento, acontece um erro…

É uma ideia interessante… mas, porque diabos continuar suportando todos os modos de operação legados? Em modo protegido as instruções continuam funcionando como antes, somente com restrições sobre o seletor, como mostrei acima, e estendendo o tamanho dos segmentos… Dessa forma, o modo real é completamente supérfluo, mas os PCs continuam a usá-lo e os processadores continuam entrando nesse modo, por default, durante o power up… Para que isso funcione a Intel teve que fazer mais uma gambiarra. Dê uma olhada abaixo:

; No modo real:
B8 00 00          MOV AX,0
66 B8 00 00 00 00 MOV EAX,0

; No modo protegido:
66 B8 00 00       MOV AX,0
B8 00 00 00 00    MOV EAX,0

Reparou que o prefixo 0x66 tem significados diferentes, de acordo com o modo de operação? No modo real ele diz ao processador que o registrador é EAX, ao invés de AX, para o micro-código 0xB8 (MOV AX,imm). Já no modo protegido é o contrário… Isso torna os códigos em assembly incompatíveis para os dois modos principais do mesmo processador! E isso, meus amigos, é o que eu chamo de gambiarra!

Para mim, os processadores modernos deveriam entrar no modo protegido, ajustando apenas 2 segmentos no ring 0: código e dados. Toda a BIOS poderia ser em modo protegido e não precisaríamos fazer a transição para esse modo… Se precisássemos executar código legado em 16 bits, existe o modo Virtual 8086!!! Assim, apenas 2 modos seriam necessários: O modo protegido de 32 bits, com as extensões de 64 bits, e o Virtual 8086.

Dispositivos:

A necessidade de manter hardware compatível com padrões é comendável, mas o PC leva isso ao extremo… Os antigos controladores de temporização (PIT), interrupção (PIC) e DMA (DMAC) continuam acessíveis, mesmo que de forma emulada… De fato, pode-se dizer que o barramento ISA (que surgiu no PC padrão) continua existindo até os dias de hoje… Isso só faz sentido por causa do modo real..

Mesmo assim, o PIT legado continua tendo uma base de tempo muito inferior (e com um valor maluco) do que o clock do sistema (1.19187 MHz, para ser exato!). O PIC continua suportando apenas 14 requisições de interrupção e o DMAC continua podendo realizar transferências de blocos com, no máximo, 64 KiB de tamanho… Quanto ao DMAC, embora o circuito integrado aceite transferências de memória para memória, isso não é implementado no PC!

O DMAC é um exemplo da busca eterna por compatibilidade reversa levada ao extremo. Mesmo que hoje tenhamos PCHs (Platform Controller Hubs) que fazem o trabalho de refresh das DRAMs, de forma transparente, o canal 0 (zero) do DMAC ainda é reservado para esse motivo…

Claro que existem versões mais modernas do DMAC, implementadas nos PCHs, mas elas são acessíveis apenas no modo protegido porque o acesso a esses recursos é feito em I/O mapeada no topo da memória, dentro dos primeiros 4 GiB. Lembre-se que o modo real só acessa 1 MiB + 64 KiB.

Cada um faz como quer

Ok… mesmo com essas tranqueiras, existem padrões, certo?

Acontece que fabricantes diferentes costumam fazer as coisas de formas diferentes… Um exemplo é o modo de habilitar o sinal Gate A20: Alguns exigem o uso do bit A20M da porta de controle A (PS/2 em diante), outros exigem que o controlador de teclado (heim!? o que o teclado tem a ver com memória?! gambiarra!!!) habilite esse sinal. Outros, nem isso… alguns escondem como A20M possa ser habilitado e exigem que seja feito pela BIOS! Outras estranhezas: Alguns fabricantes criam um buraco de 15 MiB acima do primeiro MiB (IBM Thinkpad e alguns Compaq, por exemplo). A maioria não faz isso…

Existem diversos pequenos quirks de fabricante para fabricante e para manter o sistema operacional funcionando em todos é necessário uma série de verificações… O código, obviamente, tende a ficar maior que o necessário!

Anúncios