segunda-feira, outubro 30, 2006

Livro do Mês: The End

Meros 12 dias após sair da Amazon, chegou em minhas mãos The End. As resenhas na Amazon tem oscilado fortemente entre os que adoraram e os que detestaram o livro.

As críticas principais tem a ver com a falta de resposta a vários mistérios e ao final inconclusivo da história. Na minha opinião, quem leu os livros anteriores não devia esperar coisa diferente. O livro "Lemony Snicket: The Unauthorized Biography" já era bem emblemático: cada capítulo tinha como título uma pergunta frequente, riscada pelo autor e substituída por uma pergunta mais estranha e misteriosa.

O livro aborda diretamente estes questionamentos. Na parte das respostas, diz que muitas respostas são irrelevantes e que toda resposta traz mais perguntas. E algumas respostas importantes estão lá, mas nunca de forma direta. Com relação a um final inconclusivo, Lemony Snicket é categórico: é impossível contar "Toda a História". Existem infinitas histórias entrelaçadas e é tolice falar em início em fim. Daí, parar a história num ponto apropriado qualquer é o melhor que se pode esperar.

A trama em si do livro é muito boa. Novamente os Bauldelaires estão em algum lugar estranho, cercados de personagens únicos. É possível enxergar vários simbolismos para os acontecimentos, com conclusões conflitantes. E tem o clímax com a morte de dois personagens (que traz mais um imenso mistério não respondido).

E assim chegamos ao fim. Ou não.

terça-feira, outubro 24, 2006

Gerenciamento de Memória - Windows 16 bits

O início da história das interfaces gráficas com o usuário (GUIs) é provavelmente o computador Alto, demonstrado pela Xerox em 1973 e nunca lançado comercialmente. O Alto, e o seu sucessor o Star, foram desenvolvidos por um centro de pesquisa da Xerox chamado PARC.

Em 1980, algumas semanas antes da Microsoft fechar as negociações do DOS com a IBM, um dos desenvolvedores da Xerox bateu na porta da Microsoft. Era Charles Simonyi, um húngaro que tinha desenvolvido o processador de textos do Alto, provavelmente o primeiro editor "WYSIYWYG". Alguns meses depois ele estava contratado como diretor de desenvolvimento de produtos avançados, se dedicando inicialmente ao desenvolvimento do tipo de aplicações que atualmente chamamos de Office (planilha eletrônica, editor de texto, etc).

As idéias da PARC corriam soltas na nascente indústria de microcomputadores. Na Apple, geraram o Lisa e depois o Mac. Na Comdex de 1982 Bill Gates levou um susto com um produto chamado VisiOn. Desenvolvido pela empresa responsável pelo VisiCalc (a primeira planilha eletrônica), o VisiOn era um pacote de aplições completo com interface gráfica, rodando no PC. A única boa notícia é que o produto ainda não estava pronto. Ao longo de 1983 diversos outros sistemas gráficos e multitarefa foram anunciados.

A resposta da Microsoft chamava-se inicialmente Interface Manager. Na Comdex de 1983 já estava rebatizado para Microsoft Windows e era objeto de uma campanha de marketing sem precedentes. Após várias mudanças de curso e uma negociação em cima da hora com a Apple, o Windows foi finalmente lançado na Comdex de 1985. Embora as primeiras notícias tenham sido favoráveis, testes mais completos revelaram um sistema lento, com suporte limitado a periféricos e com uma ausência quase total de aplicativos.

A primeira versão do Windows sofria com um processador sem a capacidade necessária, com placas de vídeo extremamente limitada e, o que aqui nos interessa mais, da falta de recursos de hardware para gerenciamento de memória.

Windows 1

Do ponto de vista visual, o Windows 1 tinha uma grande diferença em relação a todas as versões seguintes: não apresentava janelas sobrepostas.

Do ponto de vista de memória, o Windows 1 suportava apenas os até 640K de memória convencional do 8088 e 286. Entretanto, já apresentava uma série de recursos avançados:
  • Ao executar múltiplas instâncias de um mesmo programa, era usada um única cópia em memória do código e dos resources.
  • Grande parte das áreas de memória alocada através do windows podiam ser movidas, para agrupar as áreas livres criando blocos contíguos maiores ou para permitir expandir o tamanho de uma área alocada anteriormente.
  • Segmentos de código e resources eram normalmente carregados sob demanda, só sendo trazidos para memória quando necessários.
  • Os segmentos de código e resources eram normalmente descartáveis, podendo ser retirados temporariamente da memória (e posteriormente recarregados a partir do .EXE) para liberar memória.
Desta forma, o Windows 1 já era capaz de rodar "simultaneamente" vários programas que ocupavam junto mais memória que a disponível fisicamente. Entretanto, isto requeria acessos frequentes ao disco, com consequente queda de performance.

Windows 2

A principal alteração no Windows 2 foi o suporte a janelas sobrepostas e melhoramentos na parte visual (o que causou uma disputa judicial com a Apple).

Do ponto de vista de memória, a novidade foi a adição de suporte à memória expandida (LIM EMS).

A Microsoft criou também uma versão específica para o 386 (Windows386), que implementava memória expandida na memória extendida e usada o modo 8086 virtual para suportar múltiplas janelas DOS. O Windows e as aplicações continuavam rodando no modo real tanto no 286 como no 386.

Windows 3

A terceira versão finalmente suportou o modo protegido. A mágica foi feita pela iniciativa isolada de um programador, num momento em que a Microsoft estava concentrada no OS/2.

Do ponto de vista visual, o Windows 3 apresentava alguns efeitos simples de "3D".

O Program Manager e o File Manager substituiam o "MS-DOS Executive", fornecendo uma interface mais gráfica para disparar programas e manipular arquivos.

