Sirviendo información sobre Linux desde 1998

Creación de Bibliotecas Compartidas

Autor: Luis Colorado, LinuxFocus
Copyright pertenece al autor

1.- El proceso de generación de un programa. Introducción.

Hoy en día el proceso de generación de programas en entornos de desarrollo es el fruto de una evolución de las costumbres y la experiencia sufrida por los propios programadores y diseñadores de programas.

Este proceso consta de los siguientes pasos:

  • Creación del código fuente en lenguaje de alto nivel del programa con un programa editor de textos. Los programas muy grandes pueden llegar a ser inmanejables si pretendemos meterlos íntegros en un único fichero. Por esta razón, los programas se dividen en módulos funcionales, los cuales estarán compuestos por uno o varios ficheros con código fuente. Este código no tiene por qué estar forzosamente escrito en el mismo lenguaje, ya que ciertos lenguajes parecen mas apropiados para resolver ciertos problemas.
  • Una vez creados los ficheros con el código del programa, estos han de traducirse a segmentos de código ejecutable por la máquina, código llamado normalmente código objeto. Este código hace normalmente lo mismo pero está escrito en un lenguaje especial, que es directamente ejecutable por la máquina. Este proceso se llama compilación. La compilación se realiza por unidades y una sesión de compilación normalmente incluirá una parte del programa y en general, solamente un fichero o unos pocos. El resultado de la compilación suele ser (dependiendo del compilador) un fichero con código máquina por cada fichero con código fuente compilado. El código objeto compilado contiene un programa, una subrutina, variables, etc. en general una parte del programa que ha sido traducida y que se puede entregar a la siguiente fase.

  • Una vez generados todos los ficheros con el código máquina del programa, éstos se juntan en un proceso realizado por un programa especial al efecto, llamado normalmente el linker. En este proceso, se 'resuelven' todas las referencias que el código de un módulo hace a código perteneciente a otros módulos (como llamadas a subrutinas o referencias a variables pertenecientes o definidas en otros módulos). El resultado de este proceso es un programa que normalmente puede cargarse y ejecutarse diréctamente.

  • La ejecución de un programa la realiza un trozo de software especial que forma parte del sistema operativo y que en Linux la realiza la llamada al sistema exec(). Esta función localiza un fichero, asigna memoria al proceso carga ciertas partes del contenido del fichero (las que contienen el código y los valores iniciales de las variables) y transfiere el control de la cpu a un punto del 'texto' del programa que normalmente está indicado en el propio fichero con el ejecutable.

2.- Breve historia del proceso de generación de programas.

El proceso de generación de programas ha sufrido una evolución constante con el fin de obtener siempre mayor eficiencia de los programas o mayor aprovechamiento de los recursos del sistema informático.

Inicialmente, los programas se realizaban en lenguaje de máquina directamente. Posteriormente, se vió que la realización de programas en lenguajes de alto nivel y su traducción sistemática podía ser automatizada debido a que era una tarea sistematica. Esto aumentaba la productividad de software.

Conseguida la compilación de programas (he reducido mucho la evolución de la compilación, ya que este paso fue muy dificil de dar, ya que la compilación es un proceso muy complejo), El proceso de generación de programas consistía en generar un fichero con el programa, compilarlo y obtener el ejecutable para posteriormente ejecutarlo.

Pronto se vió (FORTRAN ya lo implementa) que el proceso de compilación era una tarea costosa que consumía muchos recursos y tiempo de CPU, y que muchas de las funciones utilizadas en los programas se utilizaban una y otra vez en diferentes programas. Además, cuando alguien modificaba una parte, compilar todo el programa de nuevo suponía lanzar el compilador para compilar otra vez un montón de código idéntico y solo compilar una vez el nuevo código introducido.

Por esta razón se introdujo la compilación por módulos. Esto consistía en separar por un lado lo que era el programa principal, y por otro, aquellas funciones que se usaban una y otra vez, que ya estaban compiladas y se guardaban en un lugar especial (que llamaremos precursor de la biblioteca).

