domingo, 16 de mayo de 2010

Librerías Compartidas en Linux con el Compilador C de GNU

Uno de los aprendizajes que más impactó mi forma de programar han sido las librerías compartidas de linux (dynamic shared objects), con esta entrada sólo quiero dejarnos lo más básico de cómo crearlas y usarlas.

Haré ejemplos muy cortos, quizá de poca utilidad práctica, pero espero dejar sentados los mínimos conceptos que necesitamos manejar para el 80% de los casos reales. Empezaré creando una librería para dos operaciones con vectores, el producto escalar y el producto vectorial. Exportaremos lo que se puede exportar de un librería compartida, variables y funciones:

Archivo mivector.h:
#ifndef _vector_h
#define _vector_h

double productoEscalar(const double* p_vector1, const double* p_vector2);
void productoVectorial(const double* p_vector1, const double* p_vector2, double* result);

#endif


Archivo mivector.c
#include "mivector.h"
#include <stdio.h>

double vec1[3];
double vec2[3];

void inicializar(void) {
puts(">>>> colocar aqui código inicialización");
for (int i=0; i<3; ++i) {
vec1[i] = 2.0;
vec2[i] = 2.0;
}
}

void finalizar(void) {
puts("colocar aqui código finalización >>>>");
}

double productoEscalar(const double* p_vector1, const double* p_vector2) {
double result = 0;
for (int i=0; i<3; ++i)
result += p_vector1[i] * p_vector2[i];
return result;
}

void productoVectorial(const double* p_vector1, const double* p_vector2, double* result) {
result[0] = p_vector1[1]*p_vector2[2]-p_vector1[2]*p_vector2[1];
result[1] = -p_vector1[0]*p_vector2[2]+p_vector1[2]*p_vector2[0];
result[2] = p_vector1[0]*p_vector2[1]-p_vector1[1]*p_vector2[0];
}


Copien este código, los dos archivos deben estar en la misma carpeta, deberan compilarlos usando la siguiente linea de comando:

%> gcc -Wl,-init=inicializar,-fini=finalizar,-soname=libmivector.so -o libmivector.so -std=c99 -fpic -shared -shared-libgcc mivector.c


No deberiamos dejar los parámetros de esta linea de comando sin explicar:

  • -Wl
    Especificar las opciones al linkeditor desde la línea de comando del compilador
    La opción -init, permite especificar el nombre de una función que deberá ser llamada en el momento en el que un proceso carga la librería
    La opción -fini permite especificar el nombre de una función que deberá ser llamada en el momento en el que un proceso descarga la librería
    La opción -soname asigna el nombre especificado de la librería dentro del archivo compilado

  • -o <nombre librería>
    El producto de la compilación quedará en un archivo del nombre especificado en el parámetro de eta opción

  • -std=c99
    Usar el estandar C99 de lenguaje C al momento de compilar el código

  • -fpic
    Es una opción para optimizar el código ejecutable relativo al acceso a la tabla global de desplazamientos (GOT), donde se registran todos los nombres de las funciones y variables que se pueden usar

  • -shared
    Produce como salida una librería compartida (un archivo .so)

  • -shared-libgcc
    Usar la librería libgcc que viene como una librería compartida



Pueden usar el comando "file" de linux para comprobar la naturaleza de esta archivo
%>file libmivector.so


Ahora veamos como usar la librería, existen dos escenarios:

  • Si queremos que la librería se cargue implícitamente al momento de invocar el programa principal

  • Si el programador debe cargar explícitamente la librería y localizar las funciones a invocar


Los dos escenarios tienen utilidad, el primero acopla más la librería al código del programa principal, pero es más optima y fácil de programar, la segunda es mas tediosa de programar pero desacopla al programa principal de la librería y permite añadirle extensiones sin necesidad de recompilarlo.

Carga Implícita de la librería
El programa principal asume que los nombres en la librería están disponibles. Veamos cómo sería:

