Apache 1.3
Soporte de Objetos Compartidos Dinámicos (Dynamic Shared Object - DSO)

Originalmente escrito por
Ralf S. Engelschall <rse@apache.org>, Abril 1998

Algo de información

En los sucesores modernos de Unix existe un ingenioso mecanismo llamado carga/enlace dinámico de Objetos Compartidos y Dinámicos (DSO), que proporciona un modo de construir un pedazo de código en un formato especial y que permite cargarlo en tiempo de ejecución en el espacio de direcciones de un programa ejecutable.

Esta carga generalmente puede hacerse de dos modos: automáticamente a través de un programa de sistema llamado ld.so y cuando un programa ejecutable ha arrancado, o manualmente desde dentro de un programa en ejecución por medio de interfaz de sistema al cargador de Unix con llamadas al sistema de tipo dlopen()/dlsym().

Del primer modo los DSO son llamados bibliotecas compartidas o bibliotecas DSO y nombrados como libfoo.so o libfoo.so.1.2. Residen en un directorio de sistema (generalmente /usr/lib) y el enlace al programa ejecutable se establece en tiempo de compilación especificando el parámetro -lfoo al enlazador (linker). Las bibliotecas así codificadas referencia al ejecutable, de modo que al arrancar el cargador de Unix es capaz de localizar libfoo.so en /usr/lib, en los paths especificados con parámetros pasados al linker como -R o en paths configurados por medio de variables de entorno en LD_LIBRARY_PATH. A partir de aquí resuelve cualquier símbolo en el programa ejecutable que esté disponible en el DSO.

Los símbolos en el programa ejecutable normalmente no se referencian por el DSO (porque es una biblioteca reutilizable de código general) y así no se debe hacer ningún tipo de resolución. El programa ejecutable no necesita hacer nada por su cuenta para utilizar los símbolo de DSO porque la resolución completa la lleva a cabo el cargador de Unix. (De hecho el código para invocar ld.so forma parte del código de arranque que está enlazado a cada programa ejecutable limitado a ser no estático). La ventaja de la carga dinámica de código común de biblioteca es obvia: el código de biblioteca necesita ser almacenado sólo una vez, en una biblioteca de sistema como libc.so, ahorrando espacio en disco para cada programa.

De la segunda manera los DSO son normalmente llamados objetos compartidos o ficheros DSO y se les puede asignar un nombre con cualquier extensión (aunque el nombre estándar es foo.so). Estos ficheros suelen permanecer dentro de un directorio específico de cada programa y no se establece ningún link (enlace) automático al programa ejecutable donde son usados. En lugar de esto, el programa ejecutable carga manualmente el DSO en tiempo de ejecución a un espacio de direcciones por medio de dlopen(). En ese momento DSO realiza ninguna resolución de símbolos para el programa ejecutable. En su lugar el cargador de Unix automáticamente resuelve cualquier símbolo en el DSO del conjunto de símbolos exportados por el programa ejecutable y que ya ha sido cargado por las librerías DSO (especialmente todos los símbolos de libc.so). De este modo DSO tiene conocimiento del conjunto de símbolos del programa ejecutable como si hubiera sido enlazado estáticamente con él desde el principio.

Finalmente, para aprovechar el API de DSO el programa ejecutable debe resolver símbolos particulares de DSO a través de dlsym() para ser utilizados posteriormente dentro de tablas enviadas etc. En otras palabras: el programa ejecutable debe resolver manualmente cada símbolo que necesita a fin de poder utilizarlo. La ventaja de este mecanismo es que las partes opcionales del programa no necesitan ser cargadas (y de este modo no gastan memoria) hasta que el programa en cuestión las necesite. Cuando sean requeridos, estas partes del programa pueden ser cargadas dinámicamente para extender la funcionalidad básica del programa.