Nosotros podíamos desarrollar programas apoyándonos en aquellas funciones y no era necesario introducir el código una y otra vez. Aún así, el proceso era complejo, ya que al enlazar (linkar???) el programa, era necesario unir todos los trozos y éstos debían ser identificados por el programador (lo que añadía el coste adicional de que podíamos estar usando una función conocida pero que use/necesite de otras funciones que desconocemos)

3.- Qué es una biblioteca?

Esto condujo a la creación de la biblioteca. Esto no es mas que un tipo de fichero especial (realmente es un archivo, tipo tar(1) o cpio(1)) pero que es especial en el sentido de que el linker entiende su formato y cuando especificamos un archivo de biblioteca, EL LINKER SELECCIONA SOLAMENTE AQUELLOS MODULOS QUE EL PROGRAMA NECESITA, sin incluirlos todos. Esto añadió una ventaja, ahora se podían desarrollar programas que hicieran uso de grandes bibliotecas de rutinas y el programador no tenía que conocer todas las dependencias de las funciones de la biblioteca.

La biblioteca tal y como la hemos visto hasta este punto no ha evolucionado mas. Tan solo se le ha añadido un fichero especial, que suele aparecer al comienzo del archivo, y que contiene una descripción de los módulos y los identificadores que va a poder resolver el linker sin necesidad de leerse toda la biblioteca (y de esta manera eliminar varias pasadas por la misma). Este proceso (el de añadir la tabla de símbolos al archivo de biblioteca) es realizado en Linux por el comando ranlib(1). Estas bibliotecas son las que se conocen como BIBLIOTECAS ESTÁTICAS.

Un avance que se introdujo con los nuevos sistemas multitarea que estaban apareciendo es la compartición de código. Si en un sistema se lanzaban dos copias del mismo programa, parecía interesante que, dado que normalmente un programa no modifica su código, que los procesos compartieran el código, sin necesidad de tener varias copias en memoria. Esto ahorró mucha memoria en sistemas grandes con muchos usuarios.

Esto fue llevado un paso mas allá: Alguien pensó (no se quien fué, pero la idea fue bastante buena ;-), que era muy frecuente el caso de que muchos programas usaban la misma biblioteca, pero al ser programas diferentes, la parte de la biblioteca usada por un programa no tenía porque ser la misma que la parte usada por otro programa y además el código principal no era el mismo (eran programas diferentes), por tanto el texto no se compartía. A esta persona se le ocurrió que si programas diferentes que usaban la misma biblioteca, realmente podían compartir el código de dicha biblioteca y de esa manera ahorrar algo en ocupación de memoria. Se inventó el proceso de carga dinámica y bibliotecas dinámicas. Ahora programas diferentes comparten el código de la biblioteca, sin que el código general del programa sea el mismo.

Sin embargo, ahora el proceso es mas complejo. El programa no se enlaza (¿¿¿linka???) por completo, sino que las referencias a identificadores de bibliotecas compartidas se postponen para el proceso de carga del programa. El linker (el linker en Linux es ld(1)) reconoce que está ante una biblioteca compartida y no incluye el código de ésta en el programa. El propio sistema (el kernel) cuando se hace el exec, reconoce que es un programa con bibliotecas compartidas y ejecuta un código especial que se encarga de cargar la biblioteca (asignar memoria compartida para el texto de la misma, asignar memoria privada para los datos propios de la biblioteca, etc.) Este proceso se realiza al cargar el programa en un proceso ahora mas complejo.

Por supuesto, el linker ante una biblioteca normal sigue comportandose como antes.

La biblioteca compartida no es un archivo con ficheros conteniendo código objeto, sino mas bien un fichero que contiene código objeto por sí mismo. Cuando se enlaza el programa con una biblioteca compartida, el linker no investiga por dentro de la biblioteca que módulos debe añadir al programa y cuales no, se limita a comprobar que referncias insatisfechas se resuelven y cuales hay que añadir a la lista por la inclusión de esta biblioteca. Se podría crear un archivo ar(1) de biblioteca de bibliotecas compartidas, pero ésto no se suele hacer, ya que una biblioteca compartida puede ser el resultado de enlazar varios módulos y la biblioteca es necesaria luego, a la hora de ejecutar el programa. Quizá el nombre de una biblioteca compartida no sea adecuado y sea mas adecuado el nombre de objeto compartible. (sin embargo no usaremos este término por no estar extendido)

