Sítio do Piropo

B. Piropo

< Coluna em Fórum PCs >
Volte
23/01/2006

< Computadores XXI: Busca e execução >
<
Final
>


Na coluna anterior, “Busca e execução – Primeiro passo”, discutimos os conceitos básicos do Ciclo de Busca e Execução e examinamos seu primeiro passo. Na de hoje examinaremos os demais passos e discutiremos alguns conceitos importantes sobre o fluxo dos programas.

Figura 1: Estado após primeiro passo.

No exemplo que estamos discutindo, ao final do primeiro passo do Ciclo de Busca e Execução o estado das posições de memória da MP e dos registradores da UCP é o mostrado na Figura 1, ou seja, o conteúdo da posição da memória principal de endereço 247 (11110111 em binário) foi lido e transcrito para o Registrador de instruções. Esse número (no caso, 00010111) é a próxima instrução a ser executada. Mas antes dessa execução é preciso tomar uma providência vital: incrementar o Ponteiro de Instruções para que ele passe a apontar para a instrução seguinte. Esta singela operação é o segundo passo do ciclo de busca e execução.
Incrementar significa “somar uma unidade”. No nosso exemplo, após o incremento o conteúdo do Ponteiro de instruções passa a ser 248 (ou 11111000 em binário). Se você reparar na Figura 1 verá que a posição de memória que tem este endereço situa-se imediatamente após a posição para a qual apontava o PI. Ou seja: o PI agora “aponta” para a posição de memória seguinte, como mostra a seta azul da Figura 2, que exibe o estado do sistema após a execução do segundo passo do ciclo de busca e execução, o incremento do Ponteiro de Instrução.

Figura 2: Estado após segundo passo.

Neste ponto a instrução a ser executada já foi lida e transposta para o Registrador de Instruções e o Ponteiro de Instruções já aponta para a posição da MP que contém a instrução seguinte. Só falta, portanto, executar a instrução. Este é o terceiro passo do ciclo da busca e execução, o passo da “Execução”.
Este passo depende da instrução a ser executada. Ela pode ser simples como incrementar um registrador qualquer, ou complexa como copiar o conteúdo de um longo trecho da memória para outro. Mas no que toca ao ciclo de busca e execução não importa seu grau de complexidade: ela é encarada simplesmente como o terceiro passo, o da execução.
Este passo se inicia com a transposição do conteúdo do registrador de instruções (que, como sabemos, contém a instrução a ser executada) para o interior da Unidade de Controle, mais especificamente para o decodificador situado logo na entrada desta unidade. O decodificador (nosso velho conhecido da coluna “Multiplexadores, decodificadores e flip-flops”) “decodifica” a instrução, ou seja, energiza os circuitos lógicos que disparam as micro-operações correspondentes à execução da instrução, que por sua vez, se necessário for, acionarão a unidade lógica e aritmética e os demais circuitos eventualmente envolvidos na execução da instrução. Mas, sempre é bom reiterar: do ponto de vista do ciclo de busca e execução, tudo isto se resume ao passo da execução, seja quais forem as operações envolvidas. Uma representação do terceiro passo, o da execução, com a introdução da instrução na unidade de controle, sua decodificação e execução, é representada esquematicamente na Figura 3.

Figura 3: Terceiro passo: “Execução”.

