Curso de Ciências da Computação 1. NÍVEL CONVENCIONAL DE MÁQUINA (Cont.) 1.6. Tipos de Instruções As instruções podem ser classificadas de acordo com o número de endereços que elas utilizam. Não se deve esquecer que um conjunto de registradores numerados da CPU constitui, de fato, uma memória de alta velocidade e define um espaço de endereçamento. Uma instrução que soma o registrador 1 ao registrador 2 deve ser classificada como tendo dois endereços porque a instrução deve especificar quais os registradores que serão adicionados, do mesmo modo que uma instrução que soma duas palavras de memória deve especificar as palavras. Instruções que especificam um, dois e três endereços são comuns. Em muitas máquinas que fazem aritmética com um endereço apenas, um registrador especial denominado acumulador prove um dos operandos. Nestas máquinas, o endereço é geralmente o endereço m de uma palavra de memória, no qual se localiza o operando. A instrução para adição com o conteúdo instruções de dois endereços de soma utiliza um dos endereços como fonte e o outro como destino. A fonte é então somada ao destino: destino := destino + fonte As instruções de três endereços especificam duas fontes e um destino. As duas fontes são adicionadas e armazenadas no destino. As instruções de nível convencional de máquina podem ser divididas aproximadamente em dois grupos: instruções de propósito geral, e; instruções de propósito especial. As instruções de propósito geral tem vasta aplicação. Por exemplo, a capacidade de mover dados pela máquina é algo necessário em quase toda aplicação. As instruções de propósito especiais têm aplicações bem restritas. Por exemplo, a instrução MOVEP do 68000 pega o conteúdo de um registrador D e o armazena na memória em bytes alternados, com um byte não usado entre os bytes de dados. Poucas aplicações podem fazer uso efetivo desta instrução, e nenhum compilador irá gerá-la. (Ela foi incluída para simplificar a comunicação com as antigas pastilhas periféricas de 8 bits.) Nosso foco, portanto, será os principais grupos de instruções de propósito geral. 1.6.1. Instruções de Transferência de Dados A cópia de dados de um lugar para outro é a mais fundamental de todas as operações. Por cópia entendemos a criação de um novo objeto, com o padrão de bits idêntico ao do original. O uso da palavra "transferência" é algo diferente de seu uso normal em português. Quando dizemos que um paciente foi transferido da UTI para o quarto, não significa que uma cópia idêntica do paciente foi criada no quarto e o original permaneceu na UTI. Disciplina: Projeto Lógico de Computadores (5º/6º Sem - 2014). Livro: Andrew S. Tanenbaum Página 1 de 7 Curso de Ciências da Computação Quando dizemos que o conteúdo da posição de memória 2000 foi transferido para algum registrador, isto quase significa que uma cópia idêntica foi criada lá e que o original permanece intacto na posição 2000. Instruções de transferência de dados seriam melhor chamadas de instruções de "duplicação de dados", mas o termo "transferência de dados" já está oficializado. Dados podem estar armazenados em vários lugares, diferindo no modo como as palavras são acessadas. Três lugares comuns são: Uma palavra de memória específica; Um registrador, ou; A pilha. A pilha é mantida tanto em registradores especiais como na memória, mas a maneira de acessa-la é diferente dos acessos-padrão a memória. Um acesso à memória requer um endereço, enquanto o empilhar de um item não explicita endereço na pilha. Sabemos que é altamente desejável ter instruções de máquina tão curtas quanta possível para economizar memória e tempo de CPU. O máximo em redução de comprimentos de endereço seria ter instruções com absolutamente nenhum endereço, apenas códigos de operação. Esta situação, bastante surpreendente, é possível. É conseguida organizando a máquina em torno de uma estrutura de dados denominada pilha. Uma pilha consiste em itens de dados (palavras, caracteres, bits) armazenados em ordem consecutiva na memória. O primeiro item colocado na pilha é denominado fundo da pilha. Associado a cada pilha há um registrador ou palavra de memória que contém o endereço do topo da pilha. É denominado apontador da pilha. Utilizar pilhas em aritmética é muito diferente de utilizar pilhas para armazenar variáveis locais (ambos os casos podem, naturalmente, ser combinados). A Fig. 1.8 ilustra a operação de uma pilha. Na Fig. 1.8(a) dois itens já se encontram na pilha. O fundo da pilha localiza-se na posição 1000 da memória e o topo da pilha localiza-se na posição 1001 da memória. O apontador da pilha contém o endereço do item no topo da pilha, ou seja, 1001; isto significa que ele "aponta" para o topo da pilha. Na Fig. 1.8(b), 6 foi colocado na pilha e o apontador da pilha indica 1002 como o novo topo da pilha. Na Fig. 1.8(c), 75 foi colocado na pilha e o apontador da pilha aumentado para 1003. Na Fig. 1.8(d), 75 foi retirado da pilha. Fig. 1.8 Funcionamento de uma pilha. Disciplina: Projeto Lógico de Computadores (5º/6º Sem - 2014). Livro: Andrew S. Tanenbaum Página 2 de 7 Curso de Ciências da Computação Os computadores orientados para pilha possuem uma instrução para inserir o conteúdo de uma posição de memória ou de um registrador na pilha. Tal instrução deve tanto incrementar o apontador de pilha quanta copiar o item. Similarmente, uma instrução que copia o conteúdo do topo da pilha para um registrador ou posição de memória deve fazer a nova cópia para o lugar escolhido e decrementar o apontador da pilha. Alguns computadores tem suas pilhas de cima para baixo, com os novos itens sendo inseridos consecutivamente em posições de memória de endereços de menor valor em vez de consecutivamente em posições de endereços superiores, este segundo como na Fig. 1.8. As instruções sem endereço são utilizadas conjuntamente com uma pilha. Esta forma de endereçamento específica que os dois operandos devem ser retirados da pilha, um após o outro, a operação executada (ex., multiplicação ou AND) e o resultado recolocado na pilha. A Fig. 1.9(a) mostra uma pilha que contém quatro itens. Uma instrução de multiplicação retira o 5 e o 6 da pilha, faz o apontador de pilha apontar temporariamente para 1001 e então recoloca o resultado, 30, de volta na pilha, como mostra a Fig. 1.9(b). Se uma adição for depois executada, o resultado será como mostra a Fig. 1.9(c). Fig. 1.9 Utilização de uma pilha em aritmética. (a) Configuração inicial. (b) Após uma multiplicação, (c) Após uma adição. Instruções de transferência de dados requerem que ambos, fonte (i.e., o original) e destino da informação (i.e., onde a cópia será colocada), sejam especificados quer explícita ou implicitamente. Instruções da transferência de dados precisam indicar, de algum modo, a quantidade de dados a ser transferida. Existem instruções para mover quantidades de dados desde 1 bit até a memória inteira. Em máquinas com palavras de tamanho fixo, o número de palavras a serem transferidas é usualmente especificado pela instrução - por exemplo, instruções separadas para mover uma palavra e mover meia palavra. Máquinas com palavra de tamanho variável frequentemente tem instruções que apenas especificam os endereços de fonte e destino mas não à quantidade. A transferência continua até que a marca do fim de dados é encontrada nos próprios dados. As CPUs Intel tem instruções MOVE muito limitadas, mas existem muitas delas, de tal maneira que são também possíveis transferências entre dois lugares quaisquer. Disciplina: Projeto Lógico de Computadores (5º/6º Sem - 2014). Livro: Andrew S. Tanenbaum Página 3 de 7 Curso de Ciências da Computação 1.6.2. Operações Diádicas Operações diádicas são aquelas que combinam dois operandos para produzir um resultado. Existe basicamente dois grupos destas instruções: Para fazer adição e subtração de inteiros, e; O grupo dos operadores booleanos. Três instruções presentes em muitas máquinas são AND, OR e XOR. Seus usos em operações diádicas são: Em máquinas com palavra de tamanho fixo, AND calcula o produto booleano bit a bit de dois argumentos de uma palavra, tendo como resultado também uma palavra. Outro uso do AND é a extração de bits de palavras. Para extrair o caractere, é feito um AND da palavra contendo o caractere e uma constante, chamada máscara. O resultado dessa operação é que os bits indesejáveis são todos mudados para zeros, isto é, mascarados. Um uso importante para OR é empacotar bits em uma palavra, sendo empacotar o inverso de extrair. A operação AND tende a remover os números 1, porque nunca há mais números 1 no resultado que em qualquer dos operandos. A operação OR tende a inserir números 1, porque há sempre no mínimo tantos números 1 no resultado quanto no operando com mais números 1. A operação XOR, por outro lado, é simétrica, tendendo, em média, a nem inserir nem remover números 1. Quase todas as máquinas de Nível 2 (Nível de Máquina Convencional) tem instruções para fazer adição e subtração de inteiros. Exceto para os microcomputadores de 8 bits, as instruções de multiplicação e divisão de inteiros são também comuns. Praticamente, todos os computadores, portanto, são equipados com instruções aritméticas. 1.6.3. Operações Monádicas As operações monádicas tem “1” operando e produzem “1” resultado. Como são especificados menos endereços que as diádicas, as instruções são bem mais curtas. Instruções para deslocar e rodar o conteúdo de uma palavra ou byte são realmente. Deslocamentos são operações nas quais os bits são movidos para a esquerda ou direita, perdendo os bits deslocados para fora da palavra. Rotações são deslocamentos nos quais os bits jogados fora da palavra reaparecem do outro lado. A diferença entre um deslocamento e uma rotação é ilustrada abaixo. 00000000 00000000 00000000 01110011 A 0000000000000000 00000000 00011100 A deslocado 2 bits para a direita 1100000000000000 00000000 00011100 A rodado 2 bits para a direita Disciplina: Projeto Lógico de Computadores (5º/6º Sem - 2014). Livro: Andrew S. Tanenbaum Página 4 de 7 Curso de Ciências da Computação 1.6.4. FLUXO DE CONTROLE O fluxo de controle se refere à sequência na qual as instruções são executadas. Geralmente, as instruções executadas sucessivamente são buscadas de posições de memória consecutivas. Chamadas de procedimento causam alterações no fluxo de controle, parando o procedimento atual e iniciando o procedimento chamado. Co-rotinas são parecidas com procedimentos e causam alterações similares no fluxo de controle. Armadilhas (traps) e Interrupções também alteram o fluxo de controle quando ocorrem condições especiais. Vejamos: 1.6.4.2. Fluxo de Controle Sequencial e Desvios A maioria das instruções não altera o fluxo de controle. Após uma instrução ser executada, a seguinte na memória é buscada e executada. Após cada instrução, o contador de programa é incrementado do número de posições de memória da instrução. Se observado durante um intervalo de tempo longo comparado com o tempo de instrução médio, o contador de programa é aproximadamente uma função linear do tempo, aumentando do comprimento médio de instruções por tempo médio de instrução. Expresso de outra forma, a ordem dinâmica na qual o processador realmente executa as instruções é a mesma ordem na qual elas aparecem na Iistagem do programa. Se um programa contém desvios, esta relação simples entre a ordem em que as instruções aparecem na memória e a ordem em que elas são executadas não é mais verdadeira. Quando desvios estão presentes, o contador de programa não é mais uma função monotonamente crescente no tempo. Como consequência, torna-se difícil visualizar a sequência de execução de instruções da Iistagem do programa. Quando os programadores tem problemas em manter-se a par da sequência na qual o processador irá executar as instruções, eles estão propensos a cometer erros. Esta observação levou Dijkstra (1968) a escrever um artigo nessa época polêmico intitulado "GO TO Statement Considered Harmful", no qual, sugere evitar comandos GO TO. Este artigo deu origem à revolução da programação estruturada, sendo um de seus lemas a substituição de comandos GO TO por formas mais estruturadas de controle de fluxo, tais como loops WHILE. Naturalmente, esses programas são compilados para programas de nível 2 que podem conter muitos desvios, pois a implementação de IF, WHILE e outras estruturas de controle de alto nível requerem desvios por toda parte. 1.6.4.3. Procedimentos A técnica mais importante para estruturar programas é o procedimento. De certo ponto de vista, uma chamada de procedimento altera o fluxo de controle tal como um desvio faz, mas, ao contrário do desvio, quando terminar sua tarefa ela retorna o controle para o comando ou instrução seguinte a chamada. Entretanto, de outro ponto de vista, o corpo de um procedimento pode ser visto como a definição de uma nova instrução em um nível superior. Deste ponto de vista, uma chamada de procedimento pode ser considerada uma única instrução, mesmo que o procedimento seja muito complicado. Para entender um trecho do programa que contenha uma chamada de procedimento, basta saber o que ele faz, não como ele faz. Disciplina: Projeto Lógico de Computadores (5º/6º Sem - 2014). Livro: Andrew S. Tanenbaum Página 5 de 7 Curso de Ciências da Computação 1.6.4.4. Co-rotinas Na sequência comum de chamada, há uma clara distinção entre o procedimento que chama e o procedimento chamado. Considere um procedimento A que chama um procedimento B. O procedimento B é executado por um tempo e então retorna para A. A primeira vista você poderia considerar essa situação simétrica, porque nem A nem B são programas principais, ambos são procedimentos (o procedimento A pode ter sido chamado pelo programa principal, mas isto é irrelevante). Além disto, inicialmente o controle é transferido de A para B – a chamada - e mais tarde o controle é transferido de B para A - o retorno. A assimetría vem do fato de que quando o controle passa de A para B, o procedimento B começa do início; quando B retorna para A, a execução não começa no início de A, mas no comando seguinte a chamada. Se A é executado por um tempo e chama B de novo, a execução começa no início de B, e não no comando seguinte ao retorno anterior. Se durante a execução A chama B várias vezes, B começa do início todas às vezes, enquanto A nunca começa do início de novo. 1.6.4.5. Traps (Armadilhas) Uma armadilha (trap) é um tipo de chamada automática de procedimento iniciada por alguma condição causada pelo programa, usualmente uma condição importante mas rara. Um bom exemplo é o transbordo (overflow). Em muitos computadores, se o resultado de uma operação aritmética excede o maior número que pode ser representado, ocorre um “trap”, significando que o fluxo de controle é transferido para alguma posição fixa na memória em vez de continuar sequencialmente. Nesta posição fixa há um salto para um procedimento chamado rotina de tratamento de trap, que executa alguma ação apropriada, tal como a impressão de uma mensagem de erro. Se o resultado de uma operação está dentro do intervalo permitido, não ocorre nenhum trap. 1.6.4.6. Interrupções Interrupções são modificações no fluxo de controle causadas não por uma execução de programa mas por outra coisa, usualmente relacionada a E/S. Por exemplo, um programa pode instruir ao disco para prover uma interrupção tão logo à transferência tenha terminado. Como uma armadilha (trap), a interrupção para o programa em execução e transfere o controle para o tratamento de interrupção, que executa alguma ação apropriada. Ao terminar, a rotina de tratamento de interrupção retorna o controle para o programa interrompido. É preciso reativar o processo interrompido exatamente no mesmo estado em que ele estava quando ocorreu a interrupção, o que significa restaurar todos os registradores internos aos seus estados anteriores a interrupção. A diferença essencial entre armadilhas e interrupções é que as armadilhas (traps) são síncronas com o programa e as interrupções são assíncronas. Se o programa é reexecutado um milhão de vezes com a mesma entrada, as armadilhas irão ocorrer no mesmo lugar a cada vez, mas as interrupções podem variar, dependendo, por exemplo, de quando precisamente uma pessoa no terminal pressiona a tecla de retorno do carro. Disciplina: Projeto Lógico de Computadores (5º/6º Sem - 2014). Livro: Andrew S. Tanenbaum Página 6 de 7 Curso de Ciências da Computação 1.7. RESUMO O nível de máquina convencional é o que muitas pessoas tem como "linguagem de máquina". Neste nível a máquina tem uma memória orientada por byte ou por palavra desde dezenas de quilo bytes a dezenas de megabytes, e instruções como MOVE, ADD e JUMP. A maioria dos computadores modernos possui memória que é organizada como uma sequência de bytes, com 2 ou 4 bytes agrupados em palavras. Estão também presentes normalmente entre 8 e 32 registradores, cada um deles contendo uma palavra. Muitos computadores vem em famílias, tais como as famílias Intel. As instruções tem geralmente um ou dois operandos que são endereçados usando os modos imediato, direto, indireto, indexado ou outros modos de endereçamento. Algumas máquinas tem muitos modos de endereçamento. Geralmente, são disponíveis instruções para transferência de dados, operações diádicas e monádicas, incluindo operações aritméticas e booleanas, desvios, chamadas de procedimento, loops e algumas vezes para E/S. As instruções tipicamente copiam uma palavra de memória para um registrador (ou vice-versa), somam, subtraem, multiplicam ou dividem os conteúdos de dois registradores ou os conteúdos de um registrador e de uma posição de memória, ou comparam dois itens localizados em registradores ou na memória. Não é comum um computador ter mais de 100 instruções em seu repertório. O fluxo de controle no nível 2 é conseguido utilizando diversas primitivas, incluindo desvios, chamadas de procedimento, chamadas de co-rotinas, traps e interrupções. Os desvios são usados para terminar uma sequência de instruções e iniciar uma nova. Os procedimentos são utilizados como um mecanismo de abstração para permitir que uma parte do programa seja isolada como uma unidade e chamada de diversos lugares. Co-rotinas permitem que duas threads de controle executem simultaneamente. Traps são utilizadas para sinalizar situações excepcionais, tais como transbordo (overflow) aritmético. Finalmente, as interrupções permitem E/S acontecer em paralelo com a computação principal, com a CPU recebendo um sinal tão logo a E/S tenha sido completada. Disciplina: Projeto Lógico de Computadores (5º/6º Sem - 2014). Livro: Andrew S. Tanenbaum Página 7 de 7