Como Funciona um CPU?


Por debaixo de todos os plásticos, peças metálicas e vidro à superfície de um computador, esquecendo todos os 1001 periféricos e componentes acessórios, esconde-se o CPU - o "coração" e cérebro de todo este sistema!




O CPU

CPU é, como o seu próprio nome indica - Central Processing Unit - um processador de instruções.


No seu interior, bits e bytes são processados sem parar, num contínuo ciclo indiferente à linguagem de programação que lhes deu origem: no que ao CPU diz respeito há apenas uma única linguagem... o código máquina. Estranhos códigos binários de zeros e uns que afastarão a maioria dos programadores, mas que são a verdadeira "língua" que o CPU entende. Toda e qualquer linguagem de programação tem que ser, de uma forma ou de outra, compilada ou interpretada, até que se torne em algo que o CPU entenda.

Para todos aqueles que mesmo assim gostam de trabalhar com o CPU a este nível, existe em versão mais humanizada... o Assembly/Assembler/ASM. Representação simbólica desse código máquina e que permite mais facilmente visualizar, manipular e programar!

Por exemplo, em vez de terem que lidar com isto:
11001010 00000001 00000000 00000000
O Assembly permite que isso se traduza em algo mais amigável, como isto:
ld %r4,%r5


Antes que comecem a deitar a mão à cabeça, vamos lá simplificar a coisa: o CPU tem no seu interior vários registos onde guarda várias informações. Uma delas, talvez a mais importante, é aquela que lhe diz qual a posição da próxima instrução a executar. É esse registo que serve de fio condutor a tudo o que será executado pelo CPU.


A Arquitectura

Mas - antes de prosseguirmos - há ainda um factor fundamental: a arquitectura do sistema em questão. É que um CPU por si só, não será capaz de fazer grande coisa... São necessários alguns componentes essenciais para que ele faça algo; componentes que estão ligados com o CPU através de barramentos (Bus), e acedidos através deendereços.

Há vários tipos de arquitectura, mas para os efeitos de hoje, vamos centrar-nos na que é mais utilizada pelos CPUs actuais: a arquitectura de Harvard modificada.

Afinal, não serviria de muito ter um CPU sem sítio onde pudessemos guardar o nosso programa a executar, nem forma de sinalizar para o exterior o seu resultado, pois não?


O Funcionamento

Imaginemos então que alguém ligava o nosso processador imaginário: a electricidade começa a fluir; o CPU "acorda", e começa a procurar o que fazer, e dirige-se à instrução número zero. Executa-a, passa para a instrução seguinte, e assim sucessivamente até que alguém o desligue... ou algo de muito grave aconteça (como por exemplo, aquecer a tal ponto que derreta! :)

Basicamente, é apenas isto. A sério!

Dependendo do tipo de CPU, este poderá ter um conjunto de instruções que vão de apenas algumas dúzias de instruções, às centenas!
E voltando à ideia original que me fez escrever este artigo, se alguma vez olharam para um qualquer pedaço de assembly, de qualquer processador, torna-se evidente que grande parte das instruções existentes na maioria dos programas, são movimentações de pedaços de informação de um lado para o outro e operações aritméticas.


Moves, adds, ands, ors, etc. etc. etc. Tudo instruções que recorrentemente encontrarão em toda e qualquer listagem de assembly de todo e qualquer CPU.

Era precisamente isto que me fazia uma enorme confusão - e que servia de base ao pensamento de que "um computador não passava de uma máquina calculadora"!

A "calculadora"

E posso dizer-vos que isto foi algo que me intrigou durante bastante tempo... Como é que mover informação de um lado para o outro, ou fazer umas contas de somar e subtrair, poderia fazer com que a tecla que eu carregasse aparecesse escrita no ecrã? Como é que isso poderia criar uma fotografia a aparecer no monitor?

Pois bem, o segredo está no tal barramento que permite que o CPU tenha acesso a outros dispositivos.

