Introdução à Organização de Computadores e Linguagens de Montagem Ricardo Anido © Draft date 10 de Março de 2011 Capítulo 2 Organização de básica de computadores e linguagens de montagem Agora que já sabemos um pouco sobre codificação da informação na memória, vamos examinar o funcionamento básico de um computador. A Figura 2.1 mostra o esquema simplificado de um computador típico, ilustrando a interligação entre três componentes: memória, processador e um bloco representando dispositivos de entrada e saída, tais como monitor de vídeo, teclado, impressora, discos, etc. Interligando os três componentes vemos os barramentos. Um barramento é simplesmente um conjunto de fios, cada um dos quais a cada momento pode ou não ter corrente elétrica fluindo. A presença ou ausência de corrente elétrica são usadas para distinguir entre dois estados, de forma que a cada momento um barramento carrega informação que pode ser interpretada da mesma forma que uma informação na memória. Os barramentos são usados para transferir informações entre dois componentes. No momento da transferência, o componente que possui a informação coloca as tensões adequadas nos fios do barramento, e o componente destino faz a leitura das tensões nos fios e desta forma adquire a informação. Note que na Figura 2.1 são usados três barramentos: um de dados, um de endereço e um de controle. Note também que os barramentos de dados e controle são desenhados com setas nas duas extremidades, indicando que 5 6 CAPÍTULO 2. ORGANIZAÇÃO BÁSICA DE COMPUTADORES Barramento de dados Processador Barramento de controle Memória Barramento de endereço Entrada e Saída Figura 2.1: Esquema simplificado de um computador os dados podem trafegar nas duas direções, enquanto que no barramento de endereços a informação trafega em uma única direção. O barramento de endereços contém tantos fios (bits) quantos forem necessários para endereçar todas as palavras da memória. O número de fios (bits) do barramento de dados é igual ao tamanho de cada palavra na memória. Assim, para comunicação com uma memória organizada como 2 MB palavras de 32 bits (total de 8 MB), o processador deve dispor de um barramento de dados com 32 fios (largura da palavra), e um barramento de endereço com pelo menos 21 fios (para endereçar 2 MB palavras). A comunicação entre o processador e a memória é feita da seguinte maneira. Quando o processador necessita de um dado na memória, ele especifica pelo barramento de endereços qual o endereço de memória da palavra que contém o dado. Feito isto, o processador especifica pelo barramento de controle que a operação é de leitura em memória (note que isto pode ser realizado com apenas um fio do barramento). A memória coloca então o valor da palavra especificada no barramento de dados, que é finalmente lido pelo processador. A operação de escrita na memória é similar. O processador coloca no barramento de endereços o endereço da palavra que deve ser modificada, e no barramento de dados o valor a ser escrito. O processador indica pelo barra- 2.1. FUNCIONAMENTO DO PROCESSADOR 7 mento de controle que a operação é de escrita, e a operação é executada pela memória. 2.1 Funcionamento do processador Vamos examinar mais detalhadamente o funcionamento do computador através da introdução de um processador didático, o Faíska. A Figura 2.2 mostra alguns dos componentes internos do Faíska. Processador B. de dados r0 ip r1 ir B. de controle r2 op1 op2 ALU r15 B. de endereço result Figura 2.2: Esquema simplificado do processador Faíska. Os componentes mostrados no interior do processador (r0, r1, r2, . . ., r15, ip e ir) são chamados registradores. Um registrador é basicamente uma palavra de memória interna ao processador. A diferença é que acesso a um registrador é muito mais rápido que o acesso a qualquer palavra de memória externa ao processador. O Faíska possui vários registradores. Os registradores r0 a r15 são de propósito geral, e podem ser usados para manipular dados do usuário, armazenar valores intermediários, etc. Todos os registradores do Faíska são de 32 bits. Na Figura 2.2 são mostrados ainda dois registradores especiais. O registrador de instruções ir (em inglês, instruction register) armazena o código da instrução que está sendo executada, enquanto que o registrador apontador de instruções ip (em inglês, instruction pointer) contém o endereço da próxima instrução a ser executada. O usuário não tem acesso direto aos registradores especiais ip e ir; eles são mostrados nas figuras para facilitar o entendimento do funcionamento do processador. O processador conta ainda com uma Unidade Aritmética e Lógica (ALU, do nome em inglês), capaz de realizar, como o nome indica, operações aritméticas (por exemplo adição) e lógicas (por exemplo ou-exclusivo). Os operandos 8 CAPÍTULO 2. ORGANIZAÇÃO BÁSICA DE COMPUTADORES das instruções aritméticas e lógicas podem ser registradores ou uma palavra de memória. O processador funciona em passos – cada instrução é composta por um número variável de passos, dependendo da complexidade da instrução. Os passos básicos de uma instrução são: • busca de instrução na memória, • busca de operando na memória, • execução e • armazenamento do resultado na memória. No passo busca de instrução na memória o processador executa uma operação de leitura à memória, no endereço indicado pelo valor corrente do registrador ip, para ler o código da instrução que deve ser executada. O valor do registrador ip é incrementado para apontar para a próxima palavra na memória. A palavra lida é colocada no registrador ir, e os próximos três passos, que podem ou não ocorrer, dependem da instrução lida. No passo busca de operando na memória, se necessário para a instrução, mais um acesso à memória é realizado, para buscar um operando para a instrução. No passo execução o processador utiliza a sua unidade de processamento lógico e aritmético para realizar a operação especificada (por exemplo, adição ou subtração). E finalmente, no passo armazenamento do resultado na memória, se necessário para a instrução, é feito um acesso de escrita à memória para armazenar o resultado da instrução. Vamos ilustrar o funcionamento do processador através da execução de uma instrução típica. Suponha que o código 11000102h represente a instrução “adicione o valor do registrador r1 ao valor do registrador r2 e coloque o resultado em r1”, e que em um dado momento a memória e os registradores contenham valores mostrados abaixo: 2.1. FUNCIONAMENTO DO PROCESSADOR 9 Processador Memória r1 80 00 11 11 r2 80 00 00 ab ip 00 00 02 00 208h 02 00 03 00 204h 11 00 01 02 200h Os passos da execução dessa instrução são: 1. Busca de instrução na memória: o processador lê a palavra de memória apontada por ip (endereço 0200h) e coloca o valor lido (11000102h) em ir (não mostrado na figura). O registrador ip é avançado para a próxima palavra: ip passa a valer 0204h. 2. Busca de operandos na memória: nada a fazer no caso desta instrução. 3. Execução: o processador executa a instrução correspondente ao código 11000102h: o processador aciona a Unidade Aritmética e Lógica tendo como entrada os valores dos registradores r1 e r2 e efetua a soma. O resultado é colocado no registrador r1 4. Armazenamento de resultado na memória: nada a fazer no caso desta instrução. A configuração da memória (que neste caso não é alterada) e do processador (com os registradores que foram alterados destacados em cinza) após a execução desse passo é: Processador Memória r1 00 00 11 bc r2 80 00 00 ab ip 00 00 02 04 208h 02 00 03 00 204h 11 00 01 02 200h 10 CAPÍTULO 2. ORGANIZAÇÃO BÁSICA DE COMPUTADORES Note que, como o registrador ip é incrementado no passo busca de instrução, a próxima execução a ser executada é a de código 02000300h, presente no endereço de memória 204h. A codificação de instruções no Faíska é bastante simples. Toda instrução tem uma ou duas palavras. A primeira palavra é sempre dividida em quatro campos de um byte cada: 31 24 instr 16 imd 8 dest 0 fonte O campo instr representa o tipo da instrução a ser executada; no exemplo anterior mostramos que este campo deve ter o valor 11h para instruções de soma. O campo imed terá sua utilização explicada mais adiante. Os campos fonte e destino codificam os registradores usados como fonte e/ou destino a serem utilizados na instrução. Os registradores são identificados nestes campos pelos seus números, ou seja, o registrador r0 tem como representação o valor 0, e o registrador r1 tem como representação o valor 1. A utilização dos campos fonte e destino dependem da instrução em questão; algumas utilizam apenas o campo destino, outras apenas o campo fonte e algumas utilizam ambos os campos. Podemos agora entender a codificação da instrução “adicione o valor do registrador r1 ao valor do registrador r2 e coloque o resultado em r1”, usada como exemplo anteriormente: instr imd dest fonte 11h - 01h 02h Note como os campos fonte e destino foram utilizados. A instrução de soma requer dois operandos fonte; por convenção, no Faíska neste caso o registrador destino é utilizado também como um dos operandos. Note também que os campos que não são significativos para uma dada instrução (neste caso, imd) podem ter qualquer valor, mas normalmente são codificados com zeros. A codificação do Faíska é exageradamente simples. Em processadores reais a codificação é bem mais complexa, devido a fatores internos da arquitetura de cada processador (como por exemplo a maneira pela qual é feita a decodificação das instruções) e devido também ao fato de que existe um compromisso entre simplicidade e compactabilidade do código. 2.1. FUNCIONAMENTO DO PROCESSADOR 11 2.1.1 Modos de endereçamento Na seção anterior nós vimos um exemplo de instrução que utiliza apenas registradores como operandos. Obviamente, os processadores possuem também instruções de transferência de dados entre memória e registradores. Instruções de leitura de memória copiam (“carregam”) o valor de uma posição de memória em um registrador, e instruções de escrita em memória copiam o valor de um registrador em uma posição de memória. Em instruções que fazem acesso à memória é necessário indicar qual a palavra de memória que deve ser utilizada. Ou, mais precisamente, é necessário especificar como deve ser calculado o endereço da palavra de memória a ser utilizada (chamado endereço efetivo do operando). Existem diversas formas possíveis de cálculo do endereço efetivo. Essas diferentes formas dão origem a diferentes modos (ou tipos) de endereçamento; cada instrução do processador define precisamente o modo de endereçamento utilizado. Nesta seção vamos apresentar apenas um modo de endereçamento, o chamado endereçamento imediato. Outros modos de endereçamento serão vistos mais adiante. 2.1.2 Endereçamento direto Um dos modo mais simples de endereçamento é o chamado endereçamento direto, no qual o endereço de uma palavra de memória (endereço do operando) faz parte da codificação da instrução. O endereçamento direto pode ser utilizado por exemplo para copiar em um registrador o conteúdo de uma palavra de memória. No Faíska, a instrução que copia o conteúdo de uma palavra de memória em um registrador é “carrega registrador com endereçamento direto”, que é codificada utilizando-se duas palavras de memória. A primeira palavra especifica o tipo de operação (carrega registrador com endereçamento direto) e o registrador destino (para onde a palavra de memória deve ser copiada). A segunda palavra da instrução contém o endereço da palavra de memória cujo conteúdo deve ser carregado no registrador destino. Vamos examinar o funcionamento do processador na instrução “carrega registrador r10 com o conteúdo da palavra de memória de endereço 2000h”. A primeira palavra dessa instrução é 03000a00h e a segunda palavra 00002000h: 12 CAPÍTULO 2. ORGANIZAÇÃO BÁSICA DE COMPUTADORES instr imd dest fonte 03h - 0ah - palavra 1 palavra 2 00002000h Note que os campos imd e fonte não têm significado para essa instrução, e são preenchidos com zeros. Suponha a configuração de processador e memória mostrada na Figura 2.3. Memória Processador 02004h 22 22 22 22 r10 02000h 55 55 55 55 0fffch ip 00 00 02 04 Antes 00 00 20 00 00208h 03 00 0a 00 00204h Figura 2.3: Exemplo de instrução carrega registrador r10 com o conteúdo da palavra de memória 2000h. A sequência de operações realizadas pelo processador a partir dessa configuração será: 1. Busca de instrução na memória: o processador faz um acesso à memória para ler a palavra de memória apontada por ip (endereço 0204h) e coloca o valor lido (02000000h) no registrador ir. O registrador ip é avançado do número de bytes lidos: ip passa a valer 0208h. Note que neste momento o registrador ip aponta para a palavra de memória que contém o endereço do valor a ser carregado. 2. Busca de operando na memória: o processador faz um acesso à memória para ler a palavra de memória apontada por ip; o valor lido (00002000h) é armazenado em um registrador interno do processador (não mostrado). O processador faz um outro acesso à memória, com o 2.1. FUNCIONAMENTO DO PROCESSADOR 13 endereço lido anteriormente (00002000h), para finalmente ler o valor a ser carregado, que é então armazenado no registrador destino r10. O registrador ip é avançado para apontar para a próxima instrução, passando a valer 0210h. 3. Execução: nada a fazer no caso desta instrução. 4. Armazenamento de resultado na memória: nada a fazer no caso desta instrução. A configuração final da memória e do processador é mostrada na Figura 2.4, com os registradores que tiveram seus valores alterados destacados em cinza. Memória Processador 02004h 22 22 22 22 r10 02000h 22 22 22 22 0fffch ip 00 00 02 10 Depois 00 00 20 00 00208h 03 00 0a 00 00204h Figura 2.4: Exemplo de instrução carrega registrador r10 com endereçamento direto (após execução). Note que também neste caso ao término da execução da instrução o registrador ip aponta para a próxima instrução a ser executada. 2.1.3 Um pequeno programa Com a informação de como é a codificação das instruções no processador, vamos escrever um “programa” bastante simples. 14 CAPÍTULO 2. ORGANIZAÇÃO BÁSICA DE COMPUTADORES Exemplo 1 Vamos escrever um trecho de programa que calcule a soma dos valores contidos nas palavras de endereço 2000h e 2004h e armazene o resultado no registrador r1. 03000200 00002000 03000100 00002004 11000102 carrega r2 com na palavra com carrega r1 com na palavra com soma r1 com r2 valor contido este endereço valor contido este endereço e coloca o resultado em r1 Colocando este trecho de programa em qualquer posição da memória, e fazendo com que o registrador ip aponte para o endereço inicial do trecho, a sequência de instruções especificada será executada. Por exemplo, se a configuração inicial da memória e processador for a mostrada na Figura 2.5, ao final da execução do trecho a configuração da memória e processador será a mostrada na Figura 2.6 com os registradores que tiveram seus valores alterados destacados em cinza. Processador r1 aa aa aa aa r2 bb bb bb bb Memória 33 33 33 33 02004h 22 22 22 22 02000h 0fffch ip 00 00 01 00 Antes 11 00 01 02 00110h 00 00 20 04 0010ch 03 00 01 00 00108h 00 00 20 00 00104h 03 00 02 00 00100h Figura 2.5: Memória e processador antes da execução do trecho do exemplo 1. 2.2. INTRODUÇÃO A LINGUAGENS DE MONTAGEM Processador r1 55 55 55 55 r2 22 22 22 22 15 Memória 33 33 33 33 02004h 22 22 22 22 02000h 0fffch ip 00 00 01 14 Depois 11 00 01 02 00110h 00 00 20 04 0010ch 03 00 01 00 00108h 00 00 20 00 00104h 03 00 02 00 00100h Figura 2.6: Memória e processador após a execução do trecho do exemplo 1. 2.2 Introdução a linguagens de montagem Já deve ter ficado claro que a programação feita manualmente, no nível das instruções do processador, no estilo do exemplo 1 seria uma tarefa árdua, pois teríamos que conhecer a codificação de cada instrução. Além disto, este método é muito sujeito a erros. Entretanto, este foi o método utilizado pelos primeiros programadores, no início da era dos computadores (cérebros eletrônicos!). Esses programadores pioneiros logo descobriram que necessitavam de uma maneira mais confiável de montar as sequências de instruções de um programa. Assim surgiram as primeiras linguagens de montagem (em inglês, assembly languages). Uma linguagem de montagem é basicamente uma linguagem de programação bastante simples; tão simples que o compilador de uma linguagem de montagem não é chamado propriamente de compilador, mas apenas de montador (em inglês, assembler). Assim como em linguagens de alto nível, um programa em uma linguagem de montagem é uma sequência de comandos. Mas, para facilitar a tarefa do montador, os comandos são simples (não há comandos estruturados como for ou if da linguagem C) e o formato de um pro- 16 CAPÍTULO 2. ORGANIZAÇÃO BÁSICA DE COMPUTADORES grama em linguagem de montagem é considerávelmente rígido (por exemplo, deve haver apenas um comando de linguagem de montagem em cada linha do programa). Os comandos da linguagem de montagem são identificados por palavras reservadas do montador, isto é, palavras que têm um significado fixo para o montador e não podem ter o seu significado redefinido pelo programador. Já vimos dois exemplos de palavras reservadas: set e add. Normalmente cada comando em linguagem de montagem é traduzido pelo montador em uma instrução de máquina. Operandos devem aparecer à direita do comando, e, se mais de um operando é necessário, eles devem ser separados por vírgulas. No Faíska usaremos a convenção de que o operando que será modificado pela instrução aparece mais à esquerda na lista de operandos. Um exemplo de comando em linguagem de montagem é ld r1,2004h Quando encontrada pelo montador, este comando em linguagem de montagem é traduzido na sequência de palavras binárias 03000100h 00002004h Um outro exemplo de comando, para outra instrução do processador que já vimos, é add r1,r2 Quando encontrada pelo montador, este comando em linguagem de montagem é traduzido na palavra 11000102h Para documentação do programa, a linguagem de montagem permite a inclusão de comentários. Um comentário é iniciado pelo caractere ‘;’ e se estende até o final da linha. Comentários e linhas em branco são desconsidera- 2.2. INTRODUÇÃO A LINGUAGENS DE MONTAGEM 17 dos pelo montador. A linguagem de montagem permite também a associação de um rótulo definido pelo programador a um endereço de memória. Rótulos devem aparecer obrigatóriamente no início de uma linha e são usados para definir pontos importantes em um programa (início de um procedimento, por exemplo), para definir variáveis, ou simplesmente para documentação. Cada linha de um programa em linguagem de montagem pode ter o seguinte formato, onde a notação [comp] indica que a presença do componente comp é opcional (a ordem de cada um dos componentes na linha, no entanto, é fixa): [rótulo:] [comando] [lista_de_operandos] [; comentário] onde rótulo é uma sequência de letras, dígitos ou o caractere ‘_’, que se inicia com uma letra, é um comando da linguagem de montagem, lista de operandos é uma lista de nomes ou expressões separadas por vírgulas. Exemplo 2 Escrever um trecho de programa, em linguagem de montagem, que calcule a soma dos valores contidos nas palavras de endereço 2000h e 2004h e armazene o resultado no registrador r1. 1 2 3 4 inicio: ld ld add r2,2000h r1,2004h r1,r2 ; carrega primeiro termo da soma ; carrega segundo termo ; e soma os dois termos Note que o montador traduz este trecho de programa em linguagem de montagem exatamente na sequência de instruções de máquina do Exemplo 1. 2.2.1 Diretivas do Montador Além das instruções uma linguagem de montagem inclui também diretivas. Diretivas da linguagem de montagem, ao contrário das instruções, não são traduzidas em código de máquina. Elas servem para transmitir informações adicionais ao montador, como por exemplo definir uma constante que será 18 CAPÍTULO 2. ORGANIZAÇÃO BÁSICA DE COMPUTADORES utiliza em vários pontos do programa. A linguagem de montagem do Faíska inclui diretivas para definir constantes, reservar espaço na memória para armazenamento de variáveis, indicar o endereço inicial de montagem e indicar o final do programa. Diretiva de definição de constantes Constantes podem ser definidas em qualquer parte do programa em linguagem de montagem, mas usualmente as definimos no início do programa, para documentação e facilidade de leitura. Uma constante é introduzida pela diretiva EQU, cuja sintaxe é nome equ valor Após a diretiva, toda ocorrência de nome é substituída por valor. Exemplo 3 1 2 3 4 5 6 ; definição de constantes VERDADEIRO FALSO MAXVAL MINVAL equ equ equ equ 0ffh 0 1000 MAXVAL/2 Diretiva de reserva de espaço em memória Podemos reservar espaço na memória do Faíska de duas maneiras: inicializando o espaço com um valor conhecido, ou deixando o espaço não inicializado (e portanto contendo um valor desconhecido). Essas duas formas de reservar espaço na memória são similares às formas de definição de variáveis na linguagem C: Exemplo 4 2.2. INTRODUÇÃO A LINGUAGENS DE MONTAGEM 1 2 3 4 5 6 19 // exemplo de declaração de variáveis não inicializadas em C: int a,b[1000]; // exemplo de declaração de variáveis inicializadas em C: int c=-1, d[4]={1,2,3,4}; Para simplesmente reservar espaço em memória usamos a diretiva ds, que tem o formato geral [rótulo:] ds [expressão_inteira] [; comentário] Esta diretiva reserva expressão_inteira bytes de memória (os valores iniciais dos bytes são indefinidos). Exemplo 5 1 2 3 4 5 6 7 8 9 ; exemplo de reserva de espaço na memória para variáveis ; vamos primeiro definir uma constante TAMANHO equ 4 ; agora reservamos espaço para algumas variáveis contador: ds 1 x: ds TAMANHO final: ds 4 Diretivas de reserva e inicialização de espaço em memória Além de reservar espaço na memória para definição por exemplo de variáveis, é possível também atribuir valores iniciais a posições de memória. Esses valores são carregados na memória do computador juntamente com o código executável do programa. Assim, quando o programa inicia sua execução, as variáveis definidas por essa diretiva já contêm os valores especificados (por isso dizemos que é uma diretiva de inicialização de memória). Para reservar e inicializar espaço em memória, usamos as diretivas db (para reservar e inicializar bytes) e dw (para reservar e inicializar palavras). 20 CAPÍTULO 2. ORGANIZAÇÃO BÁSICA DE COMPUTADORES Reserva e inicialização de bytes A diretiva db reserva e inicializa bytes: [rótulo:] db [lista_de_valores] [; comentário] A lista_de_valores é uma lista, separada por vírgulas, onde cada elemento pode ser uma expressão inteira, um caracter, ou uma sequência de caracteres entre aspas simples. O montador computa o valor de cada elemento da lista e monta os valores resultantes nos bytes de memória, em sequência, a partir do endereço associado a rótulo. Note que o processador, durante a execução do programa montado, não executa nenhuma operação para que esses bytes de memória sejam inicializados. É o montador que coloca, no código executável, informação para que essas posições de memória estejam com esses valores no início da execução do programa. Exemplo 6 1 2 3 4 5 6 7 8 9 10 ; exemplo de reserva de espaço na memória para variáveis ; vamos primeiro definir uma constante MAXVAL equ 256 ; agora reservamos espaço para algumas variáveis contador: db 1 estado: db 0 x: db MAXVAL - 1 letra: db ’a’ num: db -1 O espaço de memória reservado pela sequência de diretivas do Exemplo 6 é mostrado na Figura 2.7, onde a memória é apresentada no formato de um vetor de bytes. Para facilitar a definição de sequências de bytes, os valores podem ser separados por vírgulas. Exemplo 7 2.2. INTRODUÇÃO A LINGUAGENS DE MONTAGEM 21 Memória num ff letra 61 x ff estado 00 contador 01 endereços crescentes Figura 2.7: Memória reservada pelo Exemplo 6. 1 2 3 4 5 6 7 ; exemplo de reserva de espaço na memória para variáveis ; vamos primeiro definir uma constante MAXVAL equ 256 ; agora reservamos espaço para algumas variáveis dir: db 128, 0feh, ’a’, MAXVAL-1 esq: db 1, 0, 33 O espaço de memória reservado pela sequência de diretivas do Exemplo 7 é mostrado na Figura 2.8. Memória 21 00 01 ff endereços crescentes 61 fe 80 Figura 2.8: Memória reservada pelo Exemplo 7. Uma outra abreviação permitida é a utilização de aspas simples para a definição de cadeias de caracteres. Ou seja, a diretiva 22 1 CAPÍTULO 2. ORGANIZAÇÃO BÁSICA DE COMPUTADORES Cadeia: db biriba, 0 db ‘b’,‘i’, ‘r’, ‘i’, ‘b’, ‘a’, 0 é equivalente a 1 Cadeia: Reserva e inicialização de palavras A diretiva de reserva de espaço de memória em palavras dw é similar à diretiva db, mas uma palavra inteira (quatro bytes) é reservada e um valor é atribuído a essa palavra. O formato geral da diretiva dw é [rótulo:] dw [lista_de_valores] [; comentário] onde lista_de_valores é uma lista, separada por vírgulas, onde cada elemento pode ser uma constante numérica, um caracter, ou uma sequência de caracteres entre aspas simples. Exemplo 8 1 2 3 4 5 6 7 8 9 10 ; exemplo de uso da diretiva dw ; primeiro definimos algumas constantes ALTO equ 32000h BAIXO equ 2000h var_x: var_y: ultimo: dw dw dw dw ALTO-1 BAIXO*2 31,32 -1 O espaço de memória reservado pela sequência de diretivas do Exemplo 8 é mostrado na Figura 2.9, onde a memória é apresentada no formato de um vetor de palavras. 2.2. INTRODUÇÃO A LINGUAGENS DE MONTAGEM 23 Memória ultimo ff ff ff ff 00 00 00 20 00 00 00 1f var_y 00 00 40 00 var_x 00 03 1f ff endereços crescentes Figura 2.9: Memória reservada pelo Exemplo 8. Note que programas escritos em linguagens de alto nível também oferecem maneiras de inicializar variáveis (ou seja, atribuir valores iniciais a variáveis). Em C, por exemplo, Diretiva para indicar endereço de montagem Durante a montagem, o montador mantém um contador de montagem que indica o endereço corrente de montagem, isto é, o endereço da próxima palavra a ser montada. Durante o processo de montagem, esse contador é incrementado do número de palavras da instrução a cada instrução montada. O mesmo acontece quando o montador reserva espaço na memória: a cada espaço reservado o contador é incrementado do número de bytes ou palavras correspondente ao espaço. Para alterar o valor do contador de montagem, utilizamos a diretiva org, que tem o formato org [expressão_inteira] [; comentário] Ao encontrar esta diretiva, o montador toma expressão_inteira como o novo valor do contador de montagem. Exemplo 9 24 1 2 3 4 5 6 7 8 9 10 11 12 13 14 CAPÍTULO 2. ORGANIZAÇÃO BÁSICA DE COMPUTADORES ; exemplo de reserva de espaço na memória para variáveis ; vamos primeiro definir algumas constantes MAXVAL TAM_BLOCO NUM_BLOCO equ equ equ 256 16 64 ; agora definimos o endereço inicial de montagem do bloco org TAM_BLOCO * (NUM_BLOCO - 1) bloco1: dw 0,1,2 ; este outro bloco deve começar em outro endereço org 200h bloco2: db 10,11,12,13,14,15 O espaço de memória reservado pela sequência de diretivas do Exemplo 9 é mostrado na Figura 2.10. Memória 00 00 00 03 3f8h 00 00 00 02 3f4h 00 00 00 01 3f0h 00 00 0f 0e 204h 0d 0c 0b 0a 200h Figura 2.10: Memória reservada pelo Exemplo 9. Diretiva para indicar final de montagem A diretiva end indica ao montador que o programa que está sendo montado chegou ao fim. end [; comentário] 2.3. COMO O MONTADOR FUNCIONA 25 Ao encontrar esta diretiva, o montador termina a montagem, ignorando qualquer linha subsequente. 2.3 Como o Montador funciona O montador traduz um arquivo texto, contendo o programa, como uma sequência de linhas, contendo comandos e diretivas em linguagem de montagem, para um arquivo binário, contendo dados e instruções em linguagem de máquina. Vamos ilustrar o funcionamento de um montador simples, de dois passos. Esse tipo de montador, a cada passo, lê do início ao fim o programa dado como entrada. No primeiro passo o montador estabelece os valores de todos os rótulos que foram definidos no programa (isto é, os endereços associados a cada rótulo). No segundo passo o montador realmente produz o arquivo binário contendo as instruções e dados que compõe o programa. Como exemplo, vamos considerar o seguinte trecho de programa em linguagem de montagem: 26 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 CAPÍTULO 2. ORGANIZAÇÃO BÁSICA DE COMPUTADORES ; exemplo de funcionamento do montador ; vamos primeiro definir uma constante MINVAL equ 256 ; o endereço inicial de montagem é 0000h, vamos ; definir uma variável var1: dw -1 ; suponha que o programa propriamente deva se ; iniciar no endereço 1000h, por isso alteramos ; o endereço de montagem org 1000h inicio: ld r5,var1 ld r6,var2 add r5,r6 ; o programa continua com outros comandos não mostrados ; ... ; o endereço de montagem é alterado novamente org 2000h ; vamos definir outra variável var2: dw MINVAL*2 end O montador mantém uma Tabela de Rótulos, que associa um endereço de memória a cada rótulo encontrado no programa, uma Tabela de Constantes, que associa uma cadeia de caracteres a cada nome de constante definida com a diretiva equ e uma Tabela de Comandos, que o montador utiliza para consultar as informações sobre cada comando (basicamente tamanho em bytes e formato da instrução do processador correspondente ao comando). A variável interna PontoDeMontagem armazena o endereço corrente de montagem, que inicialmente é zero (início da memória). O montador lê e processa cada linha do programa individualmente. Em caso de erro, o montador interrompe o processamento, emitindo uma mensagem com a causa do erro. 2.3. COMO O MONTADOR FUNCIONA 27 Vamos seguir linha a linha a execução do montador. Linhas contendo apenas comentários (como a linha 1), ou linhas vazias (como a linha 5) são ignoradas pelo montador e não são comentadas. Passo 1 Neste passo, o montador irá calcular o valor correto de endereço a ser associado a cada rótulo do programa. Linha 4: definição de constante, o montador inicialmente verifica se o nome da constante (MINVAL) já está definido na Tabela de Constantes. Se já está é um erro, pois significa que a constante já foi definida anteriormente, e nesse caso o montador interrompre o processamento. Como MINVAL não foi definido anteriormente, o montador cria uma entrada na Tabela de Constantes, associando a palavra MINVAL à cadeia de caracteres “256” (note que não associa ao valor inteiro 256). Linha 8: o montador encontra o rótulo var1 e inicialmente verifica se já existe uma entrada na Tabela de Rótulos com esse nome (se existe é um erro, o mesmo rótulo está já foi utilizado anteriormente no programa). Como não encontra um rótulo com esse nome definido, cria uma entrada na Tabela de Rótulos, associando var1 ao endereço 0 (valor corrente de PontoDeMontagem). O montador encontra a diretiva dw (reserva e inicialização de palavras), com um único operando (o valor −1), e portanto o montador avança PontoDeMontagem de quatro bytes, deixando reservado na memória o espaço para o valor declarado. Linha 15: ao encontrar esta diretiva, o montador altera PontoDeMontagem para o valor 1000h. Linha 16: o montador encontra o rótulo inicio, determina que não foi ainda defindo, e cria uma entrada na Tabela de Rótulos, associando o rótulo encontrado ao valor 1000h (valor corrente de PontoDeMontagem). Linha 17: o montador encontra o comando ld e consulta a Tabela de Comandos para determinar quantos bytes a instrução correspondente utiliza. Como vimos, essa instrução utiliza duas palavras e portanto o montador atualiza PontoDeMontagem para 1008h, deixando reservado na memória o espaço para essa instrução. O montador encontra var1 como operando, consulta a Tabela de Rótulos e, como o rótulo já está definido, não há nada mais a fazer. 28 CAPÍTULO 2. ORGANIZAÇÃO BÁSICA DE COMPUTADORES Linha 18: o montador encontra o comando ld e consulta a Tabela de Comandos para determinar quantos bytes a instrução correspondente utiliza, e atualiza PontoDeMontagem para 1010h. O montador encontra var2 como operando, consulta a Tabela de Rótulos e, como um rótulo com esse nome não está definido, cria uma entrada para o rótulo, marcando-o como ainda não definido. Linha 19: o montador encontra o comando add e consulta a Tabela de Comandos para determinar quantos bytes a instrução correspondente utiliza. Como vimos, essa instrução utiliza uma palavra e portanto o montador atualiza PontoDeMontagem para 1014h. Linha 24: ao encontrar esta diretiva, o montador altera PontoDeMontagem para o valor 2000h. Linha 28: o montador encontra o rótulo var2 e inicialmente verifica se já existe uma entrada na Tabela de Rótulos com esse nome (se existe e o rótulo já está associado a um endereço de memória é um erro: o mesmo rótulo já foi utilizado e definido anteriormente no programa). Como encontra o rótulo na Tabela de Rótulos, mas este está marcado como indefinido, atualiza a Tabela de Rótulos, associandovar2 ao endereço 2000h (valor corrente de PontoDeMontagem). O montador encontra a diretiva dw (reserva e inicialização de palavras), com um único operando (o resultado da expressão MINVAL*2), e portanto o montador avança PontoDeMontagem de quatro bytes. Linha 30: o montador encontra a diretiva end marcando o final da execução do passo 1. O montador então verifica se todos os rótulos presentes na Tabela de Rótulos estão definidos (caso contrário é um erro, pois significa que o operando de algum comando ficou indefinido). Passo 2 Neste passo, o montador irá produzir o arquivo binário correspondente ao programa. O arquivo binário deve conter informações que permitam que, quando carregado na memória do computador, o programa seja corretamente executado. Não entraremos aqui em detalhes quanto ao formato do arquivo binário, informando apenas quais valores devem ser armazenados em quais endereços. No início do passo 2 a variável interna PontoDeMontagem é reinicializada para zero. 2.3. COMO O MONTADOR FUNCIONA 29 Linha 4: definição de constante, tratada apenas no passo 1, ignorada no passo 2. Linha 8: o montador encontra o rótulo var1, que é ignorado no passo 2. O montador encontra a diretiva dw (reserva e inicialização de palavras), com um único operando (o valor −1), e portanto escreve no arquivo de saída uma palavra com o valor −1 (ffffffffh) no endereço corrente de PontoDeMontagem (0h). O montador então avança PontoDeMontagem de quatro bytes. O arquivo binário contém nesse momento Endereço (em hexa) 00000000 Valor (em hexa) ffffffff Linha 15: ao encontrar esta diretiva, o montador altera PontoDeMontagem para o valor 1000h. Linha 16: o montador encontra o rótulo inicio, que é ignorado neste passo. Linha 17: o montador encontra o comando ld e consulta a Tabela de Comandos para determinar a codificação da instrução correspondente. Como vimos, essa instrução utiliza duas palavras. A primeira palavra da instrução contém o código da instrução (03h) no quarto byte (o mais significativo). O montador encontra o registrador destino (r5) como primeiro operando do comando e monta o valor correspondente (05h) no segundo byte da primeira palavra. O montador escreve no arquivo binário o valor 03000500h no endereço corrente de montagem (1000h) e atualiza PontoDeMontagem para 1004h. A segunda palavra da instrução deve conter o endereço do operando; o montador encontra var1 como segundo operando do comando, consulta a Tabela de Rótulos, e escreve, no endereço corrente de montagem (1004h) o valor associado ao rótulo var1 (0h). Nesse momento, o arquivo de saída contém Endereço (em hexa) 00000000 00001000 00001004 Valor (em hexa) ffffffff 03000500 00000000 Linha 18: o montador encontra o comando ld e consulta a Tabela de Comandos para determinar a codificação da instrução correspondente, e determina 30 CAPÍTULO 2. ORGANIZAÇÃO BÁSICA DE COMPUTADORES que a instrução utiliza duas palavras. A primeira palavra da instrução contém o código da instrução (03h) no quarto byte (o mais significativo). O montador encontra o registrador destino (r6) como primeiro operando do comando e monta o valor correspondente (06h) no segundo byte da primeira palavra. O montador escreve no arquivo binário o valor 03000600h no endereço corrente de montagem (1008h) e atualiza PontoDeMontagem para 100ch. A segunda palavra da instrução deve conter o endereço do operando; o montador encontra var2 como segundo operando do comando, consulta a Tabela de Rótulos, e escreve, no endereço corrente de montagem (100ch) o valor associado ao rótulo var2 (2000h). O montador avança PontoDeMontagem para 1010h. Nesse momento, o arquivo de saída contém Endereço (em hexa) 00000000 00001000 00001004 00001008 0000100c Valor (em hexa) ffffffff 03000500 00000000 03000600 00002000 Linha 19: o montador encontra o comando add e consulta a Tabela de Comandos para determinar a codificação da instrução correspondente. Como vimos, essa instrução utiliza uma palavra, com o código 11h no quarto byte (o mais significativo). O montador encontra o registrador destino (r5) e monta o seu valor (05h) no segundo byte da instrução; o montador encontra o registrador fonte (r6) e monta o seu valor (06h) no primeiro byte (o menos significativo) da instrução. Finalmente, o montador escreve a palavra correspondente à instrução (11000506h) no endereço corrente de montagem (1010h) e atualiza PontoDeMontagem para 1014h. Nesse momento, o arquivo de saída contém Endereço (em hexa) 00000000 00001000 00001004 00001008 0000100c 00001010 Valor (em hexa) ffffffff 03000500 00000000 03000600 00002000 11000506 2.3. COMO O MONTADOR FUNCIONA 31 Linha 24: ao encontrar esta diretiva, o montador altera PontoDeMontagem para o valor 2000h. Linha 28: o montador encontra o rótulo var2, que é ignorado no passo 2. O montador encontra a diretiva dw (reserva e inicialização de palavras), com um único operando (a expressão MINVAL*2). Consultando a Tabela de Rótulos o montador determina que MINVAL não é um rótulo, devendo então ser uma constante. O montador consulta a Tabela de Constantes, determina que MINVAL foi definido como 256, substitui MINVAL por 256 na expressão, calcula o resultado da expressão e escreve no arquivo de saída uma palavra com o valor do resultado (200h) no endereço corrente de montagem, definido por PontoDeMontagem (2000h). O montador então avança PontoDeMontagem de quatro bytes. O arquivo binário contém nesse momento Endereço (em hexa) 00000000 00001000 00001004 00001008 0000100c 00001010 00002000 Valor (em hexa) ffffffff 03000500 00000000 03000600 00002000 11000506 00000200 Linha 30: o montador encontra a diretiva end marcando o final da execução do passo 2 e do processo de montagem.