O Windows 3 era capaz de operar em três modos:
  • real mode: da mesma forma que o Windows 2, suporta o 8088 e 286/386 com pouca memória. Largamente ignorado por desenvolvedores e usuários e abandonado na versão 3.1.
  • standard mode: para máquinas com um 286 e 1M de Ram ou mais (ou máquinas com 386 e menos de 2M de Ram), usa o modo protegido do 286.
  • 386 enhanced mode: além de usar o modo protegido do 286, utiliza dois recursos do 386: paginação (para implementar memória virtual) e o modo 8086 virtual (para suportar múltiplas janelas DOS).
Organização da Memória no Windows 16 bits

A memória gerenciada pelo Windows é chamada de "memória global" ou "global heap" (global no sentido de total) e vai do ponto onde o Windows foi carregado pelo DOS até o fim da memória disponível.

O Windows 16 bits utiliza segmentos de até 64K. Cada bloco alocado da memória global é um segmento. Um programa possui um ou mais segmentos de código e um ou mais segmentos de dados. O Windows cria dois segmentos adicionais para cada um programa, um compartilhado por todas as instâncias e outro único para cada instância. Os resources são também carregados em segmentos.

Normalmente os segmentos são considerados movíveis. No modo real, quando um segmento se move, os ponteiros far (aqueles que incluem segmento e offset) precisam ser ajustados. No modo protegido, o Windows precisa apenas alterar o descritor do segmento e os ponteiros far continuam válidos. Um segmento pode ser marcado como fixo, mas isto irá atrapalhar o gerenciamento de memória, criando "buracos" na memória livre.

Para permitir a movimentação de segmentos no modo real, o Windows utiliza com frequência handles ao invés de endereços. Os handles são índices ou offsets para tabelas que contém o endereço real. Quando um segmento é movido, apenas as tabelas precisam ser atualizadas. Ao alocar memória (usando a rotina GlobalAlloc) um programa recebe um handle. Para acessar a memória, o segmento é temporariamente fixado e convertido em um endereço (usando GlobalLock), o acesso é feito e a memória é desbloqueada (usando GlobalUnlock). Para causar o mínimo impacto ao sistema, a memória deve ficar fixa somente enquanto uma mensagem de janela é tratada.

Uma outra consequência das limitações do modo real, é que um programa só pode conter inicialmente um segmento de dados movível, os demais serão fixos. Isto ocorre porque o Windows não tem como interferir com o código gerado pelo compilador para acessar dados em outros segmentos. Na prática, os programas tinham apenas um segmento de dados e criavam os outros dinamicamente (usando GlobalAlloc).

O resumo de tudo isto é que era muito complicado fazer programas no Windows 1 e 2 (ou no modo real do Windows 3) que precisassem de bastante memória.

No lado do código as coisas eram melhores, com o Windows suportando múltiplos segmentos de forma transparente (porém ao custo de desempenho). Aliás, era uma estratégia frequente quebrar programas em um número absurdo de pequenos segmentos (por exemplo, o programa Write que era incluído no Windows tinha 220K de código distribuído em 83 segmentos de código, todos movíveis e descartáveis e nenhum deles com mais de 10K). Desta forma, o Windows conseguia rodar (ou melhor, engatinhar) mesmo em um 8088 com memória bastante restrita.

A passagem para o modo protegido deixou para trás a maior parte destas preocupações. As poucas que sobraram foram embora com a passagem para os 32 bits, com o Windows NT e o Window 95.

domingo, outubro 15, 2006

Gerenciamento de Memória - Revisitando o MS-DOS

Como vimos na descrição do 8086, as primeiras versões do MS-DOS eram bastante pobres em termos de gerenciamento de memória. O DOS em si não evoluiu muito em relação a isto. Até o seu final (?), o DOS se restringiu a gerenciar de forma muito simples os primeiros 640K de Ram (o que veio a ser chamado de memória convencional).

Entretanto, muito cedo se percebeu que era errado dizer que "640K é toda a memória que alguem vai precisar" (como teria afirmado Bill Gates). O resultado foi uma série de complementos para aumentar a memória disponível para programas rodando sob o DOS.

EMS - Expanded Memory Specification

Definido em conjunto por Microsoft, Intel e Lotus*, a especificação EMS define uma forma de programas solicitarem a um driver remapear um bloco de memória no primeiro 1M de endereçamento. Tipicamente este bloco era mapeado em alguma região não usada na parte alta da memória, entre a memória da placa de vídeo e o BIOS.

Inicialmente isto era feito através de hardware especial, usando o chaveamento de bancos (que já vimos quando falamos do 8080). A partir do 386 é possível fazer um driver EMS usando os recursos de gerenciamento do processador.

* para os mais novos, a Lotus era uma das grandes empresas de software do início do PC, graças à planilha eletrônica 123. Infelizmente, seus outros produtos não tiveram muito sucesso e ela não conseguiu fazer uma transição bem sucedida para o Windows. Acabou comprada pela IBM.

Extended Memory e VDISK

A memória acima do primeiro 1M era denominada Extended Memory no tempo do DOS. A forma correta de acessar esta memória é colocando o processador no modo protegido.

Ao lançar o PC-AT a IBM forneceu os seguintes suportes à memória acima de 1M:
  • Foi incluída no BIOS uma função para informar o tamanho total da memória.
  • Foi incluída no BIOS uma função para copiar dados entre a memória convencional e a extendida. Isto era feito colocando o processador 286 no modo protegido, fazendo a cópia e gerando um reset para voltar ao modo real.
  • Junto com o PC-DOS (versão do MS-DOS customizada pela IBM) vinha um driver chamado VDISK que simulava um disco na memória acima de 1M, usando a função de cópia do BIOS.
