Baixe o app para aproveitar ainda mais
Prévia do material em texto
Arquitetura de Computadores (ARQI1) ADS171 Aula 09: Execução de código Conceito Os processadores trabalham apenas com informações digitais – 0s e 1s – formando um conjunto de bits (byte, word, doubleword, quadword). Um processador contém um repertório – conjunto ou set – de instruções, as quais são compreendidas pelo processador através de um código em formato binário: o op code. Assim, um programa é na realidade um conjunto de códigos binários, cada um correspondendo à determinada instrução. Programar em código de máquina, seja este em binário ou hexadecimal, é chamado de programação em linguagem de máquina. Como tais códigos são de difícil manipulação, os fabricantes de processadores colocam um nome para cada instrução. Este nome é chamado de mnemônico. A programação através de mnemônicos é conhecida por linguagem Assembly Assembly Na realidade, a linguagem Assembly é muito semelhante à linguagem de máquina, só diferindo da utilização de nomes ao invés de códigos. Como são linguagens que envolvem um conhecimento muito profundo do processador e são distantes da linguagem formal utilizada por nós humanos, são conhecidas por linguagens de baixo nível. Todas as demais linguagens existentes são denominadas linguagens de alto nível, pois estão mais próximas da linguagem humana. Mas tais linguagens não são entendidas pelo processador. Até mesmo a linguagem Assembly não é entendida pelo processador, que só reconhece a linguagem de máquina, os op codes. Daí, temos a necessidade de transforma a linguagem de alto nível ou Assembly, o código- fonte (o programa), em linguagem de máquina. Em alguns casos existe a figura do interpretador, que lê códigos intermediários, resultantes de uma compilação e executa rotinas (pequenos trechos de programa) em código de máquina. Assembly Transformar um código fonte em um arquivo executável – que contenha op codes – é um processo chamado compilação. A compilação é feita por uma aplicação chamada compilador. Existem milhares de compiladores, que devem ser compatíveis com a linguagem de alto nível utilizada e o sistema operacional/processador em questão. Quando o código fonte está em linguagem Assembly, utilizamos um compilador especial, denominado Assembler. O Assembler tem uma tarefa mais simples que os demais compiladores, pois basicamente transforma mnemônicos em op codes. O Assembler depende do processador que estamos trabalhando e também do sistema operacional. Transformando de alto nível para linguagem de máquina Algumas linguagens passam para uma linguagem intermediária (compilação) e depois interpretam o código intermediário, executando, depois, código de máquina. São os casos de Java (javabeans+JRE) e C# (MSIL+.NET Framework). Instruções Conforme o processador, as instruções são agrupadas conforme sua função. Assim, em processadores Intel 8086/88, as instruções são divididas nos seguintes grupos e subgrupos: Instruções A partir do Pentium MMX, a Intel criou a IA-32, sigla da arquitetura interna destes processadores, e dividiu as instruções em uma outra forma, conforme o tipo de dado: Exemplo de instrução Instrução MOV Sintaxe: MOV op1, op2 Onde op1 é o operando destino e op2 é o operando fonte. MOV copia o dado contido no operando fonte para o operando destino. O operando op2 pode ser um dado, um registrador ou uma posição de memória. Op1 poderá ser um registrador ou uma posição de memória. Alguns tipos de movimentação não são validos, tais como mover um valor diretamente para um registrador de segmento ou copiar o conteúdo de um endereço de memória para outro diretamente. O op code da instrução MOV varia conforme os operandos utilizados. Exemplo de instrução Exemplos: MOV AH,01 Coloca no registrador AH o valor 01 (byte – 8 bits) MOV BX,1234h Coloca em BX o valor 1234h MOV ECX,11223344h Coloca em ECX o valor 1223344h MOV AX,BX Copia o conteúdo de BX em AX MOV DS,AX Copia o conteúdo de AX em DS MOV [8000h],AL Coloca no endereço DS:8000h o conteúdo de AL (não apaga o conteúdo em AL ) MOV [BX],AH Coloca o conteúdo de AH no endereço de memória indicado por DS:BX Exemplo de instrução Exemplos não válidos: MOV SS,5000h Não é válido !!!!!! Não pode movimentar valores diretamente para registradores de segmento MOV AL,BX Não válido!!! Não se pode copiar um conteúdo de 16 bits para um registrador de 8 bits. MOV [0002h],1 Não válido!!! Não dá para definir se o operando 1 é um byte ou uma word. Op codes gerados Note que o op code varia de instrução para instrução, conforme, no caso de MOV, dos operandos envolvidos. Cada instrução MOV do exemplo ao lado resultou em 3 bytes de código, sendo 1 byte de op code e 2 bytes de operando. Note que o operando está invertido, pois o processador trabalha desta forma: primeiro armazena a parte baixa, depois armazena a parte alta. Já a instrução int 3, apesar de ter um operando, resultou em apenas 1 byte de op code. Ciclo Buscar-Decodificar-Executar Quando desejamos executar um programa, informamos através do Sistema Operacional qual programa desejamos executar e onde está tal programa. O programa, caso seja um código executável, é um conjunto de instruções, que é carregado para um área da memória especificada pelo Sistema Operacional. Veja o esquema: Ciclo Buscar-Decodificar-Executar O sistema operacional recebe um comando – o nome do programa e sua localização, por exemplo; O Sistema operacional, que também é um programa que está sendo executado, faz com que o processador atue nos dispositivos de E/S – como por exemplo, o disco rígido – informando para transferir todo o programa para uma determinada área da memória. A partir daí o Sistema Operacional informa ao processador para saltar para o endereço da 1ª instrução do programa carregado na memória – um desvio – carregando os devidos endereços em CS:IP. Um arquivo executável é composto por instruções em linguagem de máquina, que por sua vez são representadas pelos op codes. Basta então que o processador acesse estas instruções na memória, interpreta-as e realize as operações necessárias, continuando o processo (indo para os endereços seguintes) até o fim do programa. A este ciclo damos o nome de ciclo buscar-decodificar-executar. Tal ciclo se repetirá para cada instrução do programa. Ciclo Buscar-Decodificar-Executar A primeira parte do ciclo – buscar – consiste no processador dispor de um endereço de memória que indique onde está a instrução a ser buscada. Este endereço será indicado por CS:IP, que é enviado à BIU (Unidade de Interfaceamento de Barramento). A BIU acessa a memória, recebendo no barramento de dados o op code da instrução. ] O código recebido é testado por uma unidade e, se for apenas um dado, é enviado para um registrador especificado pelo processador. Se for uma instrução, o código é encaminhado para a Fila de Instrução. O ponteiro CS:IP é incrementado para buscar um outro código. Todo este ciclo é executado pela Prefetch Unit do processador. Ciclo Buscar-Decodificar-Executar Ciclo Buscar-Decodificar-Executar O ciclo decodificar é realizado pela Unidade de Decodificação de Instrução. Esta unidade busca na Fila de Instruções o opcode e envia à um circuito que irá comparar tal código com uma tabela, trocando-o por uma série de microcódigos, os quais são devolvidos à unidade de execução para serem executados. Como tal tabela é uma espécie de software gravado no hardware (processador), ela também é conhecida por Firmware. Quanto mais instruções o processador tem em seu set de instruções, maior e mais complexo é este Firmware. Ciclo Buscar-Decodificar-Executar Após o ciclo decodificar vem o ciclo executar. O ciclo executar é feito pela Unidade de Execução. Esta unidade recebe os microcódigos e realiza as operações indicadas por estes. Basicamente, o que o processador pode executar resume-se nas seguintes operações: a) Registradores: a operação consiste em ler o conteúdo de um determinado registrador ou armazenar um dado em um registrador. Quem define o registrador é o processador, conforme os microcódigos recebidos. b) Desvio: tal operação é um desvio no fluxo de execução do programa, consistindo em alterar o CS e/ou IP. c) Cálculo: a operação é um cálculo. O processador informa a ALU – Unidade Lógico Aritmética – o que deseja e os dados que serão processados pela ALU. d) Acesso à Memória ou E/S: a operação é de acesso a memória ou um dispositivo de E/S (porta de E/S). O processador informa à BIU e envia os dados e endereços necessários à esta. Pipeline Observamos no capítulo anterior, a seqüência buscar-decodificar-executar. A princípio parece ser um mecanismo eficiente, inclusive por contar com unidades que trabalham separadamente, cada qual com uma tarefa distinta. Porém, tal processo não é eficaz. No momento em que o processador está executando uma operação, não está ocorrendo nenhuma atividade na busca de um novo código, nem na decodificação de outros op codes. O processador necessitaria que a execução da instrução termine para poder iniciar todo um novo ciclo. Isto é considerado desperdício de tempo: enquanto uma unidade está trabalhando, as outras estão paradas. Para que tal desperdício de tempo não ocorra é que existe a técnica de pipeline: todas as partes do ciclo buscar-decodificar-executar, funcionando ao mesmo tempo. Isto significa que enquanto a unidade de execução está trabalhando, há um outro op-code sendo decodificado e uma outra instrução sendo buscada, tudo ao mesmo tempo. Há um paralelismo interno. Pipeline Há mudanças internas no processador para que ele execute este paralelismo.Em comparação com o esquema anterior, notamos a seguinte diferença: a unidade de decodificação e execução foi desmembrada. Agora, a unidade de decodificação de instrução busca o op code na fila, chamada corretamente de Prefetch Queue. O op code é enviado para a tabela de micro códigos, sendo traduzido. Os micro códigos são enviados para uma outra fila, chamada de Instruction Queue. Dalí, são buscados pela Unidade de Execução, que efetua as operações. E o mais importante: todas estas unidades funcionam ao mesmo tempo, aumentando a performance do processador. Apesar do tempo de execução da instrução não ser alterado pelo pipeline, o programa como um todo tem um ganho significativo de tempo, pois enquanto uma instrução está sendo executada, outra já está sendo buscada, e outra decodificada. Pipeline Superescalaridade Em um processador superescalar, a Unidade de Execução é dividida em várias partes. Aliás, verdadeiramente, a divisão acontece na ALU (Unidade Lógico-Aritmética), que agora dividida, tem o poder de realizar mais de uma operação aritmética por ciclo. O Intel Pentium foi o 1° processador da Intel a contar com a superescalaridade. Sua Unidade Lógico-Aritmética era capaz de processar um cálculo com números inteiros, outro cálculo com números de ponto flutuante e ainda calcular um endereço de desvio, tudo ao mesmo tempo. E para agilizar ainda mais o processo, a Instruction Queue, que contém os micro códigos a serem executados, também é desmembrada, tendo agora uma fila para operações com ponto flutuante, outra para números inteiros e outra para desvio, para que a fila não fique “congestionada” no caso de existir uma seqüência de um só tipo de cálculo. Os atuais processadores contam com um número maior de unidades de execução para diferentes tipos de execução Superescalaridade Execução dinâmica A Partir do Pentium Pro, a Intel implementou algo inovador: um mecanismo de “execução fora-de-ordem” (out-of order execution) denominado Execução Dinâmica. A Execução Dinâmica compreende três conceitos de processamento de dados: Previsão profunda de saltos; Análise dinâmica do fluxo de dados; Execução especulativa. Tais conceitos foram implementados para melhorar a performance do processador, sendo hoje considerados fundamentais em processadores de alta performance. Previsão de salto Como a técnica de pipeline consiste em executar várias partes do ciclo buscar-decodificar-executar ao mesmo tempo, a Prefetch Queue está sempre cheia, e o processador sempre está executando uma instrução. Sendo superescalar, isto ocorre mais rápido. Agora, suponha o seguinte programa: 1B79:0100 8B1E0000 MOV BX,[0000] 1B79:0104 8B07 MOV AX,[BX] 1B79:0106 50 PUSH AX 1B79:0107 EB07 JMP 0110 1B79:0109 43 INC BX 1B79:010A 8B0F MOV CX,[BX] 1B79:010C 90 NOP 1B79:010D 51 PUSH CX 1B79:010E 43 INC BX 1B79:010F 43 INC BX 1B79:0110 83C302 ADD BX,02 1B79:0113 8B07 MOV AX,[BX] 1B79:0115 50 PUSH AX Previsão de salto As instruções são buscadas e colocadas na prefetch queue na seguinte ordem: Previsão de salto Segundo o conceito de pipeline, tais instruções foram buscadas, decodificadas e executadas. Até a 3a instrução tudo ocorre normalmente. Porém, ao executar a 4ª instrução, que é um desvio incondicional para o offset 0110h, o processador deve se desviar para tal offset. A 5ª, 6ª, 7ª e assim por diante, instruções, buscadas e decodificadas não serão executadas: o processador “perdeu tempo” buscando e decodificando estas instruções, quando o que seria considerado correto era começar a buscar a partir do offset 0110h e assim por diante. Mas o processador não sabia que a 4ª instrução era um desvio. Com o mecanismo de previsão de saltos, as instruções seriam executadas “fora-de-ordem” de que se encontram na fila. Mas como o processador iria “adivinhar o salto” antes de ele ser executado? Previsão de salto Agora, antes da Prefetch Queue há a Unidade de Execução Especulativa, que, conhecendo de antemão o op code das instruções de desvio, já executa o desvio antecipadamente, fazendo com que, no ciclo de busca seguinte, o processador busque os códigos já no endereço correto. Com isso, não há perda de tempo: sempre se estará buscando e decodificando as instruções na ordem correta de execução, fora da ordem que estão na memória. Análise dinâmica do fluxo de dados Em um processador superescalar, tem-se um ponto ideal de desempenho quando todas as unidadesde execução (ponto flutuante e inteiros) estejam “cheias”, isto é, processando. Porém, conforme o fluxo do programa, pode ser que determinada unidade esteja sobrecarregada e outra vazia, fazendo com que o desempenho do processador não seja o melhor possível. A análise dinâmica de fluxo de dados envolve uma análise do fluxo do programa em tempo real, das instruções que passam pelo processador. A unidade de execução dinâmica, baseada em informações da unidade de execução determina certas pendências de dados e conteúdos de registradores que poderiam atrasar o processamento, detectando oportunidades de executar algumas instruções fora de ordem, para elevar a performance de processamento, pois a execução fora de ordem coloca uma ordem que não venha causar dependências de resultados, deixando as unidades de execução permanentemente ocupadas. Execução especulativa Observe o código de programa a seguir. No offset 0108h temos um desvio condicional. E aí surge a seguinte dúvida: e se o processador executar antecipadamente este salto? Como ele saberá se é para executar ou não, já que ele se baseia em uma condição (AX=FFFFh)? Deixar de buscar antecipadamente as instruções ou antecipar a busca como se o desvio condicional fosse ser executado poderia resultar em perda de tempo em ambos os casos. Execução especulativa É neste ponto que entra a execução especulativa. Se a análise dinâmica de fluxo de dados não resolver o problema, a unidade de execução especulativa irá calcular uma estatística da probabilidade deste desvio condicional ser executado e, baseado neste dado, executará o desvio um determinado número de vezes. Isto pode dar errado, pois não há como o processador acertar em 100% dos casos, mas na maior parte o processador acerta, poupando tempo e diminuindo o tempo de processamento.
Compartilhar