Archivo ppal1.c
#include <stdio.h>
#include "mivector.h"

extern double vec1[3];
extern double vec2[3];
double result[3];

int main(int argc, char** argv) {
printf("Producto Escalar: %f\n", productoEscalar(vec1, vec2));
productoVectorial(vec1, vec2, result);
{
int i;
puts("Producto Vectorial");
for (i=0; i<3; ++i)
printf("[%d]=%f\n", i, result[i]);
}
return 0;
}


Compilen este programa con la siguiente linea de comando:
gcc ppal.c -lmivector -L. -o ppal


Expliquemos nuevamente las opciones usadas en la linea de comando:

  • -L Especifica las rutas desde donde deberán buscarse los archivos de librerías compartidas. En este caso sólo el directorio actual

  • -o Especifica el nombre del archivo producto de la compilacion-linkedición.

  • -lmivector
    Buscar en las rutas especificadas por la opcion "-L" una libreria compartida que tenga el nombre "libmivector.so".



Esto genera el programa ejecutable "ppal", sin embargo al ejecutarlo, ocurre lo siguiente:
%> ./ppal
./ppal: error while loading shared libraries: libmivector.so: cannot open shared object file: No such file or directory


Para resolver este problema debemos indicar al sistema operativo la ruta donde debe buscar por las librerías compartidas, esto se hace mediante la variable de ambiente LD_LIBRARY_PATH:
%> export LD_LIBRARY_PATH=.

Asumiendo que nuestro shell es Bash, le estamos diciendo que nuestra libreria compartida "mivector" se encuentra en el directorio actual.

Ahora si:
%> ./ppal
>>>> colocar aqui código inicialización
Producto Escalar: 12.000000
Producto Vectorial
[0]=0.000000
[1]=0.000000
[2]=0.000000
colocar aqui código finalización >>>>


Carga Explícita de la librería
En este caso nuestro programa principal no se enlazó con la librería y por lo tanto no conoce nada de la misma ni de los símbolos que la misma exporta, por lo tanto debemos buscarlos para poder usarlos:

Archivo ppal2.c
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

double *vec1;
double *vec2;
double result[3];

double (*productoEscalar)(const double* p_vector1, const double* p_vector2);
void (*productoVectorial)(const double* p_vector1, const double* p_vector2, double* result);

int main(int argc, char** argv) {
void* module=dlopen("libmivector.so", RTLD_LAZY);
if (!module) {
puts("no se pudo encontar la librería \"mivector\"");
exit(1);
}

productoEscalar = dlsym(module, "productoEscalar");
productoVectorial = dlsym(module, "productoVectorial");

vec1 = dlsym(module, "vec1");
vec2 = dlsym(module, "vec2");

printf("Producto Escalar: %f\n", productoEscalar(vec1, vec2));

productoVectorial(vec1, vec2, result);
{
int i;
puts("Producto Vectorial");
for (i=0; i<3; ++i)
printf("[%d]=%f\n", i, result[i]);
}

dlclose(module);
return 0;
}


Para abrir la librería se usa la función dlopen, una vez abierta debemos asegurarnos de cerrarla con dlclose.
Nótese cómo el código fuente no incluye la cabecera de la librería "mivector" y la dirección de los símbolos, ya sean variables o funciones, deben obtenerse explícitamente con la función dlsym para poder usarlos puesto que no se enlazaron estáticamente al ejecutable.

Compilar con la siguiente línea de comando:
%> gcc ppal2.c -ldl -o ppal

Nótese como el programa no se enlaza con la librería "mivector", por otro lado, ¿qué es esa librería "libdl.so"?, se trata de la librería que permite cargar las librerías y obtener las direcciones de sus símbolos.

En este caso deben también asignar un valor adecuado a la variable de ambiente LD_LIBRARY_PATH, de modo que la función dlopen pueda encontrar la librería "mivector", de lo contrario el programa terminará su ejecución.

Referencias:

Seguidores