Além dos inconvenientes do reset do processador, isto não criava uma interface padrão para vários programas compartilharem a memória acima de 1M. A solução adotada pela maioria dos programas foi alocar memória a partir dos endereços mais altos e interceptar a função do BIOS que informa o tamanho da memória para esconder a parte alocada.

HMA - High Memory Area

Como vimos quando falamos no 286, os primeiros 64K acima de 1M podem ser acessados no modo real, graças a uma pequena falha do 286 em emular o 8086.

UMB - Upper Memory Blocks

Com o 386 passou a ser possível mapear RAM para todos os espaços livres acima de 640K. Embora esta memória pudesse ser gerenciada por um driver EMS, uma outra forma era usar as funções normais de alocação do DOS, incluindo estes blocos na memória alta (UMBs) na lista de blocos gerenciados pelo DOS.

XMS - eXtended Memory Specification

A XMS fornece uma interface para coordenar o uso da memória não convencional (UMBs, HMA e Extended).

Gerenciadores de Memória

No mundo DOS, um gerenciador de memória é um device driver que usa os recursos do 386 para implementar os recursos que acabamos de ver. Inicialmente eram vendidos à parte do sistema operacional; a Microsoft o incluiu no MS-DOS a partir da versão 5.

O principal objetivo dos gerenciadores de memória era aumentar a memória livre abaixo de 1M para os programas. Alguns gerenciadores eram bastante agressivos nisto, por exemplo mapeando dinamicamente parte do BIOS para dentro ou para fora.

Á medida em que foram surgindo programas que usavam o modo protegido sob o DOS, os gerenciadores passaram a ser importantes como forma de coordenação do uso da memória, implementando a XMS.

A Microsoft utilizava dois drivers para o gerenciamento de memória no DOS e no Windows 16 bits:

  • HIMEM.SYS é responsável pelo gerenciamento em si, controlando o acesso à HMA, o controle da linha A20 de endereçamento e o uso da memória extendida. Para isto ele implementa a XMS, exceto pela parte referente aos UMBs.
  • EMM386.EXE é quem controla os recursos de mapeamento da memória do 386, podendo implementar a EMS e criar UMBs.

DOS Extenders

As soluções vistas até agora possibilitam apenas acessar em pequenas porções a memória acima de 1M. Embora sejam razoáveis para armazenar dados, são totalmente desajeitadas para código.

Uma solução melhor, e mais radical, é rodar sob o DOS uma aplicação no modo protegido. A grande complicação é que o DOS em si não pode ser rodado no modo protegido. Além disso, o DOS não consegue acessar diretamente a memória acima de 1M.

Vários sistemas foram criados para permitir isto (um exemplo muito conhecido é o Windows 16 bits). Diversas empresas se especializaram em vender bibliotecas e até compiladores para isto, os chamados DOS Extenders.

Alguns DOS Extender suportavam até o 286. Neste caso, quando as aplicações protegidas chamavam as funções do DOS, o processador precisava ser ressetado para retornar ao modo real.

Para o 386 existiam DOS Extenders com suporte a 16 e/ou 32 bits. As chamadas ao DOS podiam ser implementadas retornando o processador ao modo real ou usando o modo 8086 virtual.

Concluindo

De uma forma geral, o gerenciamento de memória sob o DOS era um conjunto de "adpatações técnicas" (tb conhecidas como "gambiarras") e o resultado era bastante instável.

No final da vida do DOS, as coisas estavam bastante complicadas. A VGA ocupava uma parte considerável da memória acima dos 640K e eram precisos drivers para as placas de som e rede e para o CD. Discos de boot (disquete com o DOS configurado de uma forma toda especial) era comum para quem gostava de jogar os jogos mais modernos (como Doom ou Duke Nuken 3D).

A vida para quem programava para Windows 16 bits não era muito melhor, como veremos na próxima parte.

quinta-feira, outubro 12, 2006

Contador de Visitas

Acrescentei ao template do blog um contador de visitas. Optei pelo StatCounter após uma olhada rápida nos contadores sugeridos pelo help do Blogger.

O motivador inicial era a curiosidade em saber se alguém visitava o blog. Confesso, entretanto, que fiquei surpreso com a quantidade de informações que podem ser obtidas do browser (veja o demo na página do StatCounter). Não é atoa que recentemente foi gerada uma versão do Firefox com o objetivo de permitir a navegação anônima.

Gerenciamento de Memória - 386


Introdução

Como vimos nas partes anteriores, o 8086 não tinha recursos para gerenciamento de memória. O 286 possuia, mas os recursos não eram usados devido às incompatibilidades com o 286. Em 1985, quando parecia que a Intel estava perdendo a batalha dos microprocessadores, foi lançado o 386 que, de uma só vez:
  • evoluiu a arquitetura x86 para 32 bits
  • aumentou a capacidade de endereçamento
  • acrescentou o recurso de paginação da memória
  • introduziu o modo 8086 virtual
Do ponto de vista prático, estas características viabilizaram sistemas multitarefa com proteção entre tarefas e memória virtual sem sacrificar a compatibilidade com o 8086 (ou com o 286).

Podemos dizer que a arquitetura de software do 386 definiu o padrão para as duas décadas seguintes.

Neste post foi falar basicamente do processador, em posts seguintes vou falar sobre o impacto do 386 no DOS e sobre o gerenciamento de memória no Windows 16 bits.

Passando dos 16 para os 32 bits

Assim como os registradores do 8086 eram uma extensão dos registradores do 8080, o 386 são uma extensão dos registradores do 8086:

