Introdução

Todos os programas passam por quatro etapas de transformação desde o código-fonte armazenado no computador até a etapa que este código será executado na máquina. A Figura 1 abaixo mostra essas etapas de tradução, alguns sistemas combinam essas etapas, porém essas são consideradas as quatro etapas lógicas pelas quais os programas passam.

Etapas de Tradução de uma Linguagem de Programação

Figura 1: Etapas de Tradução de uma Linguagem de Programação

Basicamente o processo de tradução e execução de uma linguagem de alto nível começa com um programa em linguagem de alto nível sendo compilado para um programa em assembly, e após essa operação ele é montado, através de um montador, em um módulo objeto em linguagem de máquina. O próximo processo fica por conta do link-editor que combina vários módulos com as rotinas de biblioteca para resolver todas as referências. Para finalizar, o Loader é encarregado de colocar o código de máquina nos locais apropriados da memória, para ser executado pelo processador. Vale salientar que em alguns sistemas pode acontecer de algumas etapas serem puladas ou combinadas para agilizar o processo. Por exemplo, alguns compiladores poderiam produzir o módulo objeto diretamente ou loaders poderiam ser utilizados juntos com o link-editor. Para a identificação correta dos arquivos, o Linux e Unix utilizam os sufixos “.java” para arquivos em Java, “.s” para os arquivos em assembly, “.o” para os arquivos objetos, “.so” para rotinas de bibliotecas link-editadas estaticamente e “.out” para os arquivos executáveis. Já os sistemas Windows utilizam os sufixos “.java” para arquivos em Java, “.asm” para os arquivos em assembly, “.obj” para os arquivos objetos, “.lib” e “.dll” para rotinas de bibliotecas link-editadas estaticamente e “.exe” para os arquivos executáveis.

No restante do artigo verificaremos cada uma dessas etapas e posteriormente finalizaremos o assunto em outro artigo mostrando como o Java entra nesse processo, no entanto primeiramente precisaremos entender como funcionam as etapas básicas no processo de tradução.

Compilador

Um compilador basicamente transforma um programa “.java” ou “.c” em um programa assembly que é a forma simbólica de como a máquina entende. Os programas em alto nível como C e Java utilizam muito menos código do que o assembly, isso aumenta significativamente a produtividade do programador. Isso ocorre porque o assembly possui um número reduzido de instruções que precisam ser combinadas para realizar o que uma única instrução em código alto nível realiza.

No meio da década de 70 os sistemas operacionais e montadores eram todos escritos em assembly, pois as memórias eram pequenas e os compiladores eram ineficientes. Com o aumento significativo das memórias e os compiladores otimizadores de hoje que podem produzir programas em assembly quase tão bem quanto um especialista em assembly, temos que as linguagens de alto nível tomaram conta do mercado, sendo utilizadas em sistemas operacionais, sistemas aplicativos, utilitários, etc.

Montador

Assim como o assembly é a interface com o software de nível superior, o montador converte a instrução em assembly para o equivalente em linguagem de máquina. Portanto, a função do montador é transformar um programa em assembly em um arquivo objeto, que é uma combinação de instruções de linguagem de máquina, dados e informações necessárias para colocar instruções corretamente na memória. Cada instrução no programa assembly será convertida em uma versão binária da instrução.

O arquivo objeto normalmente contém seis partes distintas:

  • O cabeçalho do arquivo objeto que contém o tamanho e a posição de outras partes do arquivo objeto;
  • O segmento de texto que possui o código na linguagem de máquina;
  • O segmento de dados estáticos que contém os dados alocados por toda a vida do programa;
  • As informações de relocação que identificam instruções e words de dados que dependem de endereços absolutos quando o programa é carregado na memória;
  • A tabela de símbolos que contém os rótulos restantes que não estão definidos, como as referências externas;
  • As informações de depuração que contém uma descrição resumida de como os módulos foram compilados, para que o depurador possa associar as instruções de máquina aos arquivos fonte e tornar as estruturas de dados legíveis.

Link-editor

A função do link-editor é juntar as rotinas que já foram montadas, como as rotinas de biblioteca. Também é função do link-editor compilar e montar cada procedimento de forma independente, de modo que uma mudança em uma linha só exija a compilação e a montagem de um procedimento. Isso evita que uma única mudança em uma linha de um procedimento exigisse a compilação e montagem de um programa inteiro. Efetuar novamente a tradução completa do programa seria um desperdício computacional enorme.

As três etapas realizadas por um link-editor são:

  • Colocar os módulos de código e dados simbolicamente na memória;
  • Determinar os endereços dos rótulos de dados e instruções;
  • Remendar as referências internas e externas.

O link-editor produz um arquivo executável que pode ser executado em um computador. Normalmente esse arquivo possui o mesmo formato de um arquivo-objeto, exceto que não contém referências não resolvidas.

Nota-se que é o Link-Editor quem faz realmente o arquivo executável final e não o montador como muitos pensam.

Carregador (Loader)

Agora que o arquivo executável foi gerado, o sistema operacional lê esse arquivo executável para a memória e o inicia. Abaixo segue um exemplo das etapas seguidas pelo Carregador num sistema Unix e Linux:

  • Lê o cabeçalho do arquivo executável para determinar o tamanho dos segmentos de texto e de dados;
  • Cria um espaço de endereçamento grande o suficiente para o texto e os dados;
  • Copia as instruções e os dados do arquivo executável para a memória;
  • Caso exista, copia os parâmetros do programa principal para a pilha;
  • Inicializa os registradores da máquina e define o stack pointer para o primeiro local livre;
  • Desvia para uma rotina de partida, que copia os parâmetros para os registradores de argumentos e chama a rotina principal do programa. Quando a rotina principal retorna, a rotina de partida termina o programa com uma chamada ao sistema exit.

Conclusão

Neste artigo vimos os procedimentos básicos de tradução e execução de um programa. Analisamos melhor como funciona um compilador, montador, link-editor e um carregador e como se dá todo o processo de tradução até a execução de um programa.

Bibliografia

  • Organização e Projeto de Computadores. David Patterson, John Hennessy. Elsevier, 2005.
  • Organização de Computadores. Tennenbaum. Addison Wesley, 2010.