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
|