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 pasarselas al montador.
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.
3.2. Uso de una librería dinámica Para utilizar esta librería desde un programa no hay que hacer nada adicional; 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 la versión 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.
Cuando un programa utiliza librerías dinámicas, el asistema 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 el fichero 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.
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):
Table 3.
x (major 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 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
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 rear 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 (0×40023000) libc.so.6 => /lib/libc.so.6 (0×40025000) libdl.so.2 => /lib/libdl.so.2 (0×4010d000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0×40000000)
Si ahora modificamos la librería y creamos una nueva versión llamada liba.so.1.0.2 pero antenemos el soname liba.so.1.0 la aplicación seguirá funcionando sin más que cambiar el nlace 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 dconfig 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*