Aunque este mecanismo del DSO parece sencillo, se da un paso difícil: la resolución de símbolos desde el programa ejecutable para el DSO cuando se usa un DSO para extender un programa (la segunda manera). ¿Porqué? Porque resolver de modo inverso símbolos DSO desde el símbolo del programa ejecutable va en contra del diseño de la biblioteca (la biblioteca no tiene conocimiento del programa que lo está usando) y ni está disponible en todas las plataformas ni estandarizado. En la práctica los símbolos globales de el programa ejecutable no son a menudo reexportados y, de este modo, no están no están en disposición de ser usados en un DSO. Cuando se usa DSO para extender un programa en tiempo de ejecución, el principal problema que debe uno resolver es encontrar un modo de forzar al enlazador para que exporte todos los símbolos globales.

Windows y NetWare proporcionan funcionalidades semejantes, aunque hayan sido implementadas de una forma algo diferente que el método DSO de Unix que se describe en este documento. En particular, los módulos DSO (DLL's y NML's respectivamente) se construyen de forma diferente que en sus primos Unix. Este documento no pretende explorar el tema de la construcción de módulos DSO en esas plataformas. Sin embargo la descripción de mod_so y su configuración son semejantes.

Utilización práctica

La aproximación más usual es la de la biblioteca compartida porque es para lo que se diseñó el DSO, de ahí que sea usado para casi todo tipo de bibliotecas que proporcione el sistema operativo. Por otro lado, el uso de objetos compartidos para extender un programa no es usado por muchos programas.

Hasta 1998 sólo había unos pocos paquetes disponibles que usaran el mecanismo DSO para extender su funcionalidad en tiempo de ejecución: Perl 5 (por medio de su mecanismo XS y el módulo DynaLoader), Netscape Server, etc. Empezando con la versión 1.3, Apache se unión a la panda porque ya usaba el concepto de módulo para extender su funcionalidad y usar internamente una aproximación basada en una "lista de envíos" para enlazar módulos externos a las funcionalidades básicas de Apache. Así que Apache está realmente predestinado a usar DSO para cargar sus módulos en tiempo de ejecución.

Hasta la versión 1.3, el sistema de configuración de Apache tiene soporte opcional para sacar provecho del método DSO: compilación del programa central de Apache produciendo una librería DSO para ser compartida y compilación de módulos Apache en ficheros de una librería DSO que serán cargados explícitamente durante la ejecución.

Implementación

El soporte DSO para cargar módulos individuales de Apache está asentado en un módulo llamado mod_so.c, que debe ser estáticamente compilado en el núcleo de Apache. Es el único módulo, además de http_core.c que no puede ser añadido a DSO por sí mismo (bootstrapping, secuencia de instrucciones iniciales). Prácticamente todos los otros módulos distribuidos de Apache pueden ser emplazados en DSO habilitando para ellos el DSO build for vía configure --enable-shared (vea el fichero INSTALL) o cambiando el comando AddModule en src/Configuration al comando SharedModule (vea el fichero src/INSTALL). Después de que un módulo sea compilado en un DSO, por ejemplo, mod_foo.so puede usar el comando mod_so LoadModule en el fichero httpd.conf para cargar este módulo cuando arranque el servidor.

Para simplificar la creación de ficheros DSO para módulos Apache (especialmente para módulos de terceros) se dispone de un nuevo programa llamado apxs (APache eXtenSion). Se puede usar para construir módulos DSO fuera del árbol fuente de Apache. La idea es simple: cuando se instala Apache, el proceso make install de configure instala los ficheros C de cabecera de Apache y mete en apxs los flags del compilador y enlazador dependientes de la plataforma para construir ficheros DSO. De este modo el usuario puede usar apxs para compilar sus módulos fuente de Apache sin el árbol de código fuente de Apache y sin tener que tocar los flags del compilador y del enlazador dependientes de la plataforma para soportar DSO.