E com a execução da instrução termina o ciclo de busca e execução.
O problema é que no computador, as coisas só “terminam” quando não há energia. Enquanto o microprocessador está sendo alimentado com eletricidade, ele continua executando busca e execução de instruções, ciclo após ciclo. Então, após terminar o ciclo que acabamos de examinar, inicia-se outro.
Como vimos no passo anterior, o PI agora aponta para uma nova instrução. O primeiro passo do novo ciclo consistirá então em buscar esta instrução e escrevê-la no registrador de instruções, tomando o lugar desta que acabamos de executar. Em seguida, no segundo passo, o ponteiro de instruções será incrementado e passará a apontar para a posição de memória de endereço 249. E no terceiro passo a nova instrução recém-copiada no registrador de instrução será executada. E assim por diante, ciclo após ciclo.
Resumindo: o ciclo de busca e execução consiste de três passos: busca, incremento e execução.
Busca: verificar o endereço contido no Ponteiro de Instruções, ir até à posição de memória correspondente, ler seu conteúdo e copiá-lo no Registrador de Instruções.
Incremento: incrementar o conteúdo do ponteiro de instruções
Execução: introduzir o conteúdo do registrador de instruções na unidade de controle, onde este número (que representa uma instrução) será decodificado e a instrução correspondente executada.
Estes três passos são repetidos indefinidamente, ciclo após ciclo.
Agora que já conhecemos o ciclo de busca e execução, pense um pouco e responda: em um dado momento, durante a execução de um programa, o que representam os números contidos no ponteiro de instruções e no registrador de instruções?
Para quem entendeu o procedimento descrito nesta coluna e na anterior a resposta é simples: o registrador de instruções contém sempre a instrução que está sendo executada naquele momento, enquanto o ponteiro de instruções contém o endereço da posição de memória onde está armazenada a próxima instrução a ser executada. É simples assim.
Dirimindo dúvidas
Com os conhecimentos adquiridos até este ponto podemos esclarecer duas dúvidas relativamente comuns.
A primeira delas: se a memória principal pode armazenar dados, instruções e endereços de outras posições de memória e se tanto uns quanto outros são representados por números expressos no sistema binário, como a UCP distingue um dos outros? Explicando melhor: como a UCP distingue um dado de um endereço e de uma instrução?
A resposta é simples: ela não distingue. A distinção fica por conta do fluxo do programa.
A coisa funciona assim: se o conteúdo de uma posição de memória for copiado no registrador de instruções, ao final daquele ciclo de busca e execução ele será inapelavelmente introduzido na Unidade e Controle e executado. Se for uma instrução do programa, muito bem. Se não for e tiver sido movido para o RI por engano, qualquer coisa pode acontecer (geralmente a máquina “trava”), pois provavelmente haverá uma instrução com aquele número e a unidade de controle tentará executá-la. Portanto, e respondendo à pergunta: se o número for copiado no registrador de instruções, ele será interpretado como uma instrução que será executada.
Se, no entanto, ele for copiado em um dos registradores que exercem a função de “ponteiro”, como o ponteiro de instruções, ele será considerado um endereço de uma posição de memória e tratado como tal.
Senão, ele será encarado como um dado. É simples assim.
A segunda é uma dúvida igualmente comum: por que o programa precisa ser armazenado na MP para ser executado?
Simples: porque o primeiro passo do ciclo de busca e execução, o passo da “busca”, consiste em copiar no registrador de instruções o conteúdo da posição de memória principal “apontada” pelo ponteiro de instruções. E o ponteiro de instruções não pode apontar para nenhum outro lugar que não a MP. Portanto as instruções que compõem o programa precisam estar na MP para que sejam transpostas para o registrador de instruções e executadas.

O fluxo do programa