O 386 pode trabalhar com operandos de 8, 16 e 32 bits. Novos modos de endereçamento foram introduzidos, permitindo em vários casos usar qualquer registrador onde antes somente alguns podiam ser usados. O processador passou a ser capaz de multiplicar automaticamente um índice por um fator de escala de 1, 2, 4 ou 8.

Por estes motivos, o 386 é capaz de operar em um modo 16 bits (com as instruções codificadas como no 8086 e 286) e num novo modo 32 bits. O modo "default" é definido pelo descritor do segmento de código (como veremos adiante), porém pode ser alterado instrução a instrução, acrescentando prefixos a ela. Isto significa que mesmo sob o DOS, rodando em 16 bits, um programa pode fazer operações de 32 bits.

Existe um prefixo para alterar o tamanho do operando. No modo 32 bits, os operandos tem 8 ou 32 bits; no modo 16 bits os operandos tem 8 ou 16 bits. Um outro prefixo controla o tamanho do endereço entre 32 e 16 bits.

O 386 continua usando registradores de segmento. Entretando, com deslocamentos de 32 bits passa a ser muito incomum precisar dividir código ou dados de um programa em vários segmentos.

Modo Protegido no 386

Como o 286, o modo 386 pode operar em modo real e em modo protegido. No modo protegido os registradores de segmentos continuam sendo índices para uma tabela de descritores. Tanto no 286 como no 386 os descritores ocupam 8 bytes, porém no 386 o endereço base e o deslocamento foram aumentados para 32 bits, aproveitando bytes não usados no 286.

O fato do formato dos descritores serem diferentes entre o 386 e 286 permite ao processador descobrir que tipo de código está no interior do segmento e utilizar o modo default de operação (32 ou 16 bits) adequado.

O 386 permite retornar do modo protegido para o modo real, mas isto é praticamente desnecessário graças ao modo 8086 virtual.

Modo 8086 Virtual

No 286, a emulação do 8086 estava restrita ao modo real, que afeta o comportamento de todas as tarefas e desliga as proteções e mapeamentos do gerenciamento de memória. O modo 8086 virtual permite ao 386 executar tarefas que emulam o 8086 dentro do ambiente protegido.

No modo 8086 virtual, os endereços são calculados como no 8086. O modo default de operação é 16 bits. O sistema operacional pode controlar que instruções críticas e quais portas de entrada e saída o programa pode utilizar. Quando o programa tenta executar um instrução proibida ou acessar uma porta bloqueada, o controle passa ao sistema operacional, que pode emular a instrução ou acesso se desejar.

Paginação da Memória

No 386 os endereços lineares obtidos tanto no modo protegido (a partir do descritor do segmento) como no modo 8086 virtual (somando o segmento multiplicado por 16 ao deslocamento) passam por mais uma etapa antes de gerarem um acesso à memória física.

Na paginação, o endereço linear é quebrado em páginas de 4K. Estas páginas são convertidas em endereços físicos através de duas tabelas:
  • Diretório de páginas: divide os 4G de endereçamento linear em 1024 grupos de página com 4M cada. A cada grupo está associado uma entrada de 32 bits que indica se o grupo está na memória e, se sim, qual o endereço da tabela de páginas correspondente.
  • Tabela de páginas: possui uma entrada de 32 bits para cada página. Esta entrada indica se a página está na memória. Se sim, indica qual o endereço físico correspondente, se ela foi acessada e se ela foi alterada. Estas duas últimas informações podem ser usadas pelo sistema operacional para otimizar o swap entre a memória e o disco. Quando a memória física ficar cheia e o sistema precisar mover uma página para o disco, pode ser implementada uma política LRU (retirar a página que foi acessada a mais tempo). Se uma página foi carregada do disco para a memória e não foi alterada, ela no precisa ser salva em disco antes de usar a memória física para outra página.
Questões Comercias e Estratégicas

Do ponto de vista técnico, o 386 foi uma ruptura comparável à passagem do 8080 ao 8086. O seu impacto, porém, não se limitou à parte técnica:
  • Para a Microsoft, o 386 viabilizou o Windows. Primeiro por fornecer a capacidade de processamento e endereçamento necessária e segundo por viabilizar a transição gradual, através das janelas DOS.
  • Para a Intel, o 386 foi o momento de se desgrudar dos demais fabricantes de chips. Até esta época era praxe um fabricante licenciar os seus projetos para os outros, criando o second source. Isto permitia ao fabricante original aumentar a capacidade de produção sem maiores investimentos e dava uma garantia adicional de fornecimento aos compradores. Por outro lado, isto reduzia o lucro do fabricante original. A Intel não licenciou o 386 para outros fabricantes. Foi somente após longos trabalhos de engenharia reversa e longas disputas judiciais que a Intel voltou a ter concorrentes na linha x86.
  • Para a IBM, o 386 aumentou o medo. Alí estava um chip que rivalizava os recursos de seus computadores (não pessoais) de pequeno e médio porte, a um preço muito inferior.
  • Para os demais fabricantes, foi o momento em que a IBM piscou. O primeiro computador com o 386 foi lançado não pela IBM mas por uma empresa mais conhecida pelos seus computadores portáteis: a Compaq. Apesar da tentativa com o PS/2, a IBM não voltou a controlar a plataforma.
Por tudo isto, existiu uma pressão muito grande a favor do 386 e contra o 286. Inicialmente o custo do 386 limitou o seu uso aos sistemas mais sofisticados. Posteriormente a Intel lançou o 386SX que utilizava memória de 16 bits (estatégia semelhante à do 8088).

quarta-feira, outubro 04, 2006

The End Is Near!