Voltando ao nosso sistema imaginário, poderíamos definir que o endereço de 0 a 1000 consistiria num espaço de memória onde se iriam guardar as instruções do nosso programa. Mas que o endereço 1001 seria especial: nesse local seria colocado o valor da tecla que fosse carregada no teclado. E no endereço 1002, seria um endereço que, dependendo do seu valor, controlaria um ruidoso buzzer, o valor 0 significaria desligado, o valor 1 faria barulho.

Já estão a ver o que isto permitiria? Poderíamos fazer um programa e guardá-lo nos endereços de 0 a 1000, que - por exemplo - pudesse fazer barulho sempre que se carregasse numa determinada tecla. Numa sequência de instruções que seria semelhante à seguente:

Endereço - Instrução
0 - Ler o valor do endereço 1001 (teclado)
1 - Ver se o valor lido é igual à tecla "B"
2 - Se sim, então colocar o valor 1 no endereço 1002 (ligando o buzzer)
3 - Se não, colocar o valor 0 no endereço 1002 (desligando o buzzer)
4 - Saltar de novo para o endereço 0


Estão a começar a perceber o esquema?...


Há muito mais que o CPU...

Sim, mais que saber programar um CPU, torna-se fundamental conhecer todo o sistema em que está inserido. É preciso saber em que endereços estão que equipamentos, etc. etc. É por isso mesmo que um programa em código máquina criado para um smartphone da marca "A", não funcionará noutro da marca "B" mesmo que tenha o mesmo processador. É que nesse outro smartphone, os dados do teclado, ecrã, e outros periféricos, poderão estar em endereços completamente diferentes!


Daí que haja a necessidade de compatibilizar os equipamentos: e foi esse um dos grandes feitos dos computadores "PC Compatíveis". Criar uma base estável, que garantia que um programa feito para correr num deles, correria em todos os que fossem compatíveis.
Senão, imaginem a loucura que seria programarem para computadores, onde cada um tinha a memória, inputs e outputs, e tudo o resto, em locais diferentes?


Mas, ainda não perceberam lá muito bem como é que de assembler se pode chegar a um fabuloso jogo 3D num ecrã de alta-resolução?

O "segredo" é que um computador consiste num infindável número de módulos, e o CPU já não está sozinho no que toca a fazer todas as operações. Actualmente, temos placas gráficas com GPUs que são tão - ou mais - complexos que um CPU; controladores de discos que se encarregam de transferir dados quase sem intervenção do CPU; e muito mais.
Sim, tempos houve em que tinha que ser o CPU a preocupar-se em colocar o motor do drive de disquetes a girar, a posicionar a cabeça de leitura/gravação no sector respectivo, e ler ou escrever dados... Tudo graças aos tais endereços de input/output que lhe davam controlo sobre todos esses pequenos motores.

Aliás, é algo com que ainda hoje se deparam todos aqueles que gostam de brincar com Arduinos: portas de entrada, portas de saída, e tudo o mais que serve de base a todos os complexos equipamentos que temos hoje.


Os Drivers

Nos "PCs", parte dessa complexidade evoluiu na forma de "drivers". Alguma vez pensaram no que é efectivamente um driver? Aquele pedaço de software que frequentemente são obrigados a instalar sempre que colocam uma peça nova no vosso sistema (seja ela uma placa gráfica ou uma câmara ou impressora)?


Basicamente, temos um sistema "compatível", onde - por exemplo, e no caso de uma placa gráfica - se define que existe um conjunto de funções para concretizar um objectivo, por exemplo: pintar um pixel nas coordenadas X, Y, na cor pretendida.
Quem faz os programas para esse sistema, sabe que pode chamar essa função... sem nunca se ter que preocupar qual a placa gráfica que irá existir no computador do utilizador, que tipo de GPU ela utiliza, ou em que endereço ela está. Cabe ao fabricante da placa criar o "driver" que serve de intermediário, e que sabe que - quando alguém chamar aquela função de pintar um pixel, irá movimentar todos os bits necessários para os locais respectivos, de forma a fazer com que no ecrã surja o resultado pretendido.

Sim, como podem imaginar, mesmo uma operação simples pode originar milhares ou milhões de operações que terão que ser processadas pelo CPU.
Agora mesmo, sempre que carrego numa tecla... há um processo que se encarrega de a ler, enviar para a janela do programa correcta, mas ao mesmo tempo está a ver se a palavra está bem escrita... desenhando-a no ecrã, com uma linha a vermelho em caso de erro... enquanto na barra inferior o relógio é actualizado de minuto a minuto, e na janela do lado informações continuam a fluir, ao mesmo tempo que música continua a tocar noutra janela...
E é por isso que muitas vezes, mesmo os CPUs que trabalham a frequências de Gigahertzs parecem não ser tão rápidos como desejaríamos....


Já agora, essa frequência do CPU (assim de modo muito geral) indica a velocidade com que o CPU é capaz de executar uma instrução.

E já agora, ficam também a conhecer a diferença entre CPUs que agora estão cada vez mais na moda: os CISC e os RISC.


CISC e RISC

Lembram-se das tais instruções que podemos dar ao CPU para eles executar? Pois bem... há CPUs e CPUs; e dividem-se principalmente em duas grandes famílias: osCISC (Complex Instruction Set Computing) e os RISC (Reduced Instruction Set Computing).

Com a evolução da microelectrónica, os CPUs foram ficando cada vez mais complexos e evoluídos. O tal número de instruções que um CPU podia executar não parava de crescer e aumentar.
Começam a surgir instruções que permitiam, num só comando, ir buscar um valor ao endereço de memória "X", ir buscar outro ao endereço "Y", somar ambos os valores e colocar o resultado no endereço "Z"!
Rapidamente, estes CPUs se tornaram em sistemas altamente complexos.


Chegou-se ao ponto - como nos casos dos nossos bem conhecidos processadores x86 (cuja evolução nos trouxe do "PC Compatível" com o seu Intel 8086 até aos mais recentes Intel Core i7 actuais) - em que os CPUs eram já tão complexos, que cada instrução era internamente subdividida em várias micro-instruções mais pequenas que eram executadas pelo CPU!

Isto fez com que alguns investigadores decidissem regressar às origens e simplificar tudo novamente, com os processadores RISC (como os tão em voga processadores ARM que tanto têm dado que falar.) A ideia era simples: simplificar e reduzir. Em vez de instruções complexas, o CPU seria apenas capaz de fazer as operações mais básicas e essenciais.

Em vez de termos uma única instrução como a que referi anteriormente, de ir buscar dois valores a dois sítios diferentes e colocar a soma num terceiro sítio; num CPU RISC temos que fazer todos esses passos expressamente:


  1. ir buscar o valor 1 e guardar num registo temporário A;
  2. ir buscar o valor 2 e colocá-lo noutro registo B; 
  3. somar o registo A com o registo B (ficando aqui o resultado); 
  4. guardar o registo B na posição de destino 3. 
Dá mais trabalho, não? Então, qual a vantagem?
A vantagem é que o CPU tem um processo de fabrico muito mais simples; e em grande parte dos casos, pode até correr mais depressa. Em vez de instruções complexas que têm que ser convertidas em microcódigo e podem demorar dezenas ou centenas de ciclos de relógio, num processador RISC quase sempre cada instrução pode ser executada num único ciclo. (É por isso mesmo que não se pode comparar directamente um CPU CISC x86 a 1Ghz, com um processador ARM RISC a 1Ghz - são coisas completamente diferentes!)

Para além disso, todo esse trabalho extra acaba por ser feito pelos compiladores, já que a programação será feita numa linguagem de mais alto nível, como C.

Até aqui relegados para papeis menos mediáticos, os CPUs RISC começam agora a mostrar todo o seu valor nos Android, iPhones e demais tablets e dispositivos mobile, onde oferecem uma eficiência que não tem sido possível igualar com os mais complexos e mais gastadores CPU Cisc.

[Fonte

Nenhum comentário:

Postar um comentário