Para emplazar dentro de una biblioteca DSO el núcleo completo de Apache (sólo requerido en algunas de las plataformas soportadas para forzar al enlazador a exportar los símbolos del núcleo de apache, un requisito para la modularización de DSO) se debe habilitar la regla SHARED_CORE a través de configure con la opción --enable-rule=SHARED_CORE (consulte el fichero INSTALL) o cambiando el comando Rule en su fichero Configuration por Rule SHARED_CORE=yes (vea el fichero src/INSTALL). En ese momento el código del núcleo es emplazado en una biblioteca DSO llamada libhttpd.so. Ya que no se puede enlazar DSO con una librería estática, en todas las plataformas, se ha creado un programa ejecutable adicional llamado libhttpd.ep que enlaza este código estático y provee de a la función main(). Finalmente, el programa ejecutable httpd es sustituido por una secuencia de instrucciones (bootstrapping) que automáticamente se asegura que el cargador Unix sea capaz de cargar y arrancar libhttpd.ep pasándole LD_LIBRARY_PATH a libhttpd.so.

Plataformas soportadas

El script src/Configure un conocimiento limitado de cómo compilar ficheros DSO porque, como se mencionó anteriormente, esto depende fuertemente de la plataforma. Sin embargo, la mayoría de las plataformas Unix son soportadas. El estado actual (a mayo de 1999) es este:

Sumario de utilización

Para proporcionarle un vistazo de las características DSO en Apache 1.3, aquí tiene un breve y conciso sumario:

  1. Emplazar el código del núcleo de Apache (todas las cosas que conforman el binario httpd) en un DSO libhttpd.so, un programa ejecutable libhttpd.ep y una secuencia inicial (bootstrapping) ejecutable httpd (Nota. Esto sólo es necesario en algunas de las plataformas soportadas para forzar al enlazador a exportar los símbolos del núcleo de Apache, lo cual es un requisito para la modularización DSO):

    • Compilar e instalar a través de configure (preferido):
      $ ./configure --prefix=/path/to/install
                    --enable-rule=SHARED_CORE ...
      $ make install
      
    • Compilar e instalar manualmente:
      - Edit src/Configuration:
        << Rule SHARED_CORE=default
        >> Rule SHARED_CORE=yes
        << EXTRA_CFLAGS= 
        >> EXTRA_CFLAGS= -DSHARED_CORE_DIR=\"/path/to/install/libexec\"
      $ make 
      $ cp src/libhttpd.so* /path/to/install/libexec/
      $ cp src/libhttpd.ep  /path/to/install/libexec/
      $ cp src/httpd        /path/to/install/bin/
      
  2. Compilar e instalar un módulo Apache distribuido, digamos mod_foo.c, en su propio DSO mod_foo.so:

    • Compilar e instalar a través de configure (preferido):
      $ ./configure --prefix=/path/to/install
              --enable-shared=foo
      $ make install
      
    • Compilar e instalar manualmente:
      - Edite src/Configuration:
        << AddModule    modules/xxxx/mod_foo.o
        >> SharedModule modules/xxxx/mod_foo.so
      $ make
      $ cp src/xxxx/mod_foo.so /path/to/install/libexec
      - Edite /path/to/install/etc/httpd.conf
        >> LoadModule foo_module /path/to/install/libexec/mod_foo.so
      
  3. Compilar e instalar un módulo de terceros, digamos mod_foo.c, en su propio DSO mod_foo.so

    • Compilar e instar por medio de configure (preferido):
      $ ./configure --add-module=/path/to/3rdparty/mod_foo.c 
              --enable-shared=foo
      $ make install
      
    • Compilar e instalar manualmente:
      $ cp /path/to/3rdparty/mod_foo.c /path/to/apache-1.3/src/modules/extra/
      - Edite src/Configuration:
        >> SharedModule modules/extra/mod_foo.so
      $ make
      $ cp src/xxxx/mod_foo.so /path/to/install/libexec
      - Edite /path/to/install/etc/httpd.conf
        >> LoadModule foo_module /path/to/install/libexec/mod_foo.so
      

  4. Compilar e instalar un módulo de terceros, mod_foo.c, en su propio DSO mod_foo.so fuera del árbol fuente de Apache:

    • Compilar e instalar por medio de apxs:
      $ cd /path/to/3rdparty
      $ apxs -c mod_foo.c
      $ apxs -i -a -n foo mod_foo.so
      

Ventajas y desventajas

Las características anteriores de DSO en Apache 1.3 tienen las siguientes ventajas:

DSO presenta los siguientes inconvenientes: