creación de librerías

Anuncio
Curso de Sistemas Operativos
Creación de Librerías
CURSO DE SISTEMAS OPERATIVOS
CREACIÓN DE LIBRERÍAS
EN GNU/LINUX
www.ciberia.ya.com/eingenieria/ssoo
David Villa Alises <[email protected]>
(VERSIÓN DEL DOCUMENTO: 1.1)
1.− INTRODUCCIÓN
Las librerías son una forma sencilla y potente de modularizar y reutilizar código. En este
documento se aborda el proceso de creación de librerías tanto estáticas como dinámicas, sus
posibles usos y los recursos mínimos necesarios para gestionarlas de forma adecuada.
Los detalles de implementación y uso de las utilidades del sistema se obvian a propósito para
no hacer este documento innecesariamente largo. Para solucionar cualquier duda sobre el uso de
alguna función o utilidad mencionada será necesario recurrir a documentos complementarios
como puede ser el manual en línea de GNU/Linux, la utilidad info u otro tipo de documentación.
2.− LIBRERÍAS ESTÁTICAS
Una librería estática no es más que un conjunto de ficheros objeto empaquetados en un único
archivo. Para crear una librería estática debemos seguir los siguientes pasos:
•
•
•
Codificar los módulos de la forma habitual.
Obtener los ficheros objeto utilizando el compilador.
Empaquetar los ficheros objeto que integran la librería por medio de la utilidad ar.
Por convenio, los nombres de todas las librerías estáticas comienzan por lib− y tiene .a por
extensión. Aunque estas restricciones no son obligatorias, es poco recomendable saltárselas pues
el entorno ofrece facilidades de uso si se siguen dichas normas.
2.1.− Creación de una librería estática
Supongamos que disponemos de los ficheros fich1.c y fich2.c que contienen el código de las
funciones que deseamos incluir en la librería libfich.a. Para obtener los ficheros objeto
compilamos como siempre:
$ gcc −c −o fich1.o fich1.c
y
$ gcc −c −o fich2.o fich2.c
A continuación empaquetamos los ficheros obtenidos con ar (un empaquetador similar a tar).
$ ar rcs libfich.a fich1.o fich2.o
El significado de las opciones que se le dan a ar es el siguiente:
s construir un índice del contenido.
c crear el paquete si no existe.
r reemplazar los ficheros si ya existían en el paquete.
Otras opciones de ar que pueden resultar útiles
t lista el contenido de un paquete (o librería).
x extrae un fichero de un paquete (o librería).
Página 1 de 7
Curso de Sistemas Operativos
Creación de Librerías
2.2.− Uso de una librería estática
Para utilizar las funciones y símbolos contenidos en una librería estática es necesario enlazar
dichos símbolos cuando se monta un ejecutable. Para ello, debemos indicar al compilador qué
librerías queremos utilizar y el lugar donde se encuentran.
Si hemos escrito un programa apli1.c que hace uso de la librería que hemos creado en el
punto anterior y queremos compilarlo debemos hacer lo siguiente:
Primero compilamos el programa:
$ gcc −c −o apli1.o apli1.c
teniendo presente que en la compilación deben estar en ruta los ficheros de interfaz (.h) de la
librería; si no es así debe indicarse al compilador donde se encuentran dichos ficheros haciendo
uso de la opción −I:
$ gcc −c −o apli1.o apli1.c −Idir_lib
Después se monta el programa con la librería indicando donde está la librería y cual es su
nombre:
$ gcc −o apli1 apli1.o −Ldir_lib −lfich
Consideraciones:
•
En el ejemplo se supone que la librería y su fichero de interfaz se encuentran en un directorio
llamado dir_lib.
•
La opción −I se usa para indicar donde se encuentran ficheros de interfaz.
•
La opción −L sirve para indicar el directorio donde se encuentra la librería.
•
La opción −l es para indicar los nombres de la librerías que se van a usar, pero no debe
escribirse ni el prefijo lib ni el sufijo .a ya que el compilador espera que se sigan las normas
de nombrado anteriormente citadas.
A continuación se muestra un fichero makefile que permite automatizar todo el proceso, tanto
la creación de la librería como del programa. En este caso se supone que todos los ficheros
necesarios se encuentran en el directoria actual.
CC=gcc
CFLAGS=−Wall −ggdb −I./
LDFLAGS=−L./
LDLIBS=−lfich
all: libfich.a apli1
apli1: apli1.o
libfich.a: fich1.o fich2.o
ar rcs $@ $^
3.− LIBRERÍAS DINÁMICAS
La utilización de objetos dinámicos supone dejar pendiente en el montaje de la aplicación el
enlace de dichos objetos. Cuando la aplicación está en ejecución, y sólo entonces, se produce el
enlace (dinamic binding) con los objetos contenidos en la librería.
La creación de librerías dinámicas corre a cargo del enlazador o montador (en nuestro caso el
ld) aunque también es posible indicar al compilador las opciones necesarias para el montaje y de
ese modo, será él quien se encargue de comunicárselas al montador.
Página 2 de 7
Curso de Sistemas Operativos
Creación de Librerías
3.1.− Creación de una librería dinámica
Cuando se crea un objeto dinámico es necesario que dicho código objeto sea independiente
de la posición, para conseguir este tipo de código debe especificarse al compilador la opción
−fPIC (Position Independent Code). Dicho flag debe indicarse tanto en la compilación como en
el montaje de la librería.
$ gcc −fPIC −c −o fich1.o fich1.c
$ gcc −fPIC −c −o fich2.o fich2.c
Para montar los objetos es necesario además indicar la opción −shared para que el resultado
sea un fichero objeto ’compartible’.
$ gcc −shared −fPIC −o libfich.so fich1.o fich2.o
Para compilar la librería dinámica puede utilizarse un makefile como este:
CC=gcc
CFLAGS=−Wall −ggdb −fPIC
LDFLAGS=−fPIC −shared
libfich.so: fich1.o fich2.o
$(CC) $(LDFLAGS) −o $@ $^
En este caso, la librería tiene como extensión .so que significa shared object. Para utilizar
esta librería desde un programa no hay que hacer nada adicional; la forma de hacerlo es
exactamente igual que en el caso de la librería estática.
Al hacer uso de una librería, el compilador busca primero una versión dinámica (.so), si no la
encuentra entonces busca una estática. Si se tienen las dos versiones de una librería y se quiere
utilizar la versión estática debe indicarse al montador el flag −static.
3.2.− Uso de una librería dinámica
Cuando un programa utiliza librerías dinámicas necesita localizarlas en tiempo de ejecución
(al contrario que con las librerías estáticas). Los lugares donde un programa busca las librerías
dinámicas son los siguientes (en este orden):
•
En los directorios de la variable LD_LIBRARY_PATH
•
En el fichero ld.so.cache
•
En los directorios /usr/lib y /lib
•
En los directorios contenidos en ld.so.conf
Si el programa no encuentra la librería que necesita imprimirá un mensaje de error con el
siguiente aspecto:
$ ./apli1
apli1: error in loading shared libraries: libfich.so: cannot open
shared object file: No such file or directory
Normalmente, lo más adecuado, es utilizar la variable de entorno LD_LIBRARY_PATH para
indicar en qué directorios debe buscar:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/user/dir_lib
De este modo debe funcionar correctamente.
3.3.− Versión de una librería dinámica.
Una de las grandes ventajas del uso de librerías dinámicas, a parte de tener ficheros
ejecutables más pequeños, es que podemos modificar la implementación de las librerías sin tener
que recompilar los programas.
Página 3 de 7
Curso de Sistemas Operativos
Creación de Librerías
Para diferenciar las librerías que han sufrido modificaciones se utilizan los números de
versión que tienen el siguiente significado (si se tiene una librería libejemplo.so.x.y.z. siendo x,
y, z números de versión):
x
(mayor number) permite saber que todas las versiones con el mismo valor para este
número son compatibles.
y
(release) Cambia cuando se hace alguna modificación que afecta al interfaz de la
librería.
z
(epoch) Cambia cuando se arregla algún error o cambia alguna característica interna de
la librería.
Para que los números de versión no sean sólo el nombre del fichero de la librería sino que las
utilidades del sistema puedan gestionar dicha información es necesario indicar al enlazador el
nombre y versión de la librería para que lo incluya en el interior del archivo .so.
Para pasar valores al enlazador desde el compilador se utiliza el flag −Wl escribiendo a
continuación las opciones que queremos pasar al enlazador separadas por comas en lugar de por
espacios. Veamos cómo crear la librería libfich.so del ejemplo anterior de manera que sea la
versión libfich.so.1.0.0.
$ gcc −Wl,−soname,libfich.so.1.0.0 −shared −fPIC −o \
libfich.so.1.0.0 fich1.o fich2.o
Con un makefile sería:
CC=gcc
CFLAGS=−Wall −ggdb −fPIC
LDFLAGS=−fPIC −shared
libfich.so.1.0.0: fich1.o fich2.o
$(CC) −Wl,−soname,$@ $(LDFLAGS) −o $@ $^
El problema de utilizar estos nombres es que cuando se compila un programa que utiliza una
librería dinámica el compilador busca un fichero con extensión .so simplemente. Para solucionar
esto, basta con crear un enlace simbólico con el nombre que el compilador está buscando, es
decir:
$ ln −s libfich.so.1.0.0 libfich.so
Si compilamos un programa con una librería para la que se ha especificado un soname con
los 3 números, el programa sólo funcionará si encuentra exactamente esa versión. Sin embargo
podemos especificar un soname más genérico incluyendo sólo 1 o 2 de los números de versión.
De este modo el programa funcionará sin recompilar con cualquiera de las versiones de la
librería con tal que coincida el soname con el que fue compilado. Este sistema permite cambiar a
versiones más recientes de las librerías de una forma limpia y rápida.
El inconveniente es que necesitamos nuevos enlaces simbólicos pues el programa en
ejecución buscará una librería con el soname con el que fue compilado. Veamos un ejemplo:
$ gcc −Wl,−soname,liba.so.1.0 −shared −fPIC −o liba.so.1.0.0 a.o
Hemos creado una librería con nombre liba.so.1.0.0 y soname liba.1.0 a partir del objeto a.o.
Para compilar un programa con esta librería debe existir un enlace liba.so que apunte a
liba.so.1.0.0. después ya podemos compilar el programa.
Sería algo como:
$ ln −s liba.so.1.0.0 liba.so
$ gcc −o apli2 apli2.o −L. −la
Página 4 de 7
Curso de Sistemas Operativos
Creación de Librerías
Pero cuando intentamos ejecutar la aplicación obtenemos el siguiente error:
$ ./apli2
apli2: error in loading shared libraries: liba.so.1.0: cannot open
shared object file: No such file or directory
Esto ocurre aunque el directorio esté en el LD_LIBRARY_PATH. Para arreglarlo hay que
crear un enlace del siguiente modo:
$ ln −s liba.so.1.0.0 liba.so.1.0
3.3.1.− Utilidad ldd
Podemos saber con que versión está compilado un programa por medio del programa ldd;
veamos como funciona:
$ ldd apli2
liba.so.1.0 => /home/user/liba.so.1.0 (0x40023000)
libc.so.6 => /lib/libc.so.6 (0x40025000)
libdl.so.2 => /lib/libdl.so.2 (0x4010d000)
/lib/ld−linux.so.2 => /lib/ld−linux.so.2 (0x40000000)
Si ahora modificamos la librería y creamos una nueva versión llamada liba.so.1.0.2 pero
mantenemos el soname liba.so.1.0 la aplicación seguirá funcionando sin más que cambiar el
enlace simbólico para que apunte a la nueva librería.
3.3.2.− Utilidad ldconfig.
Como el asunto de los enlaces puede llegar a ser muy tedioso existe una utilidad llamada
ldconfig que hace todo esto por nosotros.
Como siempre, veamos como automatizar todo con un makefile:
CC=gcc
CFLAGS=−Wall −ggdb −fPIC
LDFLAGS=−fPIC −shared
NOMBRE_LIB=liba.so.1.0.0
SONAME=liba.so.1.0
$(NOMBRE_LIB): a.o
$(CC) −Wl,−soname,$(SONAME) $(LDFLAGS) −o $@ $^
ln −s $(NOMBRE_LIB) liba.so
ldconfig −vn ./
clean:
$(RM) *.o core liba.so*
4.− ENLACE DINÁMICO EN TIEMPO DE EJECUCIÓN
Cuando un programa se enlaza con una librería dinámica, este obtiene información de como
debe manipular los objetos que en ella se encuentran para cuando llegue el momento en que se
ejecute.
Existe otra posibilidad de utilizar el código que contiene una librería dinámica. Este método
permite definir la interfaz de acceso a una librería y en tiempo de ejecución acceder a ella sin
tener ningún conocimiento previo. Esta es la forma en la que habitualmente se implementan los
plugin.
El acceso se hace por medio de las siguientes funciones:
void *dlopen (const char *nomfich, int indicador);
Abre la librería dinámica nomfich (debe ser la ruta absoluta) y devuelve un manejador de la
librería. El argumento indicador es el modo en que se cargará la librería.
const char *dlerror(void);
Devuelve una cadena con la descripción del último error ocurrido.
Página 5 de 7
Curso de Sistemas Operativos
Creación de Librerías
void *dlsym(void *manejador, char *símbolo);
Devuelve la dirección del símbolo cuyo nombre corresponde con el argumento simbolo
ubicado en la librería referenciado por manejador, que es un valor devuelto dlopen( ).
int dlclose (void *manejador);
Descarga la librería dinámica referenciada por medio de manejador.
4.1.− Ejemplo de enlace dinámico.
Disponemos de una librería dinámica llamada libhola.so compilada a partir del siguiente
código:
#include <stdio.h>
void rehola(int i)
{
printf("Hola mundo por %d vez.\n", i);
}
Y tenemos un fichero con el siguiente código y que se llama apli3.c:
#include <stdio.h>
#include <dlfcn.h>
#define RUTA_LIBHOLA "/home/user/libhola.so"
int main(){
int i = 3;
void *pLibHola;
void (*pFuncion)(int);
/* manejador de la libreria libhola */
/* símbolo que importamos de libhola */
if ((pLibHola = dlopen(RUTA_LIBHOLA, RTLD_LAZY)) ==NULL) {
fprintf(stderr, dlerror());
return 1;
}
if ((pFuncion = dlsym(pLibHola, "rehola")) == NULL) {
fprintf(stderr, dlerror());
return 1;
}
pFuncion(i);
dlclose(pLibHola);
return(0);
}
Este código se compila simplemente con:
$ gcc −o apli3 apli3.c −ldl
La opción −ldl es necesaria para poder utilizar la librería libdl.so que es la encargada de
implementar los enlaces dinámicos, pero en ningún momento se han hecho referencias a la
librería libhola, sólo al lugar donde se encuentra.
Si ejecutamos el programa vemos lo siguiente:
$ ./apli3
Hola mundo por 3 vez.
Página 6 de 7
Curso de Sistemas Operativos
Creación de Librerías
5.− PRECARGA DE SÍMBOLOS
Esta es otra posibilidad más para utilizar código externo de forma dinámica. Consiste en
cargar una librería dinámica antes de un programa. Dicha librería dinámica contendrá símbolos
referenciados por el programa.
Supongamos que un programa apli4 hace uso del símbolo FuncionG que se encuentra en la
librería dinámica libDinamica.so. Pues bien, podemos escribir una librería diferente libOtra.so
que implemente la FuncionG a condición de que cumpla su prototipo, pero cuya funcionalidad
puede ser completamente distinta. Después, con este método, se puede hacer que el programa
utilice el símbolo que hay en nuestra librería en lugar del que hay en la librería libDinamica.so.
Hay dos formas para conseguir dicho comportamiento:
•
Utilizar la variable de entorno LD_PRELOAD de la siguiente manera:
LD_PRELOAD=/home/user/libOtra.so apli4
•
Por medio del fichero /etc/ld.so.preload que contendrá los nombres de las librerías cuyos
símbolos se les dará prioridad en la carga.
En el documento Depuración Avanzada en GNU/Linux, de esta misma serie de documentos,
aparecen varios ejemplos del uso de este método.
6.− REFERENCIAS
•
Curso de Enseñanzas Propias Aplicaciones de desarrollo en GNU/Linux. Escuela Superior
de Informática, Ciudad Real, 2001.
•
Manual en línea de GNU/Linux.
Este documento es de dominio público. Se puede imprimir y distribuir libre de gastos en su forma original,
incluyendo una lista de los autores. Si se altera o se utilizan partes de éste dentro de otro documento, la lista de
autores debe incluir todos los autores originales y el autor o los autores que hayan realizado los cambios. Así
mismo el documento modificado debe incluir este texto.
En caso de que este documento sea utilizado con fines comerciales, se aplicarán los términos de la GNU
General Public License.
Página 7 de 7
Descargar