Se você prestou atenção nos três passos do ciclo de busca e execução percebeu que no segundo passo o conteúdo do Ponteiro de Instrução foi incrementado, fazendo-o “apontar” para a posição de memória de endereço imediatamente superior.
Ora, se não houver uma forma de alterar isto o programa será inapelavelmente executado instrução após instrução, sucessivamente, da primeira à última, jamais alterando a ordem em que as instruções são executadas do início ao final do programa. Há programas assim, como os conversores de formato de arquivos, mas são raros. Na maioria das vezes a ordem em que as instruções são executadas depende de resultados intermediários obtidos pelo próprio programa ou de dados supridos pelo usuário. Portanto deve haver algum meio de alterar a ordem de execução das instruções, ou seja, desviar o fluxo de instruções da ordem em que estão armazenadas na memória.
Mas de acordo com os passos acima descritos, isso aparentemente é impossível, já que o conteúdo do PI é sempre incrementado de uma unidade – o que implica obrigatoriamente a execução da instrução armazenada no endereço da MP imediatamente superior. Como podem então ocorrer os desvios de fluxo no interior de um programa?
A explicação é simples: o próprio programa pode alterar o conteúdo do ponteiro de instruções executando “desvios” ou “ciclos” (“loops”). Veja como isso é possível.
Imagine que em um dado momento o programa dependa de um dado de entrada. Se este dado for, por exemplo, o nome de uma pessoa, o usuário deverá entrar com um caractere e o programa deverá iniciar a rotina correspondente à entrada de nomes. Se, por outro lado, for a idade desta pessoa, o usuário deverá entrar com um algarismo e o programa deverá iniciar a rotina correspondente à entrada de idades.
Rotinas são conjuntos de instruções encadeadas que servem para executar uma tarefa específica, uma parte de um programa (dentro desta ótica, podemos considerar a rotina como um “sub-programa”, ou como um programa “dentro” de outro programa). Portanto, como os programas, as rotinas são um conjunto de instruções concatenadas armazenadas em posições de memória em geral adjacentes.
Imaginemos que a rotina de entrada de nomes ocupe um trecho da memória cuja posição inicial tem o endereço 300000 e a rotina de entrada de números ocupe outro trecho, cujo endereço da posição inicial seja 500000. E imaginemos que, em um dado momento, o programa está executando a rotina de entrada de dados, mais especificamente a instrução que está armazenada no endereço 100000.
Se isto é verdade, e considerando o que sabemos sobre o ciclo de busca e execução, o ponteiro de instruções deve conter o valor 100001 (o endereço da posição de memória que contém a próxima instrução a ser executada caso o fluxo não seja alterado).
Pois bem: vamos supor que neste momento o usuário teclou a letra “c”.
No exemplo que estamos examinando, a instrução contida no endereço de memória 100000 deve fazer parte da rotina que processa as entradas de teclado. Como ela está no Registrador de Instruções, está sendo e executada. Durante sua execução o programa analisa a entrada do teclado e constata que o usuário teclou uma letra. Logo, ele está entrando com um nome. A rotina que processa entradas de nomes está armazenada na memória a partir do endereço 300000. Isto significa que a próxima instrução a ser executada é a que está no endereço 300000 (início da rotina de entrada de nomes) e não aquela cujo endereço ocupa neste momento o Ponteiro de Instruções.
Ora, o Ponteiro de Instruções é um registrador de uso específico mas, como os demais,  pode ter seu conteúdo alterado durante a execução de uma instrução. E como o programa determinou que a próxima instrução a ser executada é aquela contida no endereço 300000, dirige o fluxo do programa para lá simplesmente escrevendo no Ponteiro de Instruções o valor 300000. Isto fará com que, no próximo ciclo de busca e execução, seja executada a primeira instrução da rotina de entrada de nomes.
Evidentemente, no nosso exemplo, caso o usuário tivesse premido a tecla correspondente a um algarismo o programa teria escrito em PI o valor 500000 e iniciado a rotina de entrada de números.
Em resumo: o fluxo do programa pode ser alterado pelo próprio programa mudando o conteúdo do ponteiro de instruções durante a execução de uma dada instrução. Quando isto ocorre, em vez de ser executada a instrução correspondente ao incremento do ponteiro de instruções, será executada aquela contida na posição de memória correspondente ao novo endereço escrito no ponteiro de instruções.
Assim, alterando o conteúdo do ponteiro de instruções de modo a fazê-lo “apontar” para o início da rotina desejada, o próprio programa controla seu fluxo dependendo das entradas de dados fornecidos pelo usuário.
Todas as “decisões” tomadas pelos programas que causam alterações na ordem de execução de instruções decorrem de procedimentos semelhantes: comparar dados de entrada ou resultados intermediários e, dependendo do resultado desta comparação, escrever no PI o endereço da posição de memória que contém a primeira instrução da rotina a ser executada.
Finalmente, um último ponto a ser esclarecido: o que acontece quando o programa “acaba” (ou seja, quando é executada sua última instrução)?
Bem, existe uma instrução específica para esta finalidade, ou seja, encerrar um programa. Quando executada, ela escreve no ponteiro de instruções um endereço correspondente a uma das rotinas do sistema operacional.
Ora, o sistema operacional é um programa como outro qualquer. Ele se destina a controlar a máquina e seus periféricos e a carregar e administrar a execução dos demais programas, mas também consiste de um conjunto de instruções distribuídas por diferentes rotinas que são executadas sucessivamente. Ou seja: quando um programa termina, a UCP continua fazendo a única coisa que “sabe” fazer por si mesma: uma sucessão infindável de ciclos de busca e execução. A única diferença é que as instruções agora executadas fazem parte do sistema operacional. E assim prossegue até que a máquina seja desligada e cesse a atividade da UCP.
É somente neste caso que a sucessão de ciclos de busca e execução é interrompida.

Coluna anterior: “Busca e execução – Primeiro passo”
Próxima coluna: Em breve.

B. Piropo