Em setembro do ano passado eu resolvi me juntar aos "infelizes" leitores de A Series of Unfortunate Events (ASoUE como costumam chamar os americanos). Para quem não conhece, é uma série de livros teoricamente infantis, recheados de humor negro e surrealismo. A série conta as desventuras de três orfãos e a trama é cheia de reviravoltas. Toda vez que você pensa que vai cair em um padrão, dá uma guinada. O visual dos livros é outra atração, simulando livros antigos.

Os livros estão disponíveis em português com uma bela apresentação gráfica, mas eu tenho pena do tradutor pois são repletos de jogos de palavras e anagramas. A começar pelos títulos que tem sempre (exceto o último) o formato "The Xxxxx Xxxxx" (The Bad Beginning, The Reptile Rom, The Wide Window, etc). Para quem for comprar na Amazon, minha dica é comprar as caixas com três livros pois conta como um volume para o cálculo do frete (os livros não relativamente curtos).

Tem também um filme, baseado nos três primeiros livros, com o quase sempre exagerado Jim Carrey. Os créditos iniciais e finais do filme são um espetáculo a parte. Vale a pena alugar.

Além dos livros da série em si, tem dois livros adicionais: "Lemony Snicket: The Unauthorized Biography" e "The Beatrice Letters" (recém lançado). Lemony Snicket é o autor do livro, que vira personagem no meio da trama. Todos os livros tem uma dedicatória macabra para "Beatrice" (por exemplo, "To Beatrice - No one could extinguish my love, or your house"). Quem exatamente são Lemony Snicket e Beatrice são alguns dos mistérios.

E mistérios não faltam. Os livros adicionais só levantam mais dúvidas, deixando desesperados os milhares de fãs.

Bem, agora a série vai terminar, no 13o livro que sairá sexta feira 13 de outubro. O livro se chama simplemente "The End". O meu já está encomendado.

Para quem quiser saber mais sobre a série, seguem alguns links:

site oficial: http://www.lemonysnicket.com (repleto de pistas sobre o novo livro)
na Wikipedia: http://en.wikipedia.org/wiki/A_Series_of_Unfortunate_Events
um site de fãs: http://www.thequietworld.com/
um message board de fãs: http://asoue.proboards11.com/index.cgi

terça-feira, outubro 03, 2006

Gerenciamento de Memória - 286, o Processador "Brain-Dead"


Como vimos nas partes anteriores, os processadores 8080 e 8086 não tinham nenhum recurso de gerenciamento de memória. Isto mudou com o processador 286 (também conhecido como 80286 e iAPX286), que acrescentou ao 8086
  • capacidade para endereçar até 16MBytes de memória física
  • endereçamento virtual de 1 GByte
  • facilidades para implementação de swap entre memória e disco
  • proteção de memória
  • níveis de privilégio
Infelizmente, embora estas características sejam bastante úteis para sistemas multi-tarefa e até tenham sido implementadas de forma elegante, elas tinham um preço muito caro no 80286: a incompatibilidade com as aplicações DOS. A principal concessão de compatibilidade no 286 consistia no real mode, modo inicial no qual ele se comportava como um 8086, sem nenhum recurso de gerenciamento de memória. Para ter acesso a estes recursos, o processador precisava ser colocado no incompatível modo protegido. Para agravar a situação, a Intel achou que o modo protegido era tão maravilhoso que ninguém iria querer sair dele; a única forma de retornar ao real mode era fazendo um reset (via hardware).

O modo protegido se baseia em mudar a interpretação dos registradores de segmento. No 8086, os registradores de segmento guardavam uma parte do endereço que era somada ao deslocamento especificado pela instrução sendo executada. No modo protegido, o registrador de segmento contém um índice para uma tabela que descreve o segmento.

Entrando um pouco mais em detalhes, no modo protegido o valor em um registrador de segmento é interpretado da seguinte forma:
  • os 13 bits mais significativos são o índice para uma tabela de descritores
  • o bit seguinte indica se é usada a tabela global (GDT) ou local (LDT). Existe ainda uma terceira tabela, para os segmentos usados no vetor de interrupções
  • os 2 bits menos significativos indicam o nível de privilégio
O endereço inicial de cada tabela de descritores é armazenado em um registrador especial (GDTR, LDTR e IDTR). Um sistema operacional pode, por exemplo, trabalhar com uma LDT para cada processo, isolando desta forma a memória acessível por cada um.

Um descritor de segmento no 286 contém:
  • o offset máximo acessível no segmento (16 bits)
  • o endereço físico inicial do segmento (24 bits)
  • um byte de controle de acesso. Este byte indica se o conteúdo do segmento pode ser executado, lido ou escrito (não é permitido ter um segmento que possa ser executado e escrito). Contém ainda um bit que indica se o segmento está presente na memória física e outro para indicar se o segmento foi acessado; estes bits podem ser usados pelo sistema operacional para controlar o swap entre disco e memória. Por último, este byte pode indicar alguns tipos especiais de segmentos/descritores.
Quem estiver curioso em saber mais detalhes, de uma olhada neste documento.

Embora o modo protegido do 286 seja um imenso avanço sobre o 8086, ele tem algumas restrições:
  • a carga de um valor nos registradores de segmento passa a ser lenta, já que envolve um acesso à tabela de descritores que está na Ram, fora do processador
  • os segmentos continuam limitados a 64KBytes
  • o swap entre disco e memória é controlado no nível de segmento
  • não suporta programas que fiquem fazendo contas com os registradores de segmentos, escrevam nos segmentos de código e executem instruções nos segmentos de dados - o que as aplicações DOS costumavam fazer.
O resultado foi que o 286 foi usado como um 8086 rápido pelos usuários do DOS (que eram a maioria). O modo protegido só viria a ser popular com o 386. Como veremos no próximo post desta série, existem vários motivos que levaram Bill Gates a ser referir ao 286 como "brain-dead".

O PC/AT, DOS, Xenix e OS/2

A IBM utilizou o 286 no terceiro micro da linha PC, o PC-AT (o PC original e o PC-XT utilizavam o 8088). Embora fosse muito mais rápido que os modelos anteriores (além de usar um clock mais rápido, o 286 executava as instruções em menos ciclos e o HD também era mais rápido) a IBM claramente segurou o desempenho com medo de atrapalhar as vendas de seus computadores maiores (por exemplo, o PC-AT original usava um clock de 6MHz apesar de os esquemas no manual de referência mencionaram 8MHz). Ao final da "era 286" a IBM tinha menos de metade do mercado (a liderança estava com "Outros") e tinha os modelos mais lentos (8MHz contra 12, 20 ou mesmo 25MHz).

O BIOS do PC/AT trabalhava quase que inteiramente no real mode. A principal exceção era uma função para copiar dados entre o primeiro 1M e o resto da memória. Esta função desabilitava as interrupções, colocava o processador no mode protegido, fazia a cópia e ressetava o processador (através do controlador de teclado). Este processo era suficientemente demorado para atrapalhar uma comunicação serial em andamento.

Junto com o PC/AT a IBM lançou a versão 3 do DOS. A principal alteração no DOS era interna, para permitir o compartilhamento de discos em rede. Entretanto, esta alteração não ficou pronta a tempo; o DOS 3.00 saiu com esta parte do código desabilitada. A versão 3.10 suportava rede porém tinha uma série de bugs que foram corrigidos na versão 3.20. O suporte do DOS ao 286 estava limitado a um driver de ramdisk capaz de usar a memória acima de 1MByte, como visto acima isto não era rápido e podia ter efeitos colaterais indesejados.

O modo protegido do 286 foi usado pelos sistemas *nix, como o Xenix. Entretanto, a ênfase dada era para sistemas multiusuários e a capacidade de um 286 era ainda baixa para isto.

Um legado do 286 foi a insistência da IBM em suportar exclusivamente o modo protegido do 286 na versão 1 do OS/2, ignorando o 386. Isto trouxe uma série de limitações ao OS/2, mesmo em relação ao Windows 3.0, dificultando ainda mais a sua aceitação.

HIMEM

Uma última curiosidade sobre o 286. No 8086 ao somar o segmento deslocado ao offset, o eventual vai-um era ignorado. No 286, que possuia mais bits de endereço físico, este vai-um ia para o bit 20 do endereço. Com medo que isto criasse uma incompatibilidade, a IBM colocou um hardware externo que normalmente forçava o bit 20 do endereço em zero. Entretanto, isto podia ser desligado por software, permitindo acessar 64K acima de 1M no real mode. Isto veio a ser usado no final da vida do 286 e do DOS, quando os 640KBytes de Ram estavam ficando muito apertados.

segunda-feira, outubro 02, 2006

Gerenciamento de Memória - 8086, 8088, o PC-IBM e o MS-DOS


Com o sucesso dos microprocessadores de 8 bits e o constante avanço da micro-eletrônica, os fabricantes partiram para projetos mais ousados.

A Motorola, por exemplo, abandonou a arquitetura de 8 bits do 6800 e desenvolveu o 68000, com 16 registradores de 32 bits de uso geral e modos usuário e supervisor para proteção do sistema operacional. A Zilog partiu para o sofisticado Z8000.

A Intel, entretanto, se mostrou bastante conservadora com o 8086. Os registradores de uso geral do 8086 (AX, BX, CS e DX) são uma expansão direta dos registradores do 8080. Inclusive, cada registrador ?X pode ser acessado como dois registrados de 8 bit, ?H e ?L.

Isto, mais alguns cuidados na definição do conjunto de instruções, permitiu a conversão automática de código fonte assembler do 8080 para o 8086. Vários programas de sucesso no CPM/80, como WordStar e Dbase II utilizaram este processo para sair na frente no lançamento do PC IBM (por outro lado, estes programas convertidos ficaram em desvantagem quando concorrentes feitos especificamente para o PC ficaram disponíveis).

Como vimos anteriormente, embora o 8080 seja um processador de 8 bits, ele possui algumas instruções para manipular 16 bits, tipicamente endereços. O 8086, entretanto, manipula quase que exclusivamente 16 bits. Isto criou um problema, pois, mesmo no final dos anos 70, era evidente que 16 bits de endereçamento (64Kbytes) era pouco. A gamb^H^H^H^B adaptação técnica (TM Rodrigo Strauss) adotada pela Intel custou muitas noites de sono para os desenvolvedores de aplicações e compiladores. As instruções normais trabalham com endereços de 16 bits, estes endereços são convertidos para 20 bits somando com o valor de registradores especiais também de 16 bits, porém deslocados de 4 bits para a esquerda. Parece confuso? É confuso mesmo.

Estes registradores especiais são chamados registradores de segmentos. O 8086 tem 4 destes registradores, chamados CS, DS, SS e ES. Por default, estes registradores são usados, respectivamente para acesso a código, dados e pilha (o registrador ES só é usado implicitamente por algumas instruções especiais). É possível (com certas restrições) usar explicitamente um registrador de segmento diferente colocando um byte de prefixo na frente da instrução.

Vamos tentar esclarecer com um exemplo. Vamos supor que os registradores BX, DS e ES contenham, respectivamente, 0x1234, 0x9000 e 0x4321 (0x indica número hexadecimal, se você não sabia disso provavelmente está no blog errado). A instrução assembler MOV AX,[BX] carrega em AX o valor na posição de memória de endereço DS*16 + BX (ou seja, 0x91234). A instrução MOV AX,ES:[BX] usa o registrador ES no lugar do DS, carregando em AX o valor na posição de memória de endereço 0x44444.

