arquitectura de computadores

Propaganda
ARQUITECTURA DE
COMPUTADORES
CAPÍTULO II
AULA X
Ricardo Mendão Silva
[email protected]
Índice
• Traduzindo e iniciando uma aplicação
•
•
•
•
•
•
Compiladores
Assembladores
Linkers
Loaders
DLLs
Iniciando um programa em Java
Ricardo Mendão Silva
[email protected]
Traduzindo uma aplicação
• Após toda a matéria abordada nesta disciplina, chega o
momento de analisarmos ao detalhe como passamos, de facto,
de um excerto de código presente num ficheiro local, para
uma aplicação em execução.
Ricardo Mendão Silva
[email protected]
Traduzindo uma aplicação
• Numa abordagem de alto nível:
1. O programa é compilado para linguagem assembly e assemblado num
objecto (módulo) em linguagem máquina (010101010…).
2. O linker combina vários módulos com bibliotecas (de rotinas) para
resolver todas as referências,
3. O loader colocar o código máquina nos devidos locais de memória
para serem executados pelo processador.
• Para acelerar o processo alguns compiladores directamente
os módulos, enquanto que noutros casos são utilizados
linking-loaders, que executam as duas últimas acções numa
só.
Ricardo Mendão Silva
[email protected]
Traduzindo uma aplicação
• Para identificar os diferentes ficheiros, o UNIX segue a
seguinte convenção:
UNIX
MS-DOS
Ficheiros de código C
.c
.C
Ficheiros de assembly
.s
.ASM
Objectos compilados
.o
.OBJ
bibliotecas estáticas
.a
.LIB
bibliotecas dinâmicas
.so
.DLL
Executáveis
a.out
.EXE
Ricardo Mendão Silva
[email protected]
Índice
• Traduzindo e iniciando uma aplicação
•
•
•
•
•
•
Compiladores
Assembladores
Linkers
Loaders
DLLs
Iniciando um programa em Java
Ricardo Mendão Silva
[email protected]
Traduzindo uma aplicação
Compilador C
• O compilador transforma os programas em C em programas em
linguagem assembley.
• Como já sabemos, linguagem assembley acaba por ser uma forma
simbólica entendida pela máquina.
• Como também já sabemos, linguagens de programação de alto
nível requerem muito menos linhas de código do que a
linguagem assembley, o que permite aumentar bastante a
produtividade.
• Em 1975 diversos sistemas operativos e assembladores foram
escritos em linguagem assembly porque as memórias eram
pequenas e os compiladores ineficientes.
• Actualmente as memórias avançaram bastante, tanto em
capacidade, velocidade como em preço, e os compiladores
tornaram-se tão eficientes quanto qualquer programador
profissional de assembley.
Ricardo Mendão Silva
[email protected]
Índice
• Traduzindo e iniciando uma aplicação
•
•
•
•
•
•
Compiladores
Assembladores
Linkers
Loaders
DLLs
Iniciando um programa em Java
Ricardo Mendão Silva
[email protected]
Traduzindo uma aplicação
Assemblador
• Uma vez que a linguagem assembly é a interface entre o
hardware e as linguagens de alto nível, o assemblador
consegue tratar variações de linguagem máquina, como se de
instruções reais se tratassem – pseudo-instruções.
• move $t0,$t1
 add $t0,$zero,$t1
