next up previous contents
Next: Exercícios Up: Carregadores e ligadores Previous: Bibliotecas   Sumário


Carregamento e Ligação Dinâmicos

Os esquemas de ligação e carregamento apresentados até o momento assumem que o módulo executável, uma vez carregado a uma área da memória principal, será o ``proprietário'' desta área até o fim de sua execução. Em sistemas multiusuários mais recentes, não é isto o que ocorre. Programas em execução podem ser retirados da memória (swaped-out) e depois retornar à memória (swap-in) em outra posição diferente daquela na qual estava executando inicialmente.

Para atender a este tipo de necessidade, utilizam-se esquemas de ligação e carregamento dinâmico. O princípio básico destes esquemas é que referências a endereços (de dados ou de instruções) são mantidos na forma relativa até o momento em que eles são realmente necessários, ou seja, até o momento de execução da instrução que contém esta referência. Usualmente, esta funcionalidade deve ter parte suportada em hardware de modo a não haver degradações sensíveis de desempenho.

Há dois esquemas básicos de ligação dinâmica, em tempo de carregamento (load-time) ou em tempo de execução (run-time). Na ligação dinâmica em tempo de carregamento, o módulo de carga primário (módulo da aplicação) é inicialmente transferido para a memória. Qualquer referência neste módulo para módulos externos (módulos alvos) faz com que o carregador procure cada módulo alvo, carregue-o para a memória e altere as referências para um endereço relativo em memória a partir do início do módulo da aplicação.

Entre as vantagens neste esquema de carregamento pode-se destacar:

A diferença para a ligação dinâmica em tempo de execução está no fato de que o carregamento e a resolução de referências são retardados até o momento em que a instrução com a referência ao módulo externo é executada. As referências a módulos externos continuam presentes na aplicação, mas se em alguma execução a lógica de fluxo de controle fizer com que aquela referência não seja executada, então o módulo alvo não será carregado à memória por aquela aplicação. As vantagens descritas para o esquema de ligação dinâmica em tempo de carregamento continuam válidas também neste caso.

Em versões mais recentes do sistema operacional Linux os módulos objeto e de carga estão principalmente em formato ELF (Executable and Linking Format). Bibliotecas para este tipo de arquivos são denominadas bibliotecas dinâmicas ainda ou arquivos de objetos compartilhados, que são diferenciadas das bibliotecas estáticas por sua extensão -- estáticas têm extensão .a (archive) e dinâmicas têm extensão .so (shared objects).

Sob o ponto de vista do programador usuário, não há diferença no procedimento para uso de rotinas em bibliotecas estáticas ou dinâmicas -- da mesma forma, rotinas em bibliotecas padrões são automaticamente buscadas e outras bibliotecas deverão ser especificadas através da chave -l. A diferença está na forma de operação interna do ligador e carregador, que utiliza a interface de programação (API) associado ao formato ELF.

ELF apresenta uma API que pode ser utilizada em programas do sistema desenvolvidos em C para manipular objetos em biblioteca compartilhadas durante a execução de um programa. Essas rotinas, disponibilizadas através da biblioteca libdl.so, são:

  #include <dlfcn.h>
  void       * dlopen  (const char *filename, int flag);
  const void * dlsym   (void *handle, const char *symbol);
  int          dlclose (void *handle);
  const char * dlerror (void);

A rotina dlopen disponibiliza uma biblioteca dinâmica para o programa em execução -- em outros termos, as rotinas no arquivo especificados são mapeadas para o espaço de endereçamento do processo em execução. O seu valor de retorno é um ponteiro (handle), utilizado nas chamadas posteriores de manipulação da biblioteca. O argumento flag indica quando deverá se dar o carregamento. Se tiver o valor RTLD_NOW (uma constante definida no arquivo dlfcn.h), o carregamento deverá ser imediato, ou seja, ao retornar dessa rotina a biblioteca já estará carregada na memória. Caso o valor especificado seja RTLD_LAZY, o carregamento será postergado até o momento em que houver (e se houver) a um símbolo dessa biblioteca. Em caso de erro, o apontador nulo será retornado.

A rotina dlsym retorna o endereço do símbolo especificado (variável ou função) que está disponível na biblioteca compartilhada que foi aberta. Em caso de erro, o apontador nulo será retornado.

Quando a biblioteca compartilhada não é mais necessária, ela é liberada através da invocação da rotina dlclose, que retorna 0 em caso de sucesso.

Em qualquer situação de erro, a rotina dlerror pode ser invocada para obter uma string com o diagnóstico do erro.

O exemplo a seguir ilustra como a função cos pode ser dinamicamente carregada da biblioteca libm.so usando ELF em linux:
\begin{listing}{1}
...

Para criar uma biblioteca compartilhada, inicialmente é preciso gerar um módulo objeto que possa ser carregado dinamicamente. Para tanto, o código gerado deve ser independente de posição. O compilador gcc permite a criação desse tipo de código de forma automática, através do uso da chave -fPIC (de position-independent code):

 > gcc -fPIC -c arqmat.c
 > gcc -fPIC -c convexp.c

Para criar a biblioteca dinâmica contendo os objetos compartilhados, o próprio compilador é utilizado:

 > gcc -shared -o libmyd.so arqmat.o convexp.o

A chave -shared indica para o programa gcc que o programa que está sendo gerado (indicado pela opção -o) é uma biblioteca compartilhada cujos módulos podem ser carregados e ligados dinamicamente. Caso algum desses módulos faça referências a arquivos em outras bibliotecas dinâmicas, é possível tornar transparente para o usuário a necessidade de se carregar essa outra biblioteca especificando-a no momento da criação. Por exemplo, se arqmat.ofaz uso de rotinas em libm.so, a linha de comando

 > gcc -shared -o libmyd.so arqmat.o convexp.o -lm
fará com que libm.so seja automaticamente carregada quando libmyd.so for especificada.

O padrão em sistemas operacionais modernos é utilizar arquivos compartilhados com carregamento e ligação dinâmica. O compilador gcc inclui a opção -static caso seja necessário criar um executável ligado estaticamente, mudando assim o comportamento padrão.


next up previous contents
Next: Exercícios Up: Carregadores e ligadores Previous: Bibliotecas   Sumário
Ivan L. M. Ricarte 2003-02-14