É costume no 8086 falar em endereços na forma segmento:offset, onde segmento e offset são valores de 16 bits. No nosso exemplo anterior, o endereço 0x44444 poderia ser referenciado como 0x4321:0x1234. Ou 0x4000:0x4444. Ou outras 64K-2 formas diferentes (o 8086 ignora o "vai um" quando soma o segmento deslocado com o offset, guarde esta informação para quando formos falar do 286).

O que tudo isto significa na prática? Em primeiro lugar, que o 8086 é capaz de endereçar até 1M de memória. Em segundo lugar, que uma aplicação precisa fazer uma certa ginástica para usar mais que 64K de código ou dados. No caso do código, existem o JUMP e CALL intersegmento, mas são instruções mais longas e mais demoradas. No caso de dados, para acessar uma estrutura que ocupe mais de 64K é preciso fazer umas contas medonhas. Os compiladores C para o 8086 costumam suportar alguns modelos de endereçamento:
  • Tiny: Tudo fica em um único segmento de 64K. Os quatro registradores de segmento apontam para o início do segmento no início da execução e não são mais alterados. Altamente eficiente e simples, até que o seu programa não caiba em 64K.
  • Small: Um segmento de código (apontado por CS) e um segmento de dados (apontado por DS e SS). Novamente os registradores não precisam ser alterados durante a execução.
  • Medium: Um segmento de código para cada rotina e um segmento único de dados. Cada chamada de rotina passa de 3 para 5 bytes; o seu código que não cabia por pouco em 64K agora ocupa 70K.
  • Large: Múltiplos segmentos de código e dados, mas nenhuma estrutura passa de 64K. Os registradores CS e DS mudam a toda instante, o tamanho do código cresce e o desempenho cai.
  • Huge: Múltiplos segmentos de código e dados, permite estruturas com mais de 64K. Avançar um ponteiro de dados passa a ser uma operação lenta.
Neste altura vocês devem estar achando que a Intel fez uma grande besteira. Entretanto, simplificando o processador ela simplificou o processo de fabricação, saindo na frente dos concorrentes e obtendo mais chips bons por waffer de silício e portanto chips mais baratos. Além disso, a compatibilidade parcial com o 8080 permitiu ter rapidamente uma boa oferta de software. Enquanto os outros processadores de 16 bits eram usados apenas em projetos mais sofisticados, o 8086 foi ganhando terreno. Quando a IBM foi ouvir os conselhos da Microsoft (que aliás tinha muito software para 8080 e CPM/80, a ponto de ter desenvolvido uma placa de expansão com Z80 para ter penetração no mercado Apple) a estratégia deu frutos.

Um ponto interessante no interior do 8086 é a divisão do processador em uma unidade de execução e uma unidade de acesso de memória. Esta segunda unidade é a responsável por acionar os pinos de endereçamento do chip e faz algumas coisas especiais:
  • no caso de um acesso a uma valor de 16 bits que está em um endereço impar, gera dois acessos à memória. Desta forma existe uma penalidade de tempo, mas o software é executado quase que normalmente.
  • aproveita quando a unidade de execução está processando a instrução para pegar as instruções seguintes e colocá-las numa pequena fila. Não chega a ser um cache, mas dá um pequeno ganho de performance e permite usar memórias mais lentas.
A existência da unidade de acesso à memória simplificou criar o 8088, que é identico ao 8086 porém usa uma memória de 8 bits. Do ponto de vista de software, nada muda. Do ponto de vista de hardware, o custo de um sistema simples cai, pois os chips de memória típicos eram de 16K ou 64K posições de 1 bit. Com o 8088, dava para fazer um sistema usando 8 chips de memória, com o 8086 era preciso no mínimo 16 chips.

Ao contrário do 8080, o 8086 inicia a execução no final da memória, no endereço 0xFFFF0. Novamente, isto simplifica o projeto, pois pode-se colocar uma memória não volátil no fim da memória e Ram no começo. No início da memória existe uma tabela importante o vetor de interrupções. Esta tabela contém os endereços das rotinas que tratam as interrupções de hardware e software.

O PC-IBM

Por sugestão da Microsoft, a IBM utilizou o 8088 no PC IBM. Na placa principal havia lugar para até 64K de Ram, na forma de 32 chips de 16Kx1. No PC XT eram usados chips de 64Kx1, permitindo 256K na placa mãe.

O 1M de endereçamento foi dividido em três grandes partes: os primeiros 640K (0x00000 a 0x9FFFF) para a Ram, os últimos 64K (0xF0000 a 0xFFFFF) para a memória não volátil com o BASIC e o BIOS e o resto (0xA0000 a 0xEFFFF) para uso pelas placas de expansão (notadamente a Ram das placas de vídeo em 0xB0000 e 0xB8000 e o BIOS da controladora de HD em 0xC0000).

Uma curiosidade é que o chip de DMA da Intel (responsável entre outras coisas por transferir para a memória os dados das controladoras de disco) também era limitado a 16 bits. Para endereçar toda a memória, a IBM precisou acrescentar um registrador externo de 4 bits. Isto criou uma nova barreira de 64K: não era possível ler diretamente do disco para um trecho de memória que contivesse uma mudança nos 4 bits mais significativos do endereço. O BIOS se limitava a recusar este tipo de transferências, deixando por conta do sistema operacional ou aplicação contornar esta limitação.

O BIOS possui as rotinas de iniciação do hardware e rotinas básicas de entrada e saída. A memória de 0x00000 a 0x003FF contem os vetores de interrupção, de 0x00400 a 0x004FF estão as variáveis do BIOS.