• Deste modo o assemblador acaba por nos fornecer mais
instruções do que aquelas suportadas pelo hardware,
facilitando assim a programação.
• Para além disso, os assembladores ainda aceitam uma série
de variantes numéricas, não só binário ou decimal, mas
também hexadecimal.
Ricardo Mendão Silva
[email protected]
Traduzindo uma aplicação
Assemblador
• Apesar das “facilidades” apresentadas no slide anterior, a
verdadeiras função do assemblador é mesmo converter as
instruções em código máquina.
• O assemblador, ao assembler assembley, cria um objecto (um
ficheiro) que contem não só o código máquina
correspondente, mas também os dados e a informação
necessária para colocar as instruções em memória.
• Para criar a versão binária (código máquina) o assemblador
necessita de determinar todos os endereços referentes às
labels existentes no código, branches e instruções de
transferência de dados.
• Para tal essa informação é guardada numa tabela de símbolos.
Ricardo Mendão Silva
[email protected]
Traduzindo uma aplicação
Assemblador
Estrutura Object file em UNIX
Cabeçalho (object file header)
Descreve o tamanho e posição de
outras peças do objecto.
Segmento de texto
Contem o código em linguagem
máquina.
Segmento de dados estáticos
Contem os dados alocados para toda a
“vida” do programa. Em UNIX é
possível utilizar alocação dinâmica.
Informação de realocação
Identifica instruções e dados que
dependem de endereços absolutos
quando o programa é carregado em
memória.
Tabela de símbolos
Contem as restantes labels que não
estão definidas, nomeadamente
referências externas.
Informação de debug
Contem uma descrição de como os
módulos foram compilados, permitindo
aos debuggers associar o código
máquina aos ficheiro em C, mantendo
as estruturas de dados legíveis.
Ricardo Mendão Silva
[email protected]
Índice
• Traduzindo e iniciando uma aplicação
•
•
•
•
•
•
Compiladores
Assembladores
Linkers
Loaders
DLLs
Iniciando um programa em Java
Ricardo Mendão Silva
[email protected]
Traduzindo uma aplicação
Linker
• O método que apresenta-mos leva-nos a querer que cada
linha de código alterada obriga à recompilação de todo o
código.
• Essa recompilação seria um gasto de recursos pesadíssimo,
principalmente na compilação de bibliotecas, cujas rotinas
quase nunca mudam.
• Como tal, a alternativa é compilar cada procedimento
independente, de modo a que a alteração de uma linha
obrigue somente à recompilação de um procedimento.
• Esta alternativa obriga à existência de um novo
interveniente chamado linker ou link editor.
• O Linker basicamente pega em todos os blocos compilados e
aglomera-os numa solução única
Ricardo Mendão Silva
[email protected]
Traduzindo uma aplicação
Linker
• Existem três etapas no modo de funionamento do Linker:
1.
2.
3.
Coloca o código e os dados simbolicamente em memória.
Determina os endereços das labels de dados e de instruções.
Junta as referências internas com as externas.
• O Linker utiliza a informação de realocação e a tabela de
simbolos em cada objecto para resolver qualquer label
indefinida, tal como, branches, jumps e endereços de
memória.
• O Linker funciona assim como um editor que procura os
novos endereços e substitui pelos antigos.
• É muito mais rápido aplicar um patch do que recompilar ou
reassemblar todo o código.
Ricardo Mendão Silva
[email protected]
Traduzindo uma aplicação
Linker
• Quando todas as referências externas estiverem resolvidas,
o Linker determina as localização de memória que cada
modulo irá ocupar.
• Uma vez que os ficheiros são essemblados isoladamente, o
assemblador não sabe onde as instruções e os dados de um
modulo são colocados relativamente aos restantes.
• Assim, quando o Linker coloca um modulo em memória, todas
as referências absolutas (endereços de memória não
relativos a registos) são realocadas.
• Por fim, o Linker produz um ficheiro executável que pode
correr num computador. Tipicamente, este executável tem o
mesmo formato que o ficheiro objecto, mas sem referências
indefinidas.
Ricardo Mendão Silva
[email protected]
Traduzindo uma aplicação
Linker
Ricardo Mendão Silva
[email protected]
Traduzindo uma aplicação
Linker
Ricardo Mendão Silva
[email protected]
Índice
• Traduzindo e iniciando uma aplicação
•
•
•
•
•
•
Compiladores
Assembladores
Linkers
Loaders
DLLs
Iniciando um programa em Java
Ricardo Mendão Silva
[email protected]
Traduzindo uma aplicação
Loader
• Uma vez que o executável já se encontra no disco o Sistema
Operativo lê o mesmo para memória e inicia-o, seguindo os
seguintes passos:
1. Lê o cabeçalho para determinar o tamanho do texto e dos segmentos
de dados.
2. Aloca memória necessária para o texto e dados.
3. Copia a instrução e os dados do executável para memória.
4. Copia os parâmetros (se existirem) do programa para a stack.
5. Inicia os registos e coloca o stack pointer a apontar para a
primeira localização livre.
6. Salta para uma rotina de iniciação que copia os parâmetros para os
registos de argumentos e chama a rotina principal do programa.
7. Quando a rotina principal retorna, a rotina de iniciação termina o
programa com um exit system call.
Ricardo Mendão Silva
[email protected]
Índice
• Traduzindo e iniciando uma aplicação
•
•
•
•
•
•
Compiladores
Assembladores
Linkers
Loaders
DLLs
Iniciando um programa em Java
Ricardo Mendão Silva
[email protected]
Traduzindo uma aplicação
Dynamically Linked Libraries
• Como vimos anteriormente, as bibliotecas podem ser de dois
tipos: estáticas e dinâmicas.
• Bibliotecas estáticas continuam a ser a forma mais rápida de
chamar rotinas, no entanto têm várias desvantagens,
nomeadamente:
• A biblioteca faz parte do código, logo se uma nova versão da
biblioteca for lançada o código manterá em uso a versão antiga até
ser novamente compilado.
• Todas as rotinas são carregadas no executável, independentemente se
são ou não utilizadas. Isto provoca executáveis densos não por causa
do próprio código, mas devido às bibliotecas que incluem.
• Tais desvantagens levaram a que surgissem bibliotecas dinâmicas,
ou popularmente conhecidas como dynamically linked libraries
(DLLs).
• Nas DLLs as rotinas não são nem ligadas nem carregadas até o
programa iniciar a sua execução.
Ricardo Mendão Silva
[email protected]
Traduzindo uma aplicação
Dynamically Linked Libraries
• Como tal, as DLLs são bibliotecas, cujas rotinas são carregadas
durante a execução do programa, conforme necessário.
• Inicialmente, nas primeiras versões das DLL, o Loader corria um
Linker dinâmico que, com base na informação extra existentes,
achava as bibliotecas necessárias e actualizava as referências
externas.
• A desvantagem deste método era que ele continuava a ligar todas
as rotinas que poderiam ser chamadas, em vez de ligar somente as
que eram mesmo chamadas durante a execução.
• Para resolver essa questão foi desenvolvida uma segunda versão
de DLLs onde cada rotina somente é ligada depois de ser invocada
(lazy procedure linkage).
Ricardo Mendão Silva
[email protected]
Traduzindo uma aplicação
Dynamically Linked Libraries
• Este processo é baseado num salto incondicional. Na primeira
execução o comando é passado para o loader/linker que liga e
carrega a rotina desejada e só no fim de carregada o comando
volta à execução da instrução em causa. Nas instruções seguintes
a rotina já está carregada e pode ser utilizada directamente.
Ricardo Mendão Silva
[email protected]
Índice
• Traduzindo e iniciando uma aplicação
•
•
•
•
•
•
Compiladores
Assembladores
Linkers
Loaders
DLLs
Iniciando um programa em Java
Ricardo Mendão Silva
[email protected]
Traduzindo uma aplicação
Iniciando um programa em Java
• O exemplo que vimos anteriormente funciona para ISAs especificas
e é utilizado quando pretendemos máxima performance sobre
plataformas especificas de hardware.
• Em Java o cenário muda um pouco, sendo uma plataforma desenhada
para correr em qualquer hardware.
• Em vez de ser compilado para uma qualquer versão de assembly, o
código Java é primariamente compilado para instruções mais
fáceis de interpretar numa arquitectura chamada Java bytecode
instruction set.
• Esta arquitectura foi desenhada para ser aproximada da linguagem
Java, permitindo assim uma compilação fácil.
Ricardo Mendão Silva
[email protected]
Traduzindo uma aplicação
Iniciando um programa em Java
• Tal como no C, o compilador Java verifica os tipos de dados e
produz as operações necessárias para cada tipo.
• Os programas em java são distribuídos na versão binária de
bytecodes.
Ricardo Mendão Silva
[email protected]
Traduzindo uma aplicação
Iniciando um programa em Java
• Um software de interpretação chamado Java Virtual Machine (JVM)
consegue executar Java bytecodes.
• Um interpretador é um programa que simula uma instruction set
architecture.
• Nestes casos não existe a necessidade de utilizar um assemblador
separado, visto que a tradução é tão simples que o próprio
compilador consegue obter os endereços em falta e/ou a JVM
encontra-los em runtime.
Ricardo Mendão Silva
[email protected]
Traduzindo uma aplicação
Iniciando um programa em Java
• A vantagem deste método é a portabilidade. Uma vez instalada a
JVM, qualquer máquina pode corre aplicações escritas em Java.
• A desvantagem deste tipo de soluções é claramente a performance,
apesar de os últimos avanços na tecnologias não o fazerem
transparecer tanto. Porém, a performance continua ainda a ser 10
vezes mais lenta quando comparada com aplicações em C.
Ricardo Mendão Silva
[email protected]
Traduzindo uma aplicação
Iniciando um programa em Java
• Para preservar a portabilidade e melhorar a velocidade de
execução, a fase seguinte no Java passou pelo desenvolvimento de
compiladores que traduziam em runtime – Just in Time compilers.
• Este tipo de compiladores criava um perfil do programa através
dos métodos mais importantes. A solução passava depois por
compilar esses métodos mais usados para linguagens máquina,
tornando-os assim mais optimizados. Ao mesmo tempo guardava essa
compilação para que nas execuções futuras fosse mais rápido.
Ricardo Mendão Silva
[email protected]
Dúvidas e Questões
Ricardo Mendão Silva
Maio 2014
[email protected]
Ricardo Mendão Silva
[email protected]
Download