4.- Tipos de bibliotecas.

Como hemos dicho, en Linux existen dos tipos de bibliotecas: las estáticas y las compartibles. Las bibliotecas estáticas son colecciones de módulos introducidos en un archivo con la utilidad ar(1) e indexados sus símbolos con la utilidad ranlib(1). Estos archivos suelen almacenarse en ficheros terminados en .a (no utilizaré el termino extensión, ya que en Linux no existe el concepto de extensión de un fichero) por convenio. El linker ld(1) reconoce la terminación .a en un nombre de fichero y realiza la búsqueda de módulos en el mismo como si se tratara de una biblioteca estática, seleccionando y añadiendo al programa aquellos que resuelvan referencias aún no satisfechas.

Las bibliotecas dinámicas, por contraposición, no son archivos sino que son objetos reubicables, marcados con un código especial (que los identifica como bibliotecas compartidas). El linker ld(1), como hemos dicho, no añade al código del programa los módulos, sino que selecciona como resueltos los identificadores aportados por la biblioteca, añade aquellos introducidos por ésta, y continúa sin añadir el código de la misma al programa, pero como si éste hubiera sido añadido. El linker ld(1) reconoce una biblioteca compartida por tener la terminación .so (y no .so.xxx.yyy, volveremos sobre ésto mas adelante)

5.- Operación de enlazado en Linux.

Todo programa consta de módulos objeto, enlazados para conseguir el ejecutable. Esta operación la realiza ld(1), que es el linker de Linux.

ld(1) soporta varias opciones que permiten modificar su comportamiento, pero nos ceñiremos aquí a aquellas que tienen relación con el uso de bibliotecas en general. ld(1) no es llamado directamente por el usuario, sino mas bien por el propio compilador gcc(1), en su fase final, pero un conocimiento superficial de su modo de operación nos ayudará a entender el uso de bibliotecas en Linux.

ld(1) requiere para su funcionamiento la lista de objetos que se van a unir para obtener un programa. Estos objetos pueden darse en cualquier orden(*) y llamarse de cualquier manera, siempre que nos atengamos al convenio anterior, en el que decíamos que una biblioteca compartida se indica por terminar su nombre en .so (y no .so.xx.yy) y una biblioteca estática por terminar su nombre en .a (y por supuesto, los objetos simples, cuyo nombre terminará en .o).

(*) Esto no es totalmente cierto. ld(1) incluye solo aquellos módulos que resuelven referencias en el momento de la inclusión, con lo que podría haber alguna referencia provocada por la inclusión de un módulo posterior que, al no aparecer todavía en el momento de la inclusión de la biblioteca, provoque que el orden de inclusión de las bibliotecas sea significativo.

Por otro lado, ld(1) permite la inclusión de bibliotecas estandar por medio de las opciones -l y -L.

Pero... Qué entendemos por una biblioteca estandar, y que diferencia hay? Ninguna. Solo que ld(1) busca las bibliotecas estandar en lugares predeterminados mientras que las que aparecen como objetos en la lista de parámetros se buscan utilizando simplemente el nombre.

Las bibliotecas se buscan por defecto en los directorios /lib y /usr/lib (aunque he oido que dependiendo de la versión/implementación de ld(1) puede haber otros lugares, los citados se dan siempre).-L permite añadir directorios a los normales de búsqueda de bibliotecas, se hará poniendo una opción -L por cada que queramos añadir. Las bibliotecas estandar se especifican con la opción -l (donde especifica la biblioteca que vamos a cargar) y ld(1) buscará, por éste orden, en los directorios correspondientes, un fichero llamado lib.so, y si no lo hay, lib.a.

Si encuentra un fichero llamado lib.so, enlazará ésta como si se tratara de una biblioteca compartida, mientras que si encuentra una llamada lib.a, enlazará los módulos que obtenga de esta si resuelven alguna referencia insatisfecha.

6.- Enlazado dinámico y carga de bibliotecas compartidas

El enlazado dinámico se realiza en el momento de la carga del ejecutable por parte de un módulo especiál (de hecho, este módulo es una biblioteca compartida en sí mismo), llamado /lib/ld-linux.so

Realmente, existen dos módulos para el enlazado de bibliotecas dinámicas: /lib/ld.so (para bibliotecas en el antíguo formato a.out) y /lib/ld-linux.so (para las nuevas bibliotecas en el nuevo formato ELF).

Estos módulos son especiales, y han de cargarse siempre que un programa esté enlazado dinámicamente. Su nombre no varía (razón por la que no deben moverse del directorio /lib, ni cambiarse de nombre). Si cambiaramos de nombre /etc/ld-linux.so, automáticamente dejaríamos de poder ejecutar todos los programas utilizasen bibliotecas compartidas, ya que este módulo está encargado de resolver en el momento del arranque, todas las referencias que aún no están resueltas.

Este módulo se apoya en la existencia de un fichero, llamado /etc/ld.so.cache, que indica, para cada biblioteca, el fichero ejecutable mas apropiado conteniendo dicha biblioteca. Volveremos sobre ésto mas adelante.

7.- soname. Versiones de bibliotecas compartidas. Compatibilidad.

Entramos ahora en el punto mas escabroso referente a las bibliotecas compartidas: Las versiones.

Se habla a menudo de 'la biblioteca libX11.so.3 no se encuentra' dejándonos con la frustración de tener la biblioteca libX11.so.6 y no poder hacer nada. Como es posible que ld.so(8) reconozca como intercambiables las biblioteca libpepe.so.45.0.1 y libpepe.so.45.22.3 y no admita libpepe.so.46.22.3?

En Linux (y en todos los sistemas operativos que implementan formato ELF), las bibliotecas se identifican por una secuencia de caracteres que identifican dicha biblioteca: el soname.

Esta secuencia de caracteres está incluida dentro de la propia biblioteca y la secuencia se determina al enlazar los objetos de dicha biblioteca. Cuando se crea una biblioteca compartida, hay que pasar a ld(1) una opción (-soname ), para dar valor a esta cadena.

Esta secuencia de caracteres se utiliza por parte del cargador dinámico para identificar la biblioteca compartida que hay que cargar e identificar al ejecutable. El proceso aproximado es el siguiente:

Ld-linux.so identifica que el programa necesita una biblioteca compartida e identifica el soname de la misma. Después, entra en /etc/ld.so.cache con dicho soname y obtiene el nombre del fichero que la contiene. Después, compara el soname solicitado con el existente en la biblioteca, si coinciden, pues ya está, y si no, seguiremos buscando hasta encontrarla o daremos un error.

El soname permite identificar si una biblioteca es adecuada para ser cargada, ya que ld-linux.so comprueba que el soname pedido coincida con el contenido en el fichero seleccionado. En caso de disconformidad obtenemos el famoso error de 'libXXX.so.Y' not found. Lo que se está buscando es precisamente el soname y el error que se da es referente al soname.

Esto produce mucho desconcierto cuando cambiamos el nombre de una biblioteca y el problema persiste. Pero no es buena idea acceder al soname y cambiarlo, ya que existe un convenio en la comunidad Linux para asignar los sonames:

El soname de una biblioteca, por convenio, debe identificar la biblioteca propiamente dicha, Y EL INTERFACE con dicha biblioteca. Si realizamos modificaciones en una biblioteca que solamente afectan a su funcionamiento interno, pero todo el interface con la misma (número de funciones, variables, parámetros a las funciones) permanece invariable, las dos bibliotecas serán intercambiables y en general diremos que el cambio realizado es un cambio menor (ambas bibliotecas aún son compatibles y podremos sustituir una por la otra). Cuando ésto ocurre, se suele variar el minor number del número de versión (que no aparece en el soname) y la biblioteca puede sustituir sin problemas a la anterior.

Sin embargo, cuando añadimos funciones, eliminamos funciones, y en general, VARIAMOS EL INTERFACE de la biblioteca, ya no es posible seguir diciendo que una biblioteca puede sustituir a la anterior (por ejemplo en el cambio de libX11.so.3 a libX11.so.6 se realiza la transición de X11R5 a X11R6 que define funciones nuevas y por tanto varía el interface). Sin embargo el cambio de X11R6-v3.1.2 a X11R6-v3.1.3 probablemente no incluirá cambios en el interface y la biblioteca tendrá el mismo soname ---aunque para no machacar la antígua se llamará diferente (por esta razón el número de ver- sión aparecerá completo en el nombre de la biblioteca, mientras que solo aparece el major number en el soname).

8.- ldconfig(8)

El fichero /etc/ld.so.cache hemos dicho que permite a ld-linux.so convertir de soname a nombre de fichero conteniendo la biblioteca. Este es un fichero binario para mayor eficiencia y se crea con la utilización de un programa llamado ldconfig(8).
ldconfig(8) genera para cada biblioteca dinámica que encuentra en los directorios especificados en el fichero /etc/ld.so.conf un link simbólico llamado con el soname de la biblioteca, de tal forma que cuando ld.so va a obtener el nombre del fichero, lo que hace es seleccionar en la lista de directorios, un fichero con el soname buscado, y de esta manera no hace falta ejecutar ldconfig(8) cada vez que añadimos una biblioteca, sino solo cuando añadimos un directorio a la lista.

9.- Quiero Crear una Biblioteca Compartida.

Antes de crear una biblioteca dinámica debemos pensar si realmente será útil. Las bibliotecas dinámicas provocan una sobrecarga en el sistema debido a varios elementos:

  • La carga del programa se realiza en varios pasos, uno para el programa principal, mas uno por cada biblioteca dinámica que use dicho programa (veremos que si la biblioteca dinámica es apropiada, este último punto deja de ser un inconveniente y pasa a ser una ventaja)

     

  • Las bibliotecas dinámicas deben contener código reubicable, ya que la posición de carga dentro del espacio de direcciones virtuales del proceso no se sabrá hasta el momento de dicha carga. Esto obliga al compilador a reservar un registro para mantener la posición de carga de la biblioteca y por tanto tenemos un registro menos para el optimizador de código. Este caso es un mal menor, ya que la sobrecarga introducida por esta situación no representa mas de un 5% de sobrecarga en la mayoría de los casos.

Para que una biblioteca dinámica sea apropiada debe ser utilizada la mayor parte del tiempo por algún programa (esto evita el problema de cargar el texto de la biblioteca, ya que permanece cargada en memoria, tras la muerte del proceso que la usa al haber otros procesos usándola)

La biblioteca compartida se carga en memoria completa (no solo los módulos utilizados) así que para que sea util, debe serlo en su totalidad. No son buenas bibliotecas dinámicas aquellas donde solo se usa una funcion y el noventa por ciento de la biblioteca no se usa la mayor parte del tiempo.

Un buen ejemplo de biblioteca dinámica es la biblioteca estandar de C (la usan todos los programas escritos en C ;). Por termino medio todas las funciones se utilizan en uno u otro caso.

En una biblioteca estática suele importar poco incluir funciones cuyo uso sea infrecuente, siempre que dichas funciones ocupen un módulo propio, no serán enlazadas en aquellos programas que no las usen.

9.1.- Compilación de los fuentes.

La compilación de los fuentes se realizará de la misma manera que para compilar un fuente normal, salvo que utilizaremos la opción '-f PIC' (Position Independent Code ---Código Independiente de la Posición) para generar código que pueda ser cargado en diferentes posiciones del espacio de direcciones virtuales de un proceso.

Este paso es fundamental, ya que en un programa linkado estáticamente, la posición de los objetos de una biblioteca se resuelve en el momento del linkaje, y por tanto es fija. En los antíguos ejecutables a.out, este paso era imposible de realizar y había que colocar cada biblioteca compartida en una posición fija del espacio de direcciones virtuales de un proceso. Esto provocaba conflictos cuando un programa quería usar dos bibliotecas preparadas para cargarse en regiones de memoria virtual solapadas. Esto obligaba a mantener una lista, donde cuando alguien deseaba hacer una biblioteca dinámica, debía registrar el rango de direcciones que usaba para que nadie mas las usara.

Bien, como hemos dicho, para una biblioteca dinámica, este registro en una lista oficial no es necesario pues cuando una biblioteca se carga, lo hace en posiciones que se determinan en ese momento, aunque para ello, el código de la biblioteca debe ser reubicable.