O MS-DOS

A Seattle Computer foi uma das primeiras empresas a fazer um micro usando o 8086. Entretanto o único software disponível era o BASIC da Microsoft. Cansados de esperar pelo CP/M-86, eles desenvolveram o QDOS (Quick and Dirty Operationg System). Quando a IBM também se cansou de esperar pelo CP/M-86, a Microsoft licenciou o QDOS e o ofereceu para a IBM. O resto, como dizem, é história. (Recomendo para quem quiser os detalhes o livro Gates de Stephen Manes e Paul Andrews).

Apesar das inúmeras diferenças em relação ao CP/M-80, o MS-DOS suporta uma interface de programação muito semelhante. Como vimos na parte anterior, no CP/M-80 a interface entre a aplicação e o sistema operacional é feita através de uma estrutura no início da memória. No MS-DOS esta estrutura é simulada no Prefixo de Segmento de Programa (PSP). Lá estão a linha de comando, as estruturas de acesso a arquivos (FCBs) e até mesmo a chamada ao SO. Nas versões futuras do MS-DOS algumas destas características foram perdendo importâncio, mas continuam lá e sobrevivem até no Windows.

Ao contrário do CP/M-80, o MS-DOS reside nos endereços baixos. A memória disponível para os programas começa após o final do SO (mais rigorosamente, após a parte residente do COMMAND.COM) e vai até o fim da Ram (o final da Ram é também usado pelo parte transiente do COMMAND.COM). Na versão 1 o DOS não oferecia nenhuma função de gerenciamento de memória, exceto o Terminate and Stay Resident, através da qual um programa podia encerrar deixando uma parte de si na memória (ou seja, controle de memória do DOS se limitava ao endereço ondem os programas eram carregados). A partir da versão 2, o DOS passou a oferecer funções simples de alocação de memória, colocando um pequeno prefixo no final de cada bloco alocado.

O MS-DOS suporta dois formatos de arquivos executáveis: COM e EXE.

Um arquivo COM é uma imagem binária de um programa. Esta imagem é carregada no offset 0x100 de um segmento, os registradores CS, DS, ES e SS apontam para o início do segmento e a execução é iniciada em CS:0x100. No começo do segmento é colocado o PSP. Este esquema, muito semelhante ao CP/M-80, é apropriado para programas no modelo tiny.

O arquivo EXE suporta programas com múltiplos segmentos. Para isto ele possui um cabeçalho para para acertar as referências inter-segmentos conforme o segmento em que o programa foi carregado. O PSP é criado na memória antes do primeiro segmento do programa. No início da execução, os registradores DS e ES apontam para o PSP, os registradores CS, IP, SS e SP são carregados com informações obtidas do cabeçalho do EXE.

Do ponto de vista de gerenciamento de memória, o 8086 e o MS-DOS não trazem grandes vantagens em relação ao 8080 e CP/M-80 (exceto por suportarem uma memória maior). Isto ficou para a geração seguinte. Infelizmente, o 286 seguiu na direção errada, como veremos no próximo post desta série.

Programando em C#

Como estou acostumado a programar principalmente em C, programar em C# traz algumas novidade e desafios para mim:
  • Excetos pelas variáveis numéricas, enumerações e estruturas (value types), tudo é alocado no heap gerenciado (reference types) e será liberado da memória pelo Garbage Collector. Ou seja, é um monte de X x = new X(). Para quem está acostumado a gerenciar os seus bytes um a um com muito carinho é um grande desafio de confiança.
  • No C# não existem variáveis globais e tudo tem que estar dentro de uma classe. Por exemplo, imagine que você queira criar uma rotina chamada Arredonda e uma constante chamada ValorMinimo, que você queira usar em vários lugares. Você precisa declará-los como membros estaticos de alguma classe. No C# 2005, é possível criar classes estáticas úteis justamente para este tipo de coisa.
  • Os controles geram eventos quando são alterados pelo operador ou pelo programa. Por exemplo, se você selecionar um item de um combox por programa, a rotina que trata o evento de mudança de seleção será chamada. Em alguns casos isto é muito útil mas em outros causa algumas complicações.
Como disse anteriormente um grande problema é que o framework é imenso e o help é muito sucinto. Estou toda hora procurando coisas no Google. Duas dicas das que acumulei até agora:

Pegar a versão do programa no assembly

No properties do projeto é definida a versão que será colocada no assembly. Para pegar este valor, por exemplo para o diálogo de Sobre, basta usar

Assembly.GetExecutingAssembly().GetName().Version.Major.ToString()
Assembly.GetExecutingAssembly().GetName().Version.Minor.ToString()

Usar um LinkLabel

Ainda no diálogo Sobre, imagine que você quer colocar um link para abrir a sua home page. O controle LinkLabel faz isto, se você souber usar. Coloque o LinkLabel no diálogo e altere a propriedade Text para o URL desejado (por exemplo, http://www.microsoft.com). No construtor do formulário, copie o texto para a lista de links:

linkHomepage.Links.Add(0, linkHomepage.Text.Length, linkHomepage.Text);

No evento LinkClicked, siga a receita abaixo (encontrada em http://www.peterritchie.com/Hamlet/Articles/63.aspx)

string strLink = e.Link.LinkData.ToString();
Process process = new Process();
process.StartInfo.FileName = "rundll32.exe";
process.StartInfo.Arguments = "url.dll,FileProtocolHandler " + strLink;
process.StartInfo.UseShellExecute = true;
process.Start();

A solução apresentada no help é mais simples

System.Diagnostics.Process.Start (e.Link.LinkData as string);

só que não funciona...