Buscar

Programacao de sistemas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 3, do total de 219 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 6, do total de 219 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 9, do total de 219 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Prévia do material em texto

Programação de Sistemas
Sumário
I Introdução 1
1 Conceitos Básicos 2
1.1 Programas e Processos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Software de sistema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Desenvolvimento de Software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
II Montadores e Carregadores 11
2 Princípios de Montadores 12
2.1 Definições . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.2 Instruções assembly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.3 Pseudo-Instruções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.4 Macromontadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.5 Aspectos genéricos de projeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3 Estruturas de dados do montador 27
3.1 Tabelas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.2 Busca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.3 Ordenação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.4 Tabelas hash . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.5 Listas ligadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4 Montagem e Carregamento Absolutos 41
4.1 Estrutura de programa assembly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
4.2 Processadores de macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
4.3 Montadores em dois passos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.4 Montagem e carregamento assemble and go . . . . . . . . . . . . . . . . . . . . . . . . . 48
4.5 Carregamento absoluto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5 Relocação e Ligação 50
5.1 Estruturas de dados adicionais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
5.2 Carregador de ligação direta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
5.3 Ligadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
5.4 Bibliotecas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
5.5 Carregamento e Ligação Dinâmicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
i
Programação de Sistemas
Ivan L. M. Ricarte
Sumário
Sumário
III Compiladores 62
6 Princípios de Compiladores 63
6.1 Motivação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
7 Gramáticas e Reconhecimento de Sentenças 66
7.1 Terminologia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
7.2 Princípios de gramáticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
7.3 Definição formal de gramáticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
7.4 Tipos de gramáticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
7.5 Expressões regulares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
7.6 Reconhecimento de sentenças . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
8 Análise Léxica 75
8.1 Reconhecimento de símbolos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
8.2 Analisadores léxicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
8.3 Geradores de analisadores léxicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
9 Análise Sintática 84
9.1 Gramáticas livres de contexto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
9.2 Técnicas de Análise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
9.3 Analisadores de deslocamento e redução . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
9.4 Analisadores LR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
9.5 Geradores de Analisadores Sintáticos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
10 Geração de Código e Otimização 98
10.1 Gerador de Códigos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
10.2 Otimização . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
IV Sistemas Operacionais 105
11 Princípios 106
12 Escalonamento de Processos 108
12.1 Conceitos Básicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
12.2 Escalonamento de Processos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
12.3 Processos em Unix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
12.4 Processos em MS-DOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
13 Concorrência e Comunicação entre Processos 113
13.1 Concorrência . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
13.2 Comunicação Interprocessos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
14 Gerência de Memória 117
14.1 Gerência Sem Permuta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
14.2 Gerência com Permuta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
14.3 Memória Virtual . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
14.4 Algoritmos de Troca de Página . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
14.5 Gerência de Memória no UNIX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
14.6 Gerência de Memória em MS-DOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
c
2000 DCA/FEEC/UNICAMP ii
Programação de Sistemas
Ivan L. M. Ricarte
Sumário
Sumário
15 Manipulação de Arquivos 130
15.1 Interface do Sistema de Arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
15.2 Projeto do Sistema de Arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
15.3 O Sistema de Arquivos do Unix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
15.4 Sistemas de Arquivos do MS-DOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
16 Entrada e Saída 138
16.1 Princípios do Hardware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
16.2 Princípios do Software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
16.3 Discos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
16.4 E/S no Unix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
16.5 E/S em MS-DOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
V Apêndices 149
A Codificação binária 150
A.1 Representação Numérica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
A.2 Representação hexadecimal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
B Assembly do 68000 153
B.1 Organização dos Dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
B.2 Instruções assembly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
B.3 Modos de Endereçamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
B.4 Codificação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
C Programação C 167
C.1 Organização Básica de Programas C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
C.2 Tipos de Dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . 170
C.3 Declarações de Variáveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
C.4 Expressões . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
C.5 Controle do Fluxo de Execução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
C.6 Invocação de Funções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
C.7 Arranjos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
C.8 Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
C.9 Definição de estruturas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
C.10 Uniões . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
C.11 Enumerações . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
C.12 Definição de Nomes de Tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
C.13 Ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
C.14 Argumentos na Linha de Comando . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
C.15 Rotinas para Entrada e Saída de Dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
C.16 Rotinas para Interação com o Sistema Operacional . . . . . . . . . . . . . . . . . . . . . 200
C.17 O Pré-processador C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
C.18 Exemplo de Aplicativo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
C.19 Palavras reservadas em C e C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
C.20 Precedência de Operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
Referências 211
c
2000 DCA/FEEC/UNICAMP iii
Lista de Figuras
1.1 O ciclo de instrução von Neumann. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2 Etapas para execução de programa. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Processo de evolução do software. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.4 Estruturas elementares de dados. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.5 Mecanismos básicos da programação estruturada. . . . . . . . . . . . . . . . . . . . . . . 8
3.1 Estruturas auxiliares do montador. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.2 Efeito da inserção de nó em uma lista. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
7.1 Árvore sintática para expressão (x+ y)� x. . . . . . . . . . . . . . . . . . . . . . . . . . 73
8.1 Representação gráfica de uma transição. . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
8.2 Autômato finito: inteiros em octal, decimal ou hexadecimal. . . . . . . . . . . . . . . . . 77
9.1 Grafos sintáticos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
9.2 Esquema de uso do lex e yacc. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
9.3 Fluxo de Controle entre yylex() e yyparse() . . . . . . . . . . . . . . . . . . . . . . . . . 91
11.1 Papel do Sistema Operacional. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
12.1 Estados de um processo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
14.1 Organizações com partições fixas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
14.2 Organização com swapping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
14.3 Organização com mapa de bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
14.4 A posição e função da MMU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
14.5 Relação entre endereço virtual e endereço físico . . . . . . . . . . . . . . . . . . . . . . . 124
14.6 Fila de páginas candidatas a permuta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
15.1 Organização de blocos livres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
15.2 Estrutura de i-node . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
15.3 Estrutura da FAT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
16.1 Um modelo para conexão da CPU, memória, controladores e dispositivos de E/S . . . . . 139
16.2 Níveis do sistema de E/S e funções principais de cada nível . . . . . . . . . . . . . . . . . 144
16.3 Algoritmo de escalonamento menor seek primeiro (SSF) . . . . . . . . . . . . . . . . . . 145
16.4 Escalonamento de requisições no disco através do algoritmo do elevador . . . . . . . . . . 146
16.5 Esquema básico de E/S no Unix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
B.1 Modelo de registradores do 68000. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
iv
Programação de Sistemas
Ivan L. M. Ricarte
Lista de Figuras
Lista de Figuras
B.2 Organização da Memória. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
B.3 Formato de uma instrução. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
C.1 Estrutura básica de um algoritmo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
C.2 Algoritmo que faz nada. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
C.3 Seqüência em C. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
C.4 Seleção com if : : : else em C. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
C.5 Switch : : : case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
C.6 Switch : : : case sem break. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
C.7 Repetição em C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
C.8 Repetição em C com for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
c
2000 DCA/FEEC/UNICAMP v
Algoritmos
1 Busca linear em tabela. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2 Busca binária em tabela. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3 Ordenação de tabela pela busca do menor valor. . . . . . . . . . . . . . . . . . . . . . . . 31
4 Busca do menor valor em tabela. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
5 Troca de conteúdos de duas posições de uma tabela. . . . . . . . . . . . . . . . . . . . . . 32
6 Ordenação de tabela por quicksort. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
7 Ordenação de tabela por radixsort. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
8 Ordenação de tabela pela contagem de elementos. . . . . . . . . . . . . . . . . . . . . . . 34
9 Inserção de nó em lista ligada. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
10 Busca de nó com chave especificada em lista ligada. . . . . . . . . . . . . . . . . . . . . . 37
11 Remoção de nó especificado em lista ligada. . . . . . . . . . . . . . . . . . . . . . . . . . 37
12 Passo 1 do montador. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
13 Passo 2 do montador. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
14 Carregador absoluto. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
15 Primeiro passo do carregador de ligação direta . . . . . . . . . . . . . . . . . . . . . . . . 54
16 Segundo passo do carregador de ligação direta . . . . . . . . . . . . . . . . . . . . . . . . 55
17 Terceiro passo do carregador de ligação direta . . . . . . . . . . . . . . . . . . . . . . . . 56
18 Algoritmo para reconhecimento de símbolo. . . . . . . . . . . . .. . . . . . . . . . . . . 79
19 Contrução ascendente. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
20 Analisador sintático por deslocamento e redução. . . . . . . . . . . . . . . . . . . . . . . 87
vi
Prefácio
O material aqui apresentado foi desenvolvido a partir de uma necessidade real constatada nas disciplinas
EA876 (Introdução a Software de Sistema) e EA877 (Mini e microcomputadores: software) da Faculdade
de Engenharia Elétrica e de Computação da Universidade Estadual de Campinas (FEEC/UNICAMP). Seu
objetivo é oferecer um material de apoio ao estudo do aluno que apresente de forma unificada os diversos
tópicos apresentados nesse curso.
Essas disciplinas, oferecidas aos alunos de graduação dos cursos de Engenharia de Computação e de
Engenharia Elétrica, têm por meta oferecer uma visão geral sobre o funcionamento e utilização software
que permite que usuários executem seus programas em computadores. Para os alunos de Engenharia de
Computação, esse é um ponto de partida para temas que serão aprofundados em diversas outras disciplinas.
Para os alunos de Engenharia Elétrica, pode significar o único contato com esse tema, essencial para o uso
efetivo de computadores ao longo de sua vida profissional.
Uma das dificuldades no desenvolvimento desse material está em sua amplitude. Para oferecer essa
visão geral do software de sistema, agrupou-se em uma disciplina temas tão amplos como montadores,
compiladores e sistemas operacionais — cada um deles suficientemente complexo para o escopo de uma
ou uma série de disciplinas. Adequar o grau de profundidade da apresentação dos tópicos ao tempo e aos
objetivos do curso, sem tornar essa apresentação extremamente superficial, tem sido uma tarefa que vem
requerendo a constante revisão desse material. Nessa tarefa, a realimentação dos usuários do material —
alunos e instrutores das disciplinas citadas — tem sido um importante auxílio.
Para o desenvolvimento desse texto, algumas opções foram norteadas pela prática corrente na área ou
em outras disciplinas relacionadas da FEEC/UNICAMP. Nesta categoria inclui-se a opção pelos processa-
dores da família Motorola 68K para os exemplos que utilizam linguagem assembly. Na primeira categoria,
o destaque maior é o uso da linguagem C para os exemplos envolvendo linguagens de alto nível. C é a
linguagem de programação de sistemas por excelência, devendo ocupar esse papel por um longo tempo
ainda.
Com o objetivo de não tornar o corpo principal desse texto extremamente carregado ou monótono para
aqueles que detêm o conhecimento nessas duas linguagens, são oferecidos amplos apêndices que as des-
crevem num grau de profundidade não mais que necessário para a compreensão dos exemplos. Da mesma
forma, são apresentados em apêndices fundamentos da representação de variáveis em computadores, que
podem servir como revisão ou introdução ao tópico para aqueles que assim o necessitem.
Campinas, fevereiro de 2000
Ivan Luiz Marques Ricarte
vii
Parte I
Introdução
1
Capítulo 1
Conceitos Básicos
Índice
1.1 Programas e Processos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Software de sistema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Desenvolvimento de Software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.3.1 Projeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.3.2 Programação Estruturada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.3.3 Codificação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Nas últimas décadas, o computador deixou de ser uma ferramenta cara e exclusiva dos grandes centros
de pesquisas e passou a ser presença constante no cotidiano de todos. Apesar dessa quase onipresença,
para muitos ainda há uma aura de mistério no que se refere ao funcionamento e à capacidade funcional de
computadores.
Um computador tem essencialmente duas componentes, hardware e software. Hardware é o termo co-
letivo que representa todo o conjunto de circuitos que permite o funcionamento do computador. No entanto,
o que determina o que o computador faz é a segunda componente. Software é o termo coletivo expressando
o conjunto de programas que, juntamente com o hardware, permite a operação de computadores.
1.1 Programas e Processos
Um computador nada mais faz do que executar programas. Um programa é simplesmente uma seqüên-
cia de instruções definida por um programador. Assim, um programa pode ser comparado a uma receita
indicando os passos elementares que devem ser seguidos para desempenhar uma tarefa. Cada instrução
é executada no computador por seu principal componente, o processador ou CPU (de unidade central de
processamento).
Cada processador entende uma linguagem própria, que reflete as instruções básicas que ele pode exe-
cutar. O conjunto dessas instruções constitui a linguagem de máquina do processador. Cada instrução
da linguagem de máquina é representada por uma seqüência de bits distinta, que deve ser decodificada
pela unidade de controle da CPU para determinar que ações devem ser desempenhadas para a execução da
instrução.
Claramente, seria extremamente desconfortável se programadores tivessem que desenvolver programas
diretamente em linguagem de máquina. Programadores não trabalham diretamente com a representação
2
Programação de Sistemas
Ivan L. M. Ricarte
Conceitos Básicos
1.1. Programas e Processos
binária das instruções de um processador. Existe uma descrição simbólica para as instruções do processador
— a linguagem assembly do processador — que pode ser facilmente mapeada para sua linguagem de
máquina.
No entanto, mesmo assembly é raramente utilizada pela maior parte dos programadores, que trabalha
com linguagens de programação de alto nível. Em linguagens de alto nível, as instruções são expressas
usando palavras, ou termos, que se aproximam daquelas usadas na linguagem humana, de forma a facilitar
para os programadores a expressão e compreensão das tarefas a executar. Há diversas linguagens de alto
nível disponíveis para diversas máquinas distintas. Essa independência de um processador específico é
uma outra vantagem no desenvolvimento de programas em linguagens de alto nível em relação ao uso de
assembly.
Em geral, cada linguagem de alto nível teve uma motivação para ser desenvolvida. BASIC foi de-
senvolvida para ensinar princípios de programação; Pascal, para o ensino de programação estruturada;
FORTRAN, para programação científica; lisp e Prolog para aplicações em Inteligência Artificial; e a lin-
guagem C, para a programação de sistemas. O fato de uma linguagem ter sido desenvolvida com uma
aplicação em mente não significa que ela não seja adequada para outras aplicações. A linguagem C, junta-
mente com sua “sucessora” C++, é utilizada para um universo muito amplo de aplicações. Um dos atrativos
da linguagem C é sua flexibilidade: um programador C tem à sua disposição comandos que permitem
desenvolver programas com características de alto nível e ao mesmo tempo trabalhar em um nível muito
próximo da arquitetura da máquina, de forma a explorar os recursos disponíveis de forma mais eficiente.
Por este motivo, o número de aplicações desenvolvidas em C e C++ é grande e continua a crescer.
Uma vez que um programa tenha sido criado, sua representação em linguagem de máquina é armaze-
nada na unidade de memória do computador, de onde as instruções são transferidas para o processador para
decodificação e execução. Na maior parte dos computadores modernos, a execução de um programa dá-se
pela contínua repetição desse ciclo de instrução (Fig. 1.1), ou seja:
1. O processador busca da memória a seqüência de bits que representa a próxima instrução a ser exe-
cutada. O endereço dessa instrução é armazenado em registrador processador dedicado a esse fim,
tipicamente denominadoPC (Program Counter);
2. A unidade de controle do processador decodifica a instrução, isto é, descobre que ações devem ser
realizadas para a execução da instrução;
3. Se a execução da instrução demandar dados que estejam na memória, o processador busca também
esses dados (os operandos da instrução) nos endereços especificados na instrução. As várias possibi-
lidades de especificar esses endereços de operandos constituem os modos de endereçamento supor-
tados pelo processador. Modos usuais de endereçamento incluem endereçamento direto, indireto e
indexado;
4. A unidade de execução do processador realiza a ação associada à instrução;
5. O processador armazena o resultado gerado, se houver, em um registrador ou em algum endereço da
memória que tenha sido especificado na instrução.
Este processo é repetido desde a primeira até a última instrução do programa.
Computadores onde esse modelo de execução é obedecido são conhecidos como máquinas de von Neu-
mann, uma homenagem a John von Neumann (1903–1957), matemático húngaro que atuou como consultor
no Projeto ENIAC, em meados da década de 1940, e a quem é atribuída a concepção da organização interna
de computadores onde o programa é armazenado na memória.
A um programa em execução dá-se o nome de processo. Além das instruções do programa, um pro-
cesso necessita de todo um conjunto de informações adicionais para o controle de sua execução. O estado
corrente dessas informações associadas a cada programa em execução constitui o estado do processo.
c
2000 DCA/FEEC/UNICAMP 3
Programação de Sistemas
Ivan L. M. Ricarte
Conceitos Básicos
1.2. Software de sistema
Figura 1.1 O ciclo de instrução von Neumann.
Decodificador Buffer dados
Entrada
e
saída
Arranjo de
células
controle
Unidade de
Unidade lógica
e aritmética
Barramento
do sistema
MemóriaCPU
endereço
dados
R
eg
ist
ra
do
re
s
instruções
1.2 Software de sistema
Na organização de von Neumann o programa, assim como os dados, está na memória. Uma conseqüên-
cia desse fato é que há programas que manipulam outros programas, como se estes fossem seus dados. O
conjunto de programas com essa característica constitui o software de sistema. O software de sistema
é composto pelo conjunto de programas que permitem desde a criação de um programa aplicativo até o
controle de recursos utilizados pelo programa durante sua execução por um processador.
No desenvolvimento de programas, o software de sistema é extensamente utilizado, com as várias
etapas inter-relacionadas para a criação e execução de um programa (Fig. 1.2). Tipicamente, esse relacio-
namento dá-se de forma transparente para o programador.
Figura 1.2 Etapas para execução de programa.
Arquivos texto Arquivos binários
Programa
fonte
Programa
assembly
compilador
Módulo
objeto
montador ligador
carregamento
Módulo de
carregador
Execução
em 
memória
Programas são usualmente descritos em linguagens de alto nível. O compilador é o programa do siste-
ma que traduz um programa descrito através de uma linguagem de alto nível específica para um programa
equivalente em linguagem assembly. Esse processo de tradução é denominado compilação.
O montador (assembler) é o programa do sistema responsável por traduzir um programa assembly
para o código de máquina. Esse processo de tradução de um programa-fonte assembly para um programa
em código de máquina é denominado montagem; o resultado da montagem é um módulo objeto contendo
o código binário que será posteriormente executado.
Para que um programa possa ser executado, seu código de máquina deve estar presente na memória.
O carregador é o programa do sistema responsável por transferir o código de máquina de um programa
c
2000 DCA/FEEC/UNICAMP 4
Programação de Sistemas
Ivan L. M. Ricarte
Conceitos Básicos
1.3. Desenvolvimento de Software
para a memória e iniciar sua execução. O processo de transferir o conteúdo de um módulo objeto para a
memória principal é denominado carregamento. A execução de qualquer programa deve ser precedida
por seu carregamento.
Programas complexos raramente são descritos através de um único arquivo-fonte, mas sim organizados
em módulos objetos interrelacionados. Tais módulos podem agregar funcionalidades da aplicação sendo
desenvolvida ou recursos comuns do sistema que devem ser integrados à aplicação. O programa do sistema
ligador é o responsável por interligar os diversos módulos de um programa para gerar o programa que será
posteriormente carregado para a memória. Essa etapa de preparação de um programa para sua excução é
denominada ligação.
Finalmente, a execução de cada programa se dá sob o controle do sistema operacional. O sistema
operacional é o responsável por gerenciar cada processo no computador, estabelecendo como será realiza-
da sua execução. Ele também atua como um programa supervisor que estabelece uma camada de controle
entre o hardware do computador e aplicações de usuários. Uma de suas funções é estabelecer uma inter-
face de software uniforme entre o computador, outros programas do sistema e programas de aplicação de
usuários. Outra função fundamental de um sistema operacional é gerenciar os recursos de um computador
de forma a promover sua eficiente utilização. Exemplos de sistemas operacionais são MS-DOS, Windows
NT, OS/2, Linux e Solaris — estes dois implementações do sistema operacional Unix.
1.3 Desenvolvimento de Software
Não há dúvidas hoje em dia quanto à importância do software no desenvolvimento dos mais diversos
sistemas. Com esta evolução do papel do software em sistemas computacionais, veio também uma maior
complexidade de programas e uma maior preocupação em desenvolver programas que pudessem ser fa-
cilmente entendidos e modificados (se necessário), não apenas pelo autor do programa mas também por
outros programadores. A disciplina que estuda o desenvolvimento de bom software é conhecida como
Engenharia de Software.
A Engenharia de Software estabelece alguns princípios de desenvolvimento que independem da lin-
guagem de programação adotada. Estes princípios são utilizados nas três grandes fases da vida de um
programa, que são:
Especificação: estuda o que deve ser feito pelo programa, incluindo análises do sistema e do problema a
ser resolvido;
Desenvolvimento: incorpora o projeto do programa (por exemplo, uma descrição do algoritmo e estruturas
necessárias), a codificação (tradução do projeto em termos de uma linguagem de programação) e
testes do programa; e
Manutenção: é a fase de mudanças decorrentes da correção de erros e atualizações do programa.
A fase de desenvolvimento costuma tomar a maior parte do ciclo de vida de criação de um software.
Nesta fase são tomadas decisões que podem afetar sensivelmente o custo e a qualidade do software gerado.
Uma vez estabelecida a funcionalidade do software que se deseja implementar na fase de definição, a
fase de desenvolvimento propriamente dita pode ser iniciada. Esta fase pode ser dividida em três etapas
principais, que são projeto, codificação e teste. A seguir, serão destacados alguns aspectos ligados ao
desenvolvimento de software, que será o principal enfoque deste curso.
1.3.1 Projeto
O projeto de software pode ser subdividido em dois grandes passos, projeto preliminar e projeto de-
talhado. O projeto preliminar preocupa-se em transformar os requisitos especificados na fase de análise
c
2000 DCA/FEEC/UNICAMP 5
Programação de Sistemas
Ivan L. M. Ricarte
Conceitos Básicos
1.3. Desenvolvimento de Software
em arquiteturas de dados e de software. O projeto detalhado refina estas representações de arquitetura em
estruturas de dados detalhadas e em representações algorítmicas do software.
Uma das estratégias de projeto mais utilizadas é o desenvolvimento top-down. Neste tipo de desen-
volvimento, trabalha-se com o conceito de refinamento de descriçõesdo software em distintos níveis de
abstração. O conceito de abstração está relacionado com esconder informação sobre os detalhes. No nível
mais alto de abstração, praticamente nenhuma informação é detalhada sobre como uma dada tarefa será im-
plementada — simplesmente descreve-se qual é a tarefa. Em etapas sucessivas de refinamento, o projetista
do software vai elaborando sobre a descrição da etapa anterior, fornecendo cada vez mais detalhes sobre
como realizar a tarefa.
O desenvolvimento top-down estabelece o processo de passagem de um problema a uma estrutura de
software para sua solução (Fig. 1.3), resultando em uma estrutura de software que representa a organização
dos distintos componentes (ou módulos) do programa. Observe que a solução obtida pode não ser única:
dependendo de como o projeto é desenvolvido e das decisões tomadas, distintas estruturas podem resultar.
Figura 1.3 Processo de evolução do software.
S1 S2 S3 S4 S1
S2 S3
S4
"Problema"
P2
P3
P4
P1
S1
S3
S4
S2
por software
Solução
Possíveis estruturas de software
Outro aspecto tão importante quanto a estrutura de software é a estrutura de dados, que é uma re-
presentação do relacionamento lógico entre os elementos de dados individuais. As estruturas de dados
utilizadas definem a organização, métodos de acesso e alternativas de processamento para a informação
manipulada pelo programa. Embora a estrutura final possa ser tão complexa quanto o projetista deseja, há
alguns blocos básicos (Fig. 1.4) que permitem construir as estruturas mais sofisticadas.
A estrutura de dados mais simples é um escalar, que representa um elemento simples de informação
que pode ser acessado através de um identificador. Elementos escalares organizados como um grupo contí-
guo constituem um arranjo ou vetor seqüencial, cujos elementos podem ser acessados através de um
identificador em associação com um índice. Elementos não-contíguos podem ser agrupados através de
uma lista ligada, cuja unidade de acesso é um nó que contém um item de dado (escalar ou não) e um
c
2000 DCA/FEEC/UNICAMP 6
Programação de Sistemas
Ivan L. M. Ricarte
Conceitos Básicos
1.3. Desenvolvimento de Software
ponteiro para indicar o próximo nó na lista (veja a Seção 3.5).
Figura 1.4 Estruturas elementares de dados.
Escalar Vetor sequencial
. . . 
Lista ligada
Uma vez estabelecidas as estruturas de software e de dados do programa, o detalhamento do projeto
pode prosseguir com o projeto procedimental, onde são definidos os detalhes dos algoritmos que serão
utilizados para implementar o programa. Um algoritmo é uma solução passo-a-passo para a resolução do
problema especificado que sempre atinge um ponto final. Em princípio, algoritmos poderiam ser descritos
usando linguagem natural (português, por exemplo). Entretanto, o uso da linguagem natural para descrever
algoritmos geralmente leva a ambiguidades, de modo que se utilizam normalmente linguagens mais restri-
tas para a descrição dos passos de um algoritmo. Algumas formas de especificação destas linguagens são
descritas na seqüência.
1.3.2 Programação Estruturada
A programação estruturada estabelece uma disciplina de desenvolvimento de algoritmos que facilita a
compreensão de programas. O princípio básico de programação estruturada é que um programa é composto
por blocos elementares que se interligam através de três mecanismos básicos, que são seqüência, seleção
e iteração. Cada uma destas construções tem um ponto de início (o topo do bloco) e um ponto de término
(o fim do bloco) de execução. Qualquer algoritmo, independentemente da área de aplicação e de sua
complexidade, pode ser descrito através destes mecanismos básicos.
Um dos mecanismos mais utilizados para a representação de algoritmos é o fluxograma. Em um
fluxograma, o retângulo simbolicamente representa um passo do processamento (uma tarefa), o losango
indica uma condição lógica e as setas mostram o fluxo de controle (Fig. 1.5).
Seqüência implementa os passos de processamento necessários para descrever qualquer programa.
Por exemplo, um segmento de programa da forma “faça primeiro a Tarefa a e depois a Tarefa b” seria
representado por uma seqüência de dois retângulos (Fig. 1.5(a)). Uma notação textual equivalente poderia
ser obtida através de uma pseudo-linguagem que se restringisse a construções estruturadas, como em
SEQUENCIA()
1 Tarefa a
2 Tarefa b
3 return
Seleção especifica a possibilidade de selecionar o fluxo de execução do processamento baseado em
ocorrências lógicas. Há duas formas básicas de condição. A primeira forma é a construção IF (Fig. 1.5(b)),
que permite representar fluxos da forma “se a condição x for verdadeira, faça a Tarefa a; senão (isto é, se
a condição x for falsa), faça a Tarefa b.” As duas setas que saem do losango de condição no fluxograma
recebem rótulos T e F para indicar o fluxo de execução quando a condição especificada é verdadeira ou
falsa, respectivamente. O retângulo sob a seta rotulada T normalmente é denominado o bloco then da
construção, enquanto que o outro retângulo é denominado bloco else. Em termos de pseudo-linguagem, a
construção equivalente pode ser descrita como
SELECAOIF()
1 if x
c
2000 DCA/FEEC/UNICAMP 7
Programação de Sistemas
Ivan L. M. Ricarte
Conceitos Básicos
1.3. Desenvolvimento de Software
Figura 1.5 Representação dos mecanismos básicos da programação estruturada: (a) seqüência; (b) seleção
IF; (c) seleção SWITCH; (d) iteração WHILE; (e) iteração REPEAT.
(a) (b)
...
(c)
(d) (e)
T F T
T
T
F
F
F
F
T
T
F
2 then Tarefa a
3 else Tarefa b
A outra forma de seleção estende o número de condições que podem ser testadas para definir o fluxo
de execução. Esta construção, SWITCH (Fig. 1.5(c)), permite representar fluxos da forma “se a variável v
tem o valor x, faça a Tarefa a; se v tem o valor y, faça a Tarefa b ; e se tem o valor z, faça a Tarefa c.” Uma
possível representação para tal construção em termos de pseudo-linguagem é:
SELECAOSWITCH()
1 switch v
2 case x :
3 Tarefa a
4 case y :
5 Tarefa b
6 case z :
7 Tarefa c
8 case default :
9 Tarefa d
Nesse exemplo em pseudo-linguagem, ilustra-se a inclusão de uma condição default, que indica qual
deve ser a ação executada (no caso, Tarefa d) quando a variável assume um valor não explicitamente
especificado pelo programador. Observe que a construçãoSWITCH não é essencial, uma vez que ela pode
ser representada em termos da seleção com IF, como em
SELECAOMULTIPLAIF()
1 if v = x
2 then Tarefa a
3 else if v = y
c
2000 DCA/FEEC/UNICAMP 8
Programação de Sistemas
Ivan L. M. Ricarte
Conceitos Básicos
1.3. Desenvolvimento de Software
4 then Tarefa b
5 else if v = z
6 then Tarefa c
7 else Tarefa d
Entretanto, a utilização de estruturas SWITCH simplifica a expressão de situações que ocorrem fre-
quentemente em programas — por exemplo, selecionar ações dependendo de uma opção escolhida em um
menu — sem ter que recorrer ao aninhamento excessivo de condições da forma IF.
Iteração permite a execução repetitiva de segmentos do programa. Na forma básica de repetição,
WHILE (Fig. 1.5(d)), uma condição x é verificada. Caso seja verdadeira, então uma Tarefa a é executada
e a condição é reavaliada. Enquanto a condição for verdadeira, a tarefa é repetidamente executada. Em
termos da pseudo-linguagem, esta construção pode ser representada por
ITERACAOWHILE()
1 while x
2 do Tarefa a
Uma variação dessa construção é apresentada na Fig. 1.5(e), onde inicialmente a tarefa é executada
e apenas então a condição de repetição é avaliada; quando a condição torna-se verdadeira, a iteração é
encerrada. Esta variante, iteração REPEAT, pode ser expressa em termos da pseudo-linguagem por
ITERACAOREPEAT()
1 repeat
2 Tarefa a
3 until x
A estratégia de desenvolvimento top-down pode também ser utilizada na descrição algorítmica de pro-
cedimentos. Nestecaso, um retângulo ou uma linha de pseudo-código pode descrever uma tarefa tão
complexa quanto necessário, sendo que esta tarefa pode ser posteriormente descrita em termos de outro(s)
fluxograma(s) ou pseudo-código(s). Em geral, são aplicados tantos refinamentos quanto necessário até o
ponto em que uma tarefa possa ser facilmente descrita em termos das construções suportadas pela lingua-
gem de codificação.
1.3.3 Codificação
A etapa de codificação traduz a representação do projeto detalhado em termos de uma linguagem de
programação. Normalmente são utilizadas linguagens de alto nível, que podem então ser automaticamente
traduzidas para a linguagem de máquina pelo processo de compilação.
A tradução de uma especificação de um programa para uma linguagem de programação pode resultar
em programas incompreensíveis se não houver um cuidado na preservação da informação presente. As
linhas gerais que estão a seguir buscam estabelecer uma disciplina de codificação que, se seguida, facilita
o entendimento e manutenção de programas.
Código deve ser acima de tudo claro. Os compiladores modernos fazem um ótimo trabalho de otimi-
zação de código, de forma que não há necessidade do programador ficar se preocupando em usar pequenos
“truques” para economizar algumas instruções no código de máquina — provavelmente o compilador já
faria isto para o programador (veja o Capítulo 10). A preocupação com a otimização de código só de-
ve existir após a detecção da necessidade real desta otimização, e ela deve se restringir ao segmento do
programa onde o problema de desempenho foi localizado.
Nomes de identificadores (variáveis, procedimentos) devem denotar claramente o significado do iden-
tificador. Linguagens de programação modernas raramente limitam o número de caracteres que um identi-
ficador pode ter, de forma que não faz sentido restringir este tamanho. Compare as duas linhas de código a
c
2000 DCA/FEEC/UNICAMP 9
Programação de Sistemas
Ivan L. M. Ricarte
Conceitos Básicos
1.3. Desenvolvimento de Software
seguir, e observe como nomes claros podem facilitar a compreensão de um programa:
d = v * t;
distancia = velocidade * tempo;
O uso de comentários deve ser usado como forma de documentação interna de um programa. O excesso
de comentários não é recomendado, pois isto pode prejudicar a leitura de um programa. Entretanto, há
comentários que devem estar presentes em qualquer programa, tais como
� Comentários de prólogo, que aparecem no início de cada módulo. Devem indicar a finalidade do
módulo, uma história de desenvolvimento (autor e data de criação e modificações), e uma descrição
das variáveis globais (se houver);
� Comentários de procedimento, que aparecem antes de cada função indicando seu propósito, uma
descrição dos argumentos e valores de retorno;
� Comentários descritivos, que descrevem blocos de código.
Comentários devem ser facilmente diferenciáveis de código, seja através do uso de linhas em branco, seja
através de tabulações. É importante que comentários sejam corretos e coerentes com o código, uma vez que
um comentário errôneo pode dificultar mais ainda o entendimento de um programa do que se não houvesse
comentário nenhum.
Na especificação das construções estruturadas, use tabulações para indicar blocos de distintos níveis.
Evite sempre que possível o uso de condições de teste complicadas e o aninhamento muito profundo de
condições de teste. Use parênteses para deixar claro como expressões lógicas e aritméticas serão computa-
das.
Com relação a instruções de entrada e saída, todos os dados de entrada devem ser validados. O formato
de entrada deve ser tão simples quanto possível e, no caso de entrada interativa, o usuário deve receber
uma indicação de que o programa está aguardando dados. Da mesma forma, a apresentação de resultados
deve ser clara, com indicação sobre o que está sendo apresentado. Finalmente, teste o valor de retorno
de qualquer rotina que possa retornar um erro, tomando as ações que forem necessárias para minimizar o
efeito do erro.
c
2000 DCA/FEEC/UNICAMP 10
Parte II
Montadores e Carregadores
11
Capítulo 2
Princípios de Montadores
Índice
2.1 Definições . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.2 Instruções assembly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.3 Pseudo-Instruções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.3.1 Substituição simbólica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.3.2 Origem do segmento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.3.3 Definição de constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.3.4 Declaração de variáveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.3.5 Fim de programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.4 Macromontadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.4.1 Definição de macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.4.2 Uso de macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.4.3 Argumentos de macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.4.4 Expansão condicional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.5 Aspectos genéricos de projeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.5.1 Leitura de arquivo fonte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
C: manipulação de arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.5.2 Extração de campos da linha . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
C: obtenção do conteúdo de arquivos de texto . . . . . . . . . . . . . . . . . . 23
C: manipulação de strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.5.3 Tratamento de pseudo-instruções . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.5.4 Tratamento de macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.5.5 Tratamento de instruções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.5.6 Tratamento de símbolos definidos . . . . . . . . . . . . . . . . . . . . . . . . . 26
Um programa em linguagem simbólica, ou linguagem assembly, contém seqüências de instruções
mnemônicas que representam as operações que devem ser realizadas pelo processador. Essas instruções
são definidas pelos projetistas da linguagem assembly do processador; o conjunto de todas as instruções
definidas para um processador contitui seu jogo de instruções.
O papel do montador é analisar cada instrução do programa em linguagem simbólica, traduzindo-a
para a seqüência de bits correspondente à instrução de máquina. Como cada processador tem sua própria
12
Programação de Sistemas
Ivan L. M. Ricarte
Princípios de Montadores
2.1. Definições
linguagem, montadores são específicos para processadores. Embora os exemplos desse capítulo utilizem
um processador em particular, os princípios colocados para a operação e projeto de um montador são
válidos independentemente de qual seja o processador utilizado.
2.1 Definições
Montagem é o processo que realiza a tradução de cada instrução assembly para o código de máquina
correspondente. Obviamente, esse o processo de montagem seria maçante e sujeito a erros se executa-
do manualmente. A motivação para a existência de montadores é exatamente tornar esse processo tão
mecânico como possível.
Um arquivo fonte é um arquivo de texto que contém as instruções assembly que deverão passar pelo
processo de montagem. O termo arquivo fonte é também utilizado para denotar arquivos que contém
instruções em outras linguagens que não assembly — por exemplo, um programa em C que deverá passar
pelo processo de compilação. O contexto de utilização deverádeixar claro que tipo de conteúdo está
presente no arquivo fonte.
O conteúdo de um arquivo fonte nada mais é do que uma seqüência de caracteres. A forma pela
qual uma seqüência de bits é traduzida em termos de caracteres tem sido o objeto de diversos esforços
de padronização. O padrão mais utilizado ainda é o formato ASCII (American Standard for Computer
Information Interchange), seguido pelo padrão ISO-8859 e recentemente por UniCode. O formato ASCII
básico (Tabela 2.1) permite representar 128 caracteres distintos (valores entre $00 e $7F), entre os quais
estão diversos caracteres de controle (tais com ESC, associado à tecla de escape, e CR, associado ao carriage
return) e caracteres de pontuação. Neste formato, os caracteres entre ‘0’ e ‘9’ são representados pelos
valores hexadecimais $30 a $39, respectivamente (valores decimais entre 48 e 57); as letras maiúsculas ‘A’
a ‘Z’, pelos valores de $41 a $5A; e as letras minúsculas ‘a’ a ‘z’, pelos valores de $61 a $7A. ISO-8859
estende ASCII para valores entre $80 e $FF, sendo organizado em diversos subconjuntos, dos quais o ISO-
8859-1 (Latin-1) é o mais utilizado. UniCode integra várias famílias de caracteres em uma representação
unificada (de até 16 bits), sendo que a base do padrão engloba as codificações ASCII e ISO-8859-1.
Tabela 2.1 Codificação ASCII.
Cód $-0 $-1 $-2 $-3 $-4 $-5 $-6 $-7 $-8 $-9 $-A $-B $-C $-D $-E $-F
$0- NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI
$1- DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US
$2- [esp] ! " # $ % & ‘ ( ) * + , - . /
$3- 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
$4- @ A B C D E F G H I J K L M N O
$5- P Q R S T U V W X Y Z [ \ ] ^ _
$6- ‘ a b c d e f g h i j k l m n o
$7- p q r s t u v w x y z { | } ~ DEL
Um montador, ou assembler, é um programa cujos dados de entrada são seqüências de caracteres
obtidas de um arquivo fonte que constituem o programa assembly e cujo resultado é um conjunto de pa-
lavras de máquina (instruções, dados) gravadas em um arquivo binário constituindo um módulo que será
posteriormente utilizado pelo carregador — um módulo de carregamento.
Um segmento é um conjunto de palavras de máquina que deve ocupar um espaço contíguo na memória
principal. O resultado gerado por um montador pode conter um ou mais segmentos de código objeto em
linguagem de máquina. A posição de memória (endereço) associada ao início do segmento é denominada
a sua origem.
c
2000 DCA/FEEC/UNICAMP 13
Programação de Sistemas
Ivan L. M. Ricarte
Princípios de Montadores
2.2. Instruções assembly
O uso de um montador também permite facilitar a codificação assembly, de modo que o programa-
dor não precise mapear explicitamente a alocação de segmentos e variáveis à memória. Tipicamente,
um montador irá oferecer um conjunto de pseudo-instruções, facilitadores que permitem a substituição
de constantes e endereços por referências simbólicas definidas pelo programador. Pseudo-instruções são
também denominadas pseudo-operações ou ainda diretivas.
Um macromontador (macro-assembler) é um programa montador que permite adicionalmente a defi-
nição e uso de macro-instruções para facilitar o processo de programação assembly.
Um montador multiplataforma (cross-assembler) é um montador que permite gerar código para um
processador alvo diferente do processador no qual o montador está sendo executado.
Um carregador é um programa que permite transferir um programa, já montado, desde o módulo de
carregamento para a memória e então iniciar sua execução.
2.2 Instruções assembly
Em geral, um montador recebe como entrada um arquivo texto cujas linhas são instruções assembly.
Cada linha contém tipicamente quatro campos separados por espaços ou tabulações. O formato geral de
uma linha de instrução é
[rotulo] operacao [operando[,operando,...]] [comentario]
onde a notação [ ] indica que o campo indicado é opcional. No entanto, dependendo da operação o
campo de operando ou de rótulo pode ser obrigatório. Tipicamente, espaços não são permitidos em um
campo exceto no campo de comentário. Exemplos de instruções assembly são apresentados abaixo e os
campos de uma instrução são detalhados na seqüência:
CLR.W D0
LOOP MOVE.W (A0)+,D1 A0 foi definido alhures
CMP.W D1,D0
BNE LOOP
Rótulo. O rótulo permite fazer referências posteriores à posição de memória onde a instrução está arma-
zenada. No exemplo acima, a segunda instrução tem um rótulo (LOOP) que é utilizado na quarta instrução
para indicar o destino da instrução de desvio condicional (BNE). Sem o uso de rótulos, o programador
deveria calcular e codificar explicitamente o valor do operando da instruçãoBNE — no exemplo, $FA1.
Tradicionalmente, montadores reconhecem um campo de rótulo através de uma das duas alternativas:
1. O rótulo começa na primeira coluna; e/ou
2. O rótulo é encerrado pelo caráter ’:’, que não faz parte do rótulo mas age apenas como um terminador
para ele.
Operação. O campo de operação especifica a instrução que será montada. Um sufixo pode ser utilizado
para indicar o tamanho do operando — no caso dos processadores da família 68K, .B, .W ou .L.
Operandos. A presença ou não de operandos, assim como a quantidade de operandos presentes, depen-
dem da instrução que está sendo executada. Se houver operandos nesse campo, não pode haver nenhum
espaço entre eles exceto no interior de strings, seqüências de caracteres expressas entre aspas simples.
1O Apêndice A apresenta os fundamentos da representação de valores caracteres e inteiros em binário, incluindo a representação
em complemento de 2.
c
2000 DCA/FEEC/UNICAMP 14
Programação de Sistemas
Ivan L. M. Ricarte
Princípios de Montadores
2.2. Instruções assembly
Comentários. Todo o restante da linha após o campo de operandos, ou após o campo de operação de
uma instrução sem operandos, é considerada como comentário. Como tal, qualquer caráter — incluindo
espaços — pode estar presente nesse campo.
Tradicionalmente, montadores também permitem linhas de comentários que são iniciadas por algum
caráter especial — tipicamente, ’*’.
Através do uso de pseudo-instruções e dos rótulos, é possível fazer referências a posições de memória
e a variáveis através de identificadores simbólicos. Isso permite que o programador possa usar esses identi-
ficadores simbólicos como operandos de suas instruções sem ter que necessariamente saber a qual posição
de memória a variável ou instrução está alocada.
A regra para a composição de tais identificadores pode variar entre montadores. Em geral, idntificadores
devem ser iniciados por caracteres alfabéticos e têm tamanho limitado — por exemplo, apenas os oito
caracteres iniciais do identificador são significativos.
Deve ser possível especificar constantes como parte do operando — por exemplo, para identificar um
operando imediato, tipicamente indicado pelo prefixo #. A forma de identificação de valores constantes
também pode mudar entre montadores distintos. A opção adotada neste texto é comum a diversos monta-
dores:
Constantes decimais: uma seqüência de dígitos for iniciada por um dígito (0 a 9), então assumir-se-á que
a seqüência representa um valor decimal, ou seja, em base 10. Nenhum prefixo especial é utilizado.
Por exemplo,
MOVE.B #48,D0
coloca o valor 48 no byte menos significativo do registrador D0.
Constantes binárias: uma seqüência de bits (0 ou 1) precedida pelo símbolo % identifica valores em base
2. Por exemplo, a mesma instrução acima poderia ter sido codificada como
MOVE.B #%110000,D0
Constantes octais: uma seqüência de dígitos entre 0 e 7 precedidas pelo símbolo @ identifica valores em
base 8. Ainda tomando a mesma instrução como exemplo, a codificação seria
MOVE.B #@60,D0
Constantes hexadecimais: uma seqüência de dígitos hexadecimais (0 a 9, A a F) precedidas pelo símbolo
$ identifica um valor em base 16. Para a mesma instrução,
MOVE.B #$30,D0
Constantes ASCII: um valor caráter (ASCII)deverá ser apresentado entre aspas simples, como em ’A’
(valor $41). Seqüências de caracteres (strings) são definidas também entre aspas simples, como em
’ABC’ (seqüência de três bytes com valores $41, $42 e $43). Para o exemplo da instrução acima, a
codificação equivalente seria
MOVE.B #’0’,D0
Como pode ser observado, há muitas formas de expressar os mesmos valores. A forma selecionada deve
ser aquela que torne mais claro para a leitura do programa qual o papel que a constante está desempenhando.
c
2000 DCA/FEEC/UNICAMP 15
Programação de Sistemas
Ivan L. M. Ricarte
Princípios de Montadores
2.3. Pseudo-Instruções
2.3 Pseudo-Instruções
Além das instruções do processador, um programa assembly preparado para um montador também
pode conter pseudo-instruções que estabelecem a conexão entre referências simbólicas e valores a serem
efetivamente referenciados.
Cada montador pode oferecer um conjunto de pseudo-instruções diferenciado. As pseudo-instruções
descritas a seguir, porém, representam um subconjunto significativo de facilidades oferecidas por monta-
dores.
2.3.1 Substituição simbólica
EQU associa um valor específico, definido pelo programador, a um símbolo. Sua forma genérica é
rótulo EQU expressão
onde rótulo é o símbolo que está sendo definido e expressão é o valor que será atribuído a ele. Este
valor pode ser representado como uma constante ou conter uma expressão envolvendo outros símbolos já
definidos, como em
1 SEG1 EQU $1000
2 DATA EQU SEG1+16
Nesse exemplo, o montador irá substituir as ocorrências do símbolo SEG1 pelo valor $1000 (hexade-
cimal). Assim, o símbolo DATA estará associado ao valor $1010 e todas as suas ocorrências no programa
serão substituídas por esse valor.
2.3.2 Origem do segmento
ORG estabelece que a posição de memória da instrução seguinte deverá ser o valor especificado como
o operando dessa pseudo-instrução. Sua forma genérica é
ORG <expr>
onde expr pode ser um valor constante ou um símbolo já definido.
Por exemplo, assumindo as definições acima com EQU o trecho de programa a seguir
3 ORG SEG1
4 MOVE.W DATA,D0
5 MOVE.W D0,DATA+2
6 RTS
indica que a primeira instrução MOVE.W (linha 4) estará alocada à posição $1000 da memória. A codifi-
cação dessa instrução requer uma palavra para o código de operação, incluindo-se a definição do modo de
endereçamento (direto) e do registrador de destino (D0), e duas palavras para expressar o endereço DATA,
considerando por simplicidade que todos os endereços ocupam quatro bytes na codificação de máquina.
Assim, a próxima instrução (linha 5) estará alocada à posição seguinte da memória, $1006. Similarmente,
a instrução da linha 6 estará alocada à posição de memória $100C.
c
2000 DCA/FEEC/UNICAMP 16
Programação de Sistemas
Ivan L. M. Ricarte
Princípios de Montadores
2.3. Pseudo-Instruções
2.3.3 Definição de constantes
Para introduzir um valor específico em uma dada posição de memória, a pseudo-instrução DC (define
constant) é utilizada. Sua forma genérica é
<rotulo> DC.<s> <lista>
onde rótulo será o símbolo utilizado para referenciar o valor (ou lista de valores) sendo definido. O
valor deste rótulo será a posição de memória onde o valor estará sendo armazenado. O campo s identifica
o tamanho de cada um dos valores armazenados a partir daquela posição, podendo ser B (byte), W (word)
ou L (long word). Se omitido, W é assumido. Finalmente, lista identifica o valor ou seqüência de valores
a armazenar na memória.
Algumas possíveis definições usando DC são
1 ORG $2000
2 CONTADOR DC.L 100
3 ARR1 DC.W 0,1,1,2,3,5,8,13
4 MENSAGEM DC.B ’Alo, pessoal!’
A primeira linha com DC (linha 2) define que na posição $2000 (definida pela instrução ORG anterior)
de memória será armazenado o valor 100 em quatro bytes, sendo que essa posição pode ser referenciada no
programa pelo símbolo CONTADOR. A linha seguinte indica que a partir da posição seguinte de memória
($2004) será armazenado um conjunto de inteiros de dois bytes, sendo 0 o conteúdo armazenado na posição
$2004, 1 na posição $2006, 1 em $2008, 2 em $200A e assim por diante. O símbolo ARR1 estará associado
ao valor $2004, o início do arranjo. A pseudo-instrução na última linha expressa que a seqüência de
caracteres (um byte cada um) indicada no campo de operando será armazenada a partir da posição $2014,
que será o valor do símbolo MENSAGEM.
2.3.4 Declaração de variáveis
Em algumas situações deseja-se reservar espaço na memória para uma variável, mas não se conhece
seu valor de antemão. Neste caso, é possível usar a pseudo-instrução DS (declare storage), da forma
<rotulo> DS.<s> <quant>
Esta pseudo-instrução reserva espaço em memória para quant variáveis de tamanho s, que pode ser
B (byte), W (word) ou L (long word). É precedida por um rótulo que define um nome simbólico para a
variável, como em
1 ORG $2040
2 VALUE DS.W 1
que reserva na posição corrente ($2040) espaço para uma variável de tamanho dois bytes (word) e associa
à posição dessa variável o símbolo VALUE.
2.3.5 Fim de programa
Uma outra pseudo-instrução importante é END, que indica ao montador o fim do programa assembly.
Seu formato geral é
END rotulo_inicio
c
2000 DCA/FEEC/UNICAMP 17
Programação de Sistemas
Ivan L. M. Ricarte
Princípios de Montadores
2.4. Macromontadores
onde o rótulo no operando está associado a um rótulo do início do programa que está sendo encerrado por
essa pseudo-instrução. O uso desse rótulo ficará mais claro quando for descrito o processo de carregamento
(Seção 4.4).
Essa pseudo-instrução só aparece uma vez no programa fonte e deve estar na sua última linha.
2.4 Macromontadores
Uma instrução de macro (ou simplesmente macro) é um sinônimo para um grupo de instruções. O uso
de macros facilita a utilização de trechos repetitivos de código, que podem ser invocados pelo programador
como um única linha no programa. Por esse motivo, diversos montadores apresentam extensões com
funcionalidades para a definição e utilização de macros — são os macromontadores.
2.4.1 Definição de macros
Na sua forma mais simples, uma macro é simplesmente uma abreviatura para um grupo de instruções.
A forma geral de definição de uma macro é:
nome MACRO [argumentos]
corpo
ENDM
A pseudo-instruçãoMACROmarca o início da definição da macro-instrução. Toda macro tem um nome,
que será utilizado pelo programador para invocar a macro, e um corpo, que será usado pelo processador de
macro para substituir o nome usado pelo programador. O fim da definição é indicado pela pseudo-instrução
ENDM.
O nome da macro está definido no campo de rótulo. Uma macro pode opcionalmente ter argumentos
que serão usados na expansão da macro. O corpo é simplesmente uma seqüência de instruções que podem
eventualmente ser parametrizadas pelos argumentos.
Considere o seguinte exemplo de macro. Uma aplicação repetidamente transfere uma seqüência de
quatro caracteres alfabéticos na posição de memória indicada pela constante CHARS_I para o registrador
D0, onde converte essas letras para minúsculas. Os caracteres convertidos são colocados na posição de
memória indicada pela constante CHARS_O. Através da definição de uma macro, essas ações podem ser
especificadas como:
1 TOLOWER MACRO
2 MOVE.L CHARS_I,D0
3 ORI.L $20202020,D0
4 MOVE.L D0,CHARS_O
5 ENDM
2.4.2 Uso de macros
Uma vez que uma macro esteja definida, seu nome pode ser utilizado como se fosse uma operação
válida do montador. Considerando a definição acima, o uso da macro TOLOWER dar-se-ia da forma:
6 CHARS_I EQU $0400
7 CHARS_O EQU $0600
8 ORG $1000
9 PROG ...
c
2000 DCA/FEEC/UNICAMP 18
Programação de Sistemas
Ivan L. M. Ricarte
Princípios de Montadores
2.4. Macromontadores
10 TOLOWER
11 ...
12 TOLOWER
13 ...
14 END PROG
O exemplo ilustra duas invocações à macro-instrução. Após passar pelo processador de macro, o código
acima seria convertidopara o seguinte código assembly onde as macros já estão expandidas:
15 CHARS_I EQU $0400
16 CHARS_O EQU $0600
17 ORG $1000
18 PROG ...
19 MOVE.L CHARS_I,D0
20 ORI.L $20202020,D0
21 MOVE.L D0,CHARS_O
22 ...
23 MOVE.L CHARS_I,D0
24 ORI.L $20202020,D0
25 MOVE.L D0,CHARS_O
26 ...
27 END PROG
2.4.3 Argumentos de macros
A macro TOLOWER, da forma como foi definida, não oferece nenhuma flexibilidade, pois os endereços
de entrada e saída de dados são pré-definidos. O uso de argumentos de macro permite flexibilizar essas
definições e substituições.
Argumentos em macros são representados por variáveis fictícias (dummy) que devem ser substituídas
pelos nomes de variáveis reais durante a expansão da macro. A associação entre as variáveis reais e as va-
riáveis dummy é feita pela posição da variável na declaração e invocação, assim como ocorre em subrotinas
nas linguagens de alto nível.
Um argumento de macro é identificado pelo símbolo &, como em
28 TOLOWER2 MACRO &IN,&OUT
29 MOVE.L &IN,D0
30 ORI.L $20202020,D0
31 MOVE.L D0,&OUT
32 ENDM
Na invocação dessa macro TOLOWER2, dois argumentos devem ser fornecidos. Assim, a invocação
33 TOLOWER2 CHARS_I,CHARS_O
tem exatamente a mesma expansão que a macro anterior. No entanto, é possível agora invocar a mesma
macro para processar os quatro caracteres seguintes, como em
34 TOLOWER2 CHARS_I+4,CHARS_O+4
que seria expandido para
35 MOVE.L CHARS_I+4,D0
36 ORI.L $20202020,D0
37 MOVE.L D0,CHARS_O+4
Essa flexibilidade não seria possível com a definição original da macro TOLOWER.
c
2000 DCA/FEEC/UNICAMP 19
Programação de Sistemas
Ivan L. M. Ricarte
Princípios de Montadores
2.5. Aspectos genéricos de projeto
2.4.4 Expansão condicional
Outra facilidade geralmente associada a macro-instruções é a possibilidade de definir expansões con-
dicionais de trechos de código. Para tanto, uma outra pseudo-instrução, AIF, é definida. Para ilustrar
esse conceito, considere a situação na qual uma macro TOLOWER3 pudesse ser invocada especificando o
registrador D0 como destino,
38 TOLOWER3 CHARS_I,D0
Caso o corpo de TOLOWER3 fosse idêntico ao corpo de TOLOWER2, haveria na última linha da ex-
pansão da macro uma instrução desnecessária. Para evitar essa situação, TOLOWER3 poderia ser definida
como
39 TOLOWER3 MACRO &IN,&OUT
40 MOVE.L &IN,D0
41 ORI.L $20202020,D0
42 AIF (&OUT EQ ’D0’).FIM
43 MOVE.L D0,&OUT
44 .FIM ENDM
Na quarta linha da definição, aparece a instrução de expansão condicional. Após o código da operação
AIF, a condição entre parênteses verifica se o argumento de macro &OUT é igual a D0. Se esse for o caso, a
expansão continua na linha com o rótulo .FIM — rótulos de macro são precedidos pelo caráter ’.’ (ponto).
Caso contrário, a expansão continua na linha seguinte.
Assim, a seguinte seqüência de invocações
45 TOLOWER3 CHARS_I,CHARS_0
46 TOLOWER3 CHARS_I+4,D0
seria expandida para
47 MOVE.L CHARS_I,D0
48 ORI.L $20202020,D0
49 MOVE.L D0,CHARS_O
50 MOVE.L CHARS_I+4,D0
51 ORI.L $20202020,D0
No Apêndice C.17 apresenta-se uma facilidade de macro associada a uma linguagem de alto nível — o
pré-processador C.
2.5 Aspectos genéricos de projeto
As tarefas que devem ser desempenhadas por um montador dependem de características específicas do
processador, da sintaxe da linguagem simbólica e do tipo de carregador que será utilizado para a execução
do programa. Algumas funções são no entanto comuns a todos os montadores suportando as características
apresentadas, destacando-se:
� Ler um arquivo texto contendo instruções assembly, sendo capaz de identificar os vários campos de
cada linha.
� Realizar o pré-processamento de macros e de pseudo-instruções.
c
2000 DCA/FEEC/UNICAMP 20
Programação de Sistemas
Ivan L. M. Ricarte
Princípios de Montadores
2.5. Aspectos genéricos de projeto
� Determinar o comprimento de cada instrução. Para isso o montador precisa “conhecer” quantos
operandos são requeridos para cada instrução e os modos de endereçamento destes operandos.
� Substituir os mnemônicos de instruções e modos de endereçamento dos operandos pelos equivalentes
binários.
� Resolver e traduzir as expressões e os literais no campo de operandos para representações binárias.
� Determinar posições de memória, relativas ao programa, para os endereços simbólicos (rótulos).
� Substituir todas as referências a um endereço simbólico pelo endereço binário (efetivo) correspon-
dente.
� Gerar o resultado da montagem para algum arquivo, provavelmente usando algum formato interme-
diário.
Na seqüência, algumas dessas funcionalidades são detalhadas.
2.5.1 Leitura de arquivo fonte
A criação do programa assembly — a seqüência de instruções que será traduzida para código de má-
quina — é realizada através de qualquer programa editor. O arquivo fonte, resultado desse processo de
edição, é a entrada para o montador.
A primeira tarefa do montador é realizar a leitura desse arquivo fonte, cujo conteúdo é do tipo texto
(caracteres). Para manipular qualquer arquivo, é preciso primeiro especificar esse arquivo — em termos
computacionais, é preciso abrir o arquivo. Assim, alguma operação da forma OPENFILE(nome) deve
estar presente. Essa operação pode não ter sucesso — o arquivo especificado pode não ser encontrado ou
pode existir mas não oferecer permissão de acesso. Em caso de sucesso, o programa recebe uma referência
para a manipulação do arquivo.
Uma vez que o arquivo tenha sido aberto com sucesso, o próximo passo é realizar a leitura do seu con-
teúdo. Sob o ponto de vista do montador, a principal necessidade é realizar a leitura de cada linha, pois essa
é a unidade que delimita uma instrução. Assim, deve haver alguma operação da forma READLINE(arquivo),
cujo retorno é uma string contendo a linha lida.
Outras necessidades relacionadas à manipulação de arquivos incluem operações para detectar o fim do
arquivo, ENDOFFILE(arquivo), retornando um valor booleano (verdadeiro ou falso); para fechar o arquivo
após sua manipulação, CLOSEFILE(arquivo), que em princípio não precisa de nenhum valor de retorno; e
retornar ao início do arquivo, REWINDFILE(arquivo).
C: manipulação de arquivos
Um arquivo é um recurso manipulado pelo sistema operacional ao qual a linguagem de programação
tem acesso. É uma abstração que permite acessar convenientemente dispositivos externos (E/S), dos quais
teclado e tela são casos particulares.
Usualmente, para trabalhar com um arquivo, ele deve ser inicialmente aberto. Sua abertura indica
ao sistema operacional que o arquivo será manipulado, de forma que informação que agiliza o acesso
ao arquivo é mantida em memória. Após aberto, um arquivo é acessado através de um descritor, que
basicamente indica onde o sistema operacional mantém a informação sobre o arquivo.
Após aberto, operações de leitura e escrita podem ser efetuadas sobre o arquivo. Para que essas ope-
rações de transferência de dados tenham sucesso, é preciso que haja a permissão adequada para a operação.
Por exemplo, um teclado seria um dispositivo que não aceita saída de dados (escrita), mas apenas entrada
(leitura).
c
2000 DCA/FEEC/UNICAMP 21
Programação de Sistemas
Ivan L. M. Ricarte
Princípios de Montadores
2.5. Aspectos genéricos de projeto
Encerradas as operações sobre um arquivo, ele deve ser fechado. Isso permite que o sistema opera-
cional libere o espaço ocupado pelas informações sobre o arquivo para que esse mesmo espaço possa ser
reocupado para a manipulação de outros arquivos. Esta liberação é importante, uma vez que sistemas ope-
racionais tipicamente limitam a quantidade de arquivos que podem ser abertos simultaneamente devido a
restrições de espaço alocado para essas estruturas auxiliares.
Na linguagem de programação C, a informação sobre um arquivo é mantida em uma estrutura definida
no arquivo de cabeçalho stdio.h. Um programa C que vá manipular arquivos deve então incluirno
início de seu programa fonte a linha de inclusão desse arquivo de cabeçalho:
#include <stdio.h>
Esse arquivo de cabeçalho define o nome de tipo FILE associado a essa estrutura. Não é necessário
conhecer o formato interno dessa estrutura para manipular arquivos. O programador C, para acessar ar-
quivos, define variáveis ponteiros para este tipo, FILE *, que são manipuladas diretamente pelas funções
da biblioteca padrão de entrada e saída. Tais variáveis são usualmente chamadas de manipuladores de
arquivo.
Dando continuidade ao exemplo, a função que vai manipular o arquivo deve incluir a declaração de
uma variável manipulador de arquivo, como em:
FILE *arqFonte;
Para abrir um arquivo, a rotina fopen é invocada recebendo dois parâmetros. O primeiro é uma string
com o nome do arquivo que será aberto. O segundo parâmetro é outra string que especifica o modo de
acesso, que pode conter os caracteres r (leitura), w (escrita), a (escrita ao final — append), e b (acesso em
modo binário). O valor de retorno é o manipulador alocado para o arquivo aberto.
Por exemplo, para realizar a leitura do conteúdo de um arquivo chamado teste.asm, a seguinte
expressão poderia ser usada no programa:
arqFonte = fopen("teste.asm", "r");
Caso o arquivo não possa ser aberto, a função fopen retorna o ponteiro nulo. Assim, para verificar de
o arquivo foi aberto sem problemas, é necessário testar o valor de retorno:
if (arqFonte != 0) {
/* tudo bem */
}
else {
/* erro */
}
Quando o arquivo é aberto, a posição corrente (mantida internamente pelo sistema) é o início do ar-
quivo. A cada operação executada sobre o arquivo, essa posição é atualizada. O valor da posição corrente
pode ser obtido pela função ftell.
A função feof retorna um valor verdadeiro (inteiro diferente de 0) se a posição corrente para o arquivo
indicado é o final do arquivo, ou falso (inteiro igual a 0) em caso contrário.
A função fseek permite o modificar a posição corrente para um ponto arbitrário do arquivo. O pri-
meiro argumento dessa função é o manipulador do arquivo; o segundo, um valor long indicando o des-
locamento desejado; e o terceiro, um valor inteiro inidcando a referência para o deslocamento, que pode
ser o início do arquivo (SEEK_SET), a posição corrente (SEEK_CUR) ou o final do arquivo (SEEK_END).
Um valor de retorno 0 indica que a operação foi completada com sucesso. A função rewind retorna a
posição corrente para o início do arquivo, sem valor de retorno.
Para fechar um arquivo previamente aberto, a rotina fclose pode ser usada. Ela recebe como argu-
mento o manipulador do arquivo e não retorna nenhum valor. Assim, após encerrada a operação com o
arquivo a expressão fclose(arqFonte); fecha-o.
c
2000 DCA/FEEC/UNICAMP 22
Programação de Sistemas
Ivan L. M. Ricarte
Princípios de Montadores
2.5. Aspectos genéricos de projeto
2.5.2 Extração de campos da linha
As facilidades para a leitura de um arquivo texto restringem-se tipicamente à obtenção do conjunto de
caracteres de interesse — no caso do montador, uma linha do arquivo por vez. Para que o montador possa
realizar seu processamento, é preciso que ele esteja informado de como extrair os campos de interesse de
cada linha.
Dessa forma, pode-se definir um conjunto de operações que encapsulem essa funcionalidade, recebendo
como argumento uma string que é a linha sendo analisada:
GETLABEL(linha) para extrair o rótulo da linha, se presente;
GETOPERATION(linha) para extrair da linha o mnemônico de operação;
GETOPERAND(linha) para extrair o campo de operandos da linha, se presente.
O quarto campo de uma linha, de comentários, não é processado pelo montador. Assim, ele pode ser
simplesmente ignorado.
Quando um mnemônico de operação é obtido da linha de instrução, o seu tratamento vai depender do
tipo de operação que ele representa. Assim, uma pseudo-instrução demanda um tratamento diferenciado
daquele dispensado a uma instrução do processador ou a uma definição de macro.
C: obtenção do conteúdo de arquivos de texto
Para ler as linhas de um arquivo texto previamente aberto, a função fgets pode ser utilizada. Essa
função recebe três parâmetros. O primeiro é o endereço de um arranjo de caracteres que irá receber a linha
lida. O segundo é o número máximo de caracteres que deve ser lido da linha, caso a linha tenha mais
caracteres que essa quantidade. O terceiro parâmetro é o manipulador do arquivo de onde a linha será lida.
O retorno é um ponteiro para o início do arranjo com a linha, ou o ponteiro nulo caso o final do arquivo
tenha sido atingido.
O programa a seguir ilustra a manipulação de um arquivo texto em C usando essas funções. A função
main recebe o nome de um arquivo pela linha de comando e abre esse arquivo. Em caso de sucesso, o
conteúdo do arquivo é lido linha a linha (usando gets) e apresentado na tela (usando a função printf),
precedido do número da linha.
1 /* Apresenta arquivo com linhas numeradas */
2 #include <stdio.h>
3 #define TAMBUFF 120
4 int main(int argc, char* argv[]) {
5 FILE *arquivo;
6 char *linha;
7 char buffer[TAMBUFF];
8 int contLinha = 0;
9 if (argc != 2) {
10 fprintf(stderr, "%s requer um nome de arquivo\n", argv[0]);
11 return 1;
12 }
13 arquivo = fopen(argv[1],"r");
14 if (arquivo == 0) {
15 fprintf(stderr,
16 "Arquivo %s nao pode ser aberto para leitura\n", argv[1]);
17 return 2;
18 }
19 while (!feof(arquivo)) {
c
2000 DCA/FEEC/UNICAMP 23
Programação de Sistemas
Ivan L. M. Ricarte
Princípios de Montadores
2.5. Aspectos genéricos de projeto
20 linha = fgets(buffer,TAMBUFF,arquivo);
21 if (linha != 0)
22 printf("%3d: %s", ++contLinha, linha);
23 }
24 fclose(arquivo);
25 return 0;
26 }
C: manipulação de strings
O arquivo de cabeçalho string.h contém a declaração de diversas funções para a manipulação de
strings em C. Strings em C são arranjos de caracteres, sendo usualmente manipuladas através do ponteiro
para o início do arranjo. O fim da string é indicado pela presença do caráter NUL (’\0’).
O exemplo a seguir ilustra uma função para extrair rótulos de uma linha. A linha é uma string que é
passada como argumento para a função. O rótulo, se presente na linha, tem início logo na primeira coluna,
sendo terminado por um espaço em branco.
1 #define LABELSIZE 10
2 char* getLabel(char* linha) {
3 static char labelBuf[LABELSIZE];
4 int pos = 0;
5 if (linha[0] == ’ ’)
6 return 0;
7 else
8 while (linha[pos] != ’ ’)
9 labelBuf[pos] = linha[pos++];
10 labelBuf[pos] = ’\0’;
11 return labelBuf;
12 }
Uma simples adaptação no programa que manipula arquivos, em combinação com o uso dessa função,
permite criar um programa que identifica rótulos em um arquivo texto. Tendo como argumento o seguinte
arquivo texto:
CHARS_I EQU $0400
CHARS_O EQU $0600
ORG $1000
PROG ...
MOVE.L CHARS_I,D0
ORI.L $20202020,D0
MOVE.L D0,CHARS_O
...
MOVE.L CHARS_I,D0
ORI.L $20202020,D0
MOVE.L D0,CHARS_O
...
END PROG
o resultado da execução do programa é:
Rotulo CHARS_I na linha 1
Rotulo CHARS_O na linha 2
Rotulo PROG na linha 4
c
2000 DCA/FEEC/UNICAMP 24
Programação de Sistemas
Ivan L. M. Ricarte
Princípios de Montadores
2.5. Aspectos genéricos de projeto
Na função getLabel, tanto a linha (argumento) como o rótulo resultante (valor de retorno) são pas-
sados através de ponteiros. A manipulação de ponteiros é fonte usual de confusão em qualquer linguagem.
Por exemplo, tendo duas variáveis ponteiros char* s1 e char* s2 indicando o início de duas strings,
não seria possível copiar o conteúdo de s2 para s1 simplesmente por atribuição,
s1 = s2; /* copia o endereco! */
ou comparar seus conteúdos diretamente,
if (s1 != s2) /* compara os enderecos! */
...
Assim, diversas rotinas são suportadas para manipular strings na biblioteca padrão, tais como:
#include <string.h>
char *strcat(char *s1, const char *s2);
char *strncat(char *s1, const char

Continue navegando