9.2.- Linkado de los objetos en la biblioteca

Una vez tenemos todos los objetos compilados, hay que linkarlos con una opción especial, para que al final se genere un objeto cargable dinámicamente.

gcc -shared -o lib.so.xxx.yyy.zzz

-Wl,-soname,lib.so.xxx

Como puede verse, es una operación de linkado normal, salvo que se introducen una serie de opciones especiales que conducirán a la generación de una biblioteca compartida. Las explicaremos a continuación, una por una:

  • -shared.
    Esta fase indica al linker que debe generarse una biblioteca compartida al final, y que por tanto, habrá un tipo de ejecutable en el fichero de salida correspondiente a una biblioteca compartida.

     

  • -o lib.so.xxx.yyy.zzz.
    Es el nombre del fichero final. No es necesario seguir el convenio respecto al nombre, pero si queremos utilizar dicha biblioteca como estandar en futuros desarrollos, es conveniente hacerlo.

     

  • -Wl,-soname,lib.so.xxx.
    La opción -Wl indica a gcc(1) que las opciones que vienen a continuación (separadas por coma para separar diferentes opciones) son para el linker. De esta forma, cuando gcc(1) llame a ld(1), para linkar, le pasará los siguientes parámetros:
       -soname lib.so.xxx

    Esta opción fija el soname de la biblioteca, de tal forma que solo podrá ser llamada por aquellos programas que requieran la biblioteca cuyo soname sea el especificado.

9.3.- Instalación de la biblioteca

Bueno, ya tenemos el ejecutable correspondiente. Ahora debemos instalar en un lugar apropiado la biblioteca, con el fin de poder usarla.

Cuando compilemos un programa que deba usar esta biblioteca, lo haremos, indicándola en la línea de compilación:

gcc -o programa lib.so.xxx.yyy.zzz

o si la hemos instalado en el lugar apropiado (/usr/lib), bastará con:

gcc -o programa -l

(si hubiera estado en /usr/local/lib, hubieramos añadido la opción '-L/usr/local/lib')Para instalar la biblioteca haremos lo siguiente:

  • Copiaremos la biblioteca al directorio /lib o /usr/lib. Si decidimos copiarla a otro directorio (por ejemplo /usr/local/lib), no tendremos la seguridad de que el linker ld(1) la encuentre automáticamente al enlazar programas.

     

  • Ejecutaremos ldconfig(1) para que se cree el link simbólico de lib.so.xxx.yyy.zzz a lib.so.xxx. Este paso nos indicará, por la creación del link, si hemos realizado todos los pasos correctamente y la biblioteca es reconocida como dinámica. Este último paso no afecta a como se linkan los programas, sino solamente la carga de bibliotecas en tiempo de ejecución.

     

  • Crearemos un link simbólico de lib.so.xxx.yyy.zzz (o desde lib.so.xxx, el soname) a lib.so, con el fin de que el linker pueda encontrarla con la opción -l. Para que esto funcione despues, es necesario que el nombre de la biblioteca se ajuste al patrón lib.so

10.- Quiero Crear La Libraria Estática

Si por el contrario, lo que queremos es crear una biblioteca estática (o queremos tener las dos versiones, para poder ofrecer copias linkadas estáticamente), deberemos proceder como sigue.
Nota: El linker, en su búsqueda de bibliotecas, busca primero un fichero llamado lib.so, seguido de lib.a. Si llamamos a las dos bibliotecas (la versión estática y la versión dinámica) por el mismo nombre, no podremos controlar de forma general cual es la que se va a linkar en cada caso (se linkará siempre con la dinámica, al encontrarse primero).

Por esta razón, se recomienda siempre que vayamos a tener las dos versiones de una misma biblioteca, nombrar a la biblioteca estática como lib_s.a, mientras que a la dinámica la llamaremos lib.so. Así para linkar estáticamente, haremos:

gcc -o programa -l_s

para linkar con la versión estática, mientras que haremos:

gcc -o programa -l

para linkar con la versión dinámica.

10.1.- Compilación de las fuentes

Para compilar los fuentes, no tomaremos ninguna medida especial. Así como la posición de los objetos se decide en la fase de linkado, no es necesario compilar con la opción -f PIC (aunque es posible seguir haciéndolo).

10.2.- Linkado de los objetos en la biblioteca

En el caso de las bibliotecas estáticas, no se realiza la fase de linkado. Por otro lado, se introducen todos los objetos en un archivo con el comando ar(1). Posteriormente, para resolver rapidamente todos los símbolos existentes en la biblioteca, es recomendable ejecutar el comando ranlib(1) sobre la biblioteca (aunque no es imprescindible, la no ejecución de este comando puede dar lugar a módulos no enlazados en el ejecutable, debido a que cuando se procesó el módulo por parte del linker al procesar la biblioteca, aún no se había producido la dependencia ---indirecta--- de este módulo, por ser éste requerido por otro módulo existente mas adelante en el archivo, ésto puede dar lugar a que se requieran varias pasadas de la misma biblioteca para que se resuelvan todas las referencias).

10.3.- Instalación de la biblioteca

Las bibliotecas estáticas se llamarán lib.a, si solamente vamos a mantener la biblioteca estática, mientras que es recomendable llamarlas por otro nombre, en el caso de tener los dos tipos de bibliotecas (yo sugiero llamarlas lib_s.a, con el fin de poder controlar si cargamos la biblioteca estática o la dinámica)

El proceso de enlazado permite la introducción de la opción -static. Esta opción controla la carga del módulo /lib/ld-linux.so, y no afecta al orden de búsqueda de bibliotecas, con lo que si ponemos -static y ld(1) encuentra una biblioteca dinámica, seguirá trabajando con ella (en vez de su versión estática). Esto provocará errores en tiempo de ejecución (por la llamada a rutinas de una biblioteca que no están en el ejecutable---el módulo de carga dinámica no está enlazado y por tanto no se puede realizar este proceso).

11.- Enlazado dinámico vs. Enlazado estático

Supongamos que queremos distribuir un programa que hace uso de una biblioteca, la cual estamos autorizados a distribuir incluida estáticamente en un programa pero solo de esta forma. (un ejemplo de esto son las aplicaciones desarrolladas con Motif).

Para la realización de este tipo de software, tenemos dos opciones: Producir un ejecutable linkado estáticamente (con todas las bibliotecas estáticas, producto de archivos .a) y que no utilice el cargador dinámico para nada. Este tipo de programas se cargan de una sola vez, y no requieren de tener ninguna biblioteca instalada (ni siquiera /lib/ld-linux.so). Por el contrario, tienen el inconveniente de llevar todo el software necesario dentro del ejecutable y por esta razón, los binarios suelen ser inmensamente grandes.

Por otro lado, podemos distribuir nuestro software enlazado dinámicamente. Esto quiere decir que en el entorno donde se ejecute la aplicación se deberán aportar todas las bibliotecas dinámicas correspondientes. El ejecutable es muy pequeño, aunque a veces no es posible tener todas las bibliotecas disponibles (por ejemplo para aquellas personas que no hayan comprado Motif, la no disponibilidad de la biblioteca compartida, puede provocar problemas para poder ejecutar el programa).

Existe un tercer método, mixto, en el que las bibliotecas se enlazan dinámicamente, salvo aquella biblioteca conflictiva, en la que usaremos la biblioteca estática, en vez de la compartida. En este caso se tiene una solución mixta que permite la distribución cómoda del software.

Por ejemplo, yo podría compilar tres versiones de un programa del siguiente modo:

gcc -static -o programa.estatico programa.o -lm_s -lXm_s -lXt_s -lX11_s -lXmu_s -lXpm_s

gcc -o programa.dinamico programa.o-lm -lXm -lXt -lX11 -lXmu -lXpm

gcc -o programa.mixto programa.o -lm -lXm_s -lXt -lX11 -lXmu -lXpm

En el tercer caso, solo se linkará estáticamente la biblioteca de Motif (-lXm_s) enlazándose dinámicamente el resto. El entorno donde se ejecute el programa deberá disponer de versiones dinámicas de las bibliotecas libm.so.xx libXt.so.xx libX11.so.xx libXmu.so.xx y libXpm.so.xx


Para mas informacion:
Consultar HOWTO-ELF