Articulo de referencia

Carga dinámica

La carga dinámica es un mecanismo mediante el cual un programa informático puede, en tiempo de ejecución , cargar una biblioteca (u otro binario ) en la memoria, recuperar las d...

La carga dinámica es un mecanismo mediante el cual un programa informático puede, en tiempo de ejecución , cargar una biblioteca (u otro binario ) en la memoria, recuperar las direcciones de las funciones y variables contenidas en la biblioteca, ejecutar dichas funciones o acceder a dichas variables, y descargar la biblioteca de la memoria. Es uno de los tres mecanismos mediante los cuales un programa informático puede utilizar otro software dentro del programa; los otros son el enlace estático y el enlace dinámico . A diferencia del enlace estático y el enlace dinámico, la carga dinámica permite que un programa informático se inicie en ausencia de estas bibliotecas, descubra las bibliotecas disponibles y potencialmente obtenga funcionalidad adicional. [ 1 ] [ 2 ]

Historia

La carga dinámica era una técnica común en los sistemas operativos de IBM para System/360 , como OS/360 , especialmente para subrutinas de E/S y bibliotecas de tiempo de ejecución de COBOL y PL/I , y continúa utilizándose en los sistemas operativos de IBM para z/Architecture , como z/OS . Desde la perspectiva del programador de aplicaciones, la carga es prácticamente transparente, ya que la gestiona principalmente el sistema operativo (o su subsistema de E/S). Las principales ventajas son:

  • Las correcciones ( parches ) a los subsistemas corrigieron todos los programas a la vez, sin necesidad de volver a enlazarlos.
  • Las bibliotecas podrían estar protegidas contra modificaciones no autorizadas.

El sistema de procesamiento de transacciones estratégicas de IBM , CICS (desde la década de 1970 en adelante), utiliza ampliamente la carga dinámica tanto para su núcleo como para la carga normal de programas de aplicación . Las correcciones a los programas de aplicación podían realizarse sin conexión y las nuevas copias de los programas modificados se cargaban dinámicamente sin necesidad de reiniciar CICS [ 3 ] [ 4 ] (que puede, y frecuentemente lo hace, funcionar las 24 horas del día, los 7 días de la semana ).

Las bibliotecas compartidas se añadieron a Unix en la década de 1980, pero inicialmente sin la capacidad de permitir que un programa cargara bibliotecas adicionales después del inicio. [ 5 ]

Usos

La carga dinámica se utiliza con mayor frecuencia en la implementación de complementos de software . [ 1 ] Por ejemplo, los archivos de complemento de "objeto compartido dinámico" del servidor web Apache son bibliotecas que se cargan en tiempo de ejecución mediante carga dinámica. [ 6 ] La carga dinámica también se utiliza en la implementación de programas informáticos donde varias bibliotecas diferentes pueden proporcionar la funcionalidad requerida y donde el usuario tiene la opción de seleccionar qué biblioteca o bibliotecas proporcionar.*.dso

En C/C++

No todos los sistemas admiten la carga dinámica. Los sistemas operativos tipo Unix, como macOS , Linux y Solaris, proporcionan carga dinámica mediante la biblioteca "dl" del lenguaje de programación C. El sistema operativo Windows proporciona carga dinámica a través de la API de Windows .

Resumen

Cargando la biblioteca

La carga de la biblioteca se realiza con LoadLibraryo LoadLibraryExen Windows y con dlopenen sistemas operativos tipo Unix . A continuación se muestran algunos ejemplos:

La mayoría de los sistemas operativos tipo Unix (Solaris, Linux, *BSD, etc.)

void * sdl_library = dlopen ( "libSDL.so" , RTLD_LAZY ); if ( ! sdl_library ) { // informar de un error ... } else { // usar el resultado en una llamada a dlsym }

macOS

Como biblioteca de Unix :

void * sdl_library = dlopen ( "libSDL.dylib" , RTLD_LAZY ); if ( ! sdl_library ) { // informar de un error ... } else { // usar el resultado en una llamada a dlsym }

Como un framework de macOS :

void * sdl_library = dlopen ( "/Library/Frameworks/SDL.framework/SDL" , RTLD_LAZY ); if ( ! sdl_library ) { // informar de un error ... } else { // usar el resultado en una llamada a dlsym }

O si el marco o paquete contiene código Objective-C:

NSBundle * bundle = [ NSBundle bundleWithPath : @"/Library/Plugins/Plugin.bundle" ]; NSError * err = nil ; if ([ bundle loadAndReturnError :& err ]) { // Usar las clases y funciones del bundle. } else { // Manejar el error. }

Windows

HMODULE sdl_library = LoadLibrary ( TEXT ( "SDL.dll" )); if ( ! sdl_library ) { // informar de un error ... } else { // usar el resultado en una llamada a GetProcAddress }

Extracción del contenido de la biblioteca

La extracción del contenido de una biblioteca cargada dinámicamente se logra con GetProcAddressen Windows y con dlsymen sistemas operativos tipo Unix .

Sistemas operativos tipo Unix (Solaris, Linux, *BSD, macOS, etc.)

void * initializer = dlsym ( sdl_library , "SDL_Init" ); if ( ! initializer ) { // informar de un error ... } else { // convertir el inicializador a su tipo adecuado y usarlo }

En macOS, al usar paquetes Objective-C, también se puede:

Clase rootClass = [ bundle principalClass ]; // Alternativamente, se puede usar NSClassFromString() para obtener una clase por nombre. if ( rootClass ) { id object = [[ rootClass alloc ] init ]; // Usar el objeto. } else { // Informar de un error. }

Windows

FARPROC inicializador = GetProcAddress ( sdl_library , "SDL_Init" ); if ( ! inicializador ) { // informar error ... } else { // convertir inicializador a su tipo correcto y usarlo }

Conversión de un puntero a función de biblioteca

El resultado de dlsym()la operación GetProcAddress()debe convertirse a un puntero del tipo apropiado antes de poder utilizarse.

Windows

En Windows, la conversión es sencilla, ya que FARPROC es esencialmente un puntero a función :

typedef INT_PTR ( * FARPROC )( void );

Esto puede resultar problemático cuando se trata de recuperar la dirección de un objeto en lugar de una función. Sin embargo, normalmente se busca extraer funciones, por lo que esto no suele ser un problema.

typedef void ( * SDLInitFunctionType )( void ); SDLInitFunctionType init_func = ( SDLInitFunctionType ) initializer ;

Unix (POSIX)

Según la especificación POSIX, el resultado dlsym()es un voidpuntero. Sin embargo, un puntero a función no tiene por qué tener el mismo tamaño que un puntero a objeto de datos, por lo que una conversión válida entre el tipo void*y un puntero a una función puede no ser fácil de implementar en todas las plataformas.

En la mayoría de los sistemas actuales, los punteros a funciones y objetos son convertibles de facto . El siguiente fragmento de código muestra una solución alternativa que permite realizar la conversión en muchos sistemas:

typedef void ( * SDLInitFunctionType )( void ); SDLInitFunctionType init_func = ( SDLInitFunctionType ) initializer ;

El fragmento anterior generará una advertencia en algunos compiladores: warning: dereferencing type-punned pointer will break strict-aliasing rules. Otra solución alternativa es:

typedef void ( * SDLInitFunctionType )( void );unión { SDLInitFunctionType func ; void * obj ; } alias ;alias . obj = inicializador ; SDLInitFunctionType init_func = alias . func ;

lo que desactiva la advertencia incluso si el alias estricto está en efecto. Esto aprovecha el hecho de que leer de un miembro de la unión diferente al que se escribió más recientemente (llamado " type punning ") es común y está explícitamente permitido incluso si el alias estricto está en vigor, siempre que se acceda a la memoria a través del tipo de unión directamente. [ 7 ] Sin embargo, este no es el caso estrictamente aquí, ya que el puntero de función se copia para usarse fuera de la unión. Tenga en cuenta que este truco puede no funcionar en plataformas donde el tamaño de los punteros de datos y el tamaño de los punteros de función no es el mismo.

Solución del problema de los punteros a funciones en sistemas POSIX

Lo cierto es que cualquier conversión entre punteros a funciones y a objetos de datos debe considerarse una extensión de implementación (intrínsecamente no portable), y que no existe una forma "correcta" de realizar una conversión directa, puesto que en este sentido las normas POSIX e ISO se contradicen entre sí.

Debido a este problema, la documentación POSIX para dlsym()la versión obsoleta 6 indicaba que "una versión futura podría agregar una nueva función para devolver punteros a funciones, o bien la interfaz actual podría quedar obsoleta en favor de dos nuevas funciones: una que devuelva punteros a datos y otra que devuelva punteros a funciones". [ 8 ]

Para la versión subsiguiente del estándar (edición 7, 2008), se discutió el problema y se llegó a la conclusión de que los punteros a funciones deben ser convertibles void*para cumplir con POSIX. [ 8 ] Esto requiere que los creadores de compiladores implementen una conversión funcional para este caso.

Si el contenido de la biblioteca se puede modificar (por ejemplo, en el caso de una biblioteca personalizada), además de la función en sí, se puede exportar un puntero a ella. Dado que un puntero a un puntero de función es en sí mismo un puntero a objeto, este puntero siempre se puede recuperar legalmente mediante una llamada dlsym()y la posterior conversión. Sin embargo, este enfoque requiere mantener punteros separados para todas las funciones que se vayan a utilizar externamente, y las ventajas suelen ser mínimas.

Descargando la biblioteca

La carga de una biblioteca implica la asignación de memoria; esta debe liberarse para evitar fugas de memoria . Además, no descargar una biblioteca puede impedir las operaciones del sistema de archivos en el archivo que la contiene. La descarga de la biblioteca se realiza con FreeLibraryen Windows y con en sistemas operativosdlclose tipo Unix . Sin embargo, la descarga de una DLL puede provocar fallos en el programa si los objetos de la aplicación principal hacen referencia a la memoria asignada dentro de la DLL. Por ejemplo, si una DLL introduce una nueva clase y se cierra, las operaciones posteriores sobre instancias de esa clase desde la aplicación principal probablemente causarán una violación de acceso a la memoria. Del mismo modo, si la DLL introduce una función de fábrica para instanciar clases cargadas dinámicamente, llamar o desreferenciar esa función después de que la DLL se haya cerrado provoca un comportamiento indefinido.

Sistemas operativos tipo Unix (Solaris, Linux, *BSD, macOS, etc.)

dlclose ( sdl_library );

Windows

Biblioteca gratuita ( sdl_library );

Biblioteca especializada

Las implementaciones de carga dinámica en sistemas operativos tipo Unix y Windows permiten a los programadores extraer símbolos del proceso que se está ejecutando actualmente.

Los sistemas operativos tipo Unix permiten a los programadores acceder a la tabla de símbolos global, que incluye tanto el ejecutable principal como las bibliotecas dinámicas cargadas posteriormente.

Windows permite a los programadores acceder a los símbolos exportados por el ejecutable principal. Windows no utiliza una tabla de símbolos global y no dispone de una API para buscar un símbolo por nombre en varios módulos.

Sistemas operativos tipo Unix (Solaris, Linux, *BSD, macOS, etc.)

void * this_process = dlopen ( NULL , 0 );

Windows

HMODULE this_process = GetModuleHandle ( NULL );HMODULE this_process_again ; GetModuleHandleEx ( 0 , 0 , & this_process_again );

En Java

En el lenguaje de programación Java , las clases se pueden cargar dinámicamente utilizando el ClassLoaderobjeto. Por ejemplo:

Clase tipo = ClassLoader.getSystemClassLoader () . loadClass ( nombre ) ; Objeto obj = tipo.newInstance ( ) ;

El mecanismo de reflexión también proporciona un medio para cargar una clase si aún no está cargada. Utiliza el cargador de clases de la clase actual:

Clase tipo = Clase.forName ( nombre ) ; Objeto obj = tipo.newInstance ( ) ;

Sin embargo, no existe una forma sencilla de descargar una clase de manera controlada. Las clases cargadas solo pueden descargarse de forma controlada, es decir, cuando el programador lo desea, si el cargador de clases utilizado para cargar la clase no es el cargador de clases del sistema y, además, se descarga. Para ello, es necesario tener en cuenta diversos detalles para garantizar que la clase se descargue correctamente. Esto hace que la descarga de clases sea un proceso tedioso.

La descarga implícita de clases, es decir, de forma no controlada por el recolector de basura, ha cambiado varias veces en Java. Hasta Java 1.2, el recolector de basura podía descargar una clase cuando lo considerara necesario, independientemente del cargador de clases utilizado para cargarla. A partir de Java 1.2, las clases cargadas mediante el cargador de clases del sistema nunca se descargaban, y las cargadas mediante otros cargadores de clases solo se descargaban cuando se descargaba este último. A partir de Java 6, las clases pueden contener un marcador interno que indica al recolector de basura que pueden descargarse si este lo desea, independientemente del cargador de clases utilizado para cargarlas. El recolector de basura puede ignorar esta indicación.

De manera similar, las bibliotecas que implementan métodos nativos se cargan dinámicamente utilizando el System.loadLibrarymétodo. No hay ningún System.unloadLibrarymétodo.

Plataformas sin carga dinámica

A pesar de su promulgación en la década de 1980 a través de Unix y Windows, algunos sistemas aún optaron por no agregar —o incluso eliminar— la carga dinámica. Por ejemplo, Plan 9 de Bell Labs y su sucesor 9front evitan intencionalmente el enlace dinámico, ya que lo consideran "perjudicial". [ 9 ] El lenguaje de programación Go , por algunos de los mismos desarrolladores que Plan 9, tampoco admitía el enlace dinámico, pero la carga de complementos está disponible desde Go 1.8 (febrero de 2017). El entorno de ejecución de Go y cualquier función de biblioteca se enlazan estáticamente en el binario compilado. [ 10 ]

Véase también

Referencias

  1. 1 2 Autoconf, Automake y Libtool: Carga dinámica
  2. "Linux4U: Carga dinámica de ELF" . Archivado del original el 11 de marzo de 2011. Consultado el 31 de diciembre de 2007 .
  3. "Utilizando los procedimientos proporcionados por CICS para instalar programas de aplicación" .
  4. "La solicitud IBM CEMT NEWCOPY o PHASEIN falla con NOT FOR HOLD PROG - Estados Unidos" . 15/03/2013.
  5. Ho, W. Wilson; Olsson, Ronald A. (1991). "Un enfoque para el enlace dinámico genuino". Software: Practice and Experience . 21 (4): 375– 390. CiteSeerX 10.1.1.37.933 . doi : 10.1002/spe.4380210404 . S2CID 9422227 .  
  6. "Soporte para objetos compartidos dinámicos (DSO) de Apache 1.3" . Archivado del original el 22 de abril de 2011. Consultado el 31 de diciembre de 2007 .
  7. Opciones de optimización de GCC 4.3.2: -fstrict-aliasing
  8. 1 2 Documentación POSIX sobredlopen() (problemas 6 y 7).
  9. "Enlace dinámico" . cat-v.org . 9front . Consultado el 22 de diciembre de 2014 .
  10. "Preguntas frecuentes" .

Lecturas adicionales

  • Silberschatz, Abraham; Galvin, Peter Baer; Gagne, Greg (2005). "Capítulo 8.1.4 "Carga dinámica" y Capítulo 8.1.5 "Enlace dinámico y bibliotecas compartidas"Conceptos de sistemas operativos . J. Wiley & Sons . ISBN 978-0-471-69466-3.
  • Enlaces generales
    • Carga dinámica en Linux4U
    • Compatibilidad con objetos compartidos dinámicos (DSO) por parte de Apache
    • Enlace dinámico de C++ mediante ejemplos
    • Ejemplo de carga dinámica de biblioteca (ejemplo práctico completo pero conciso)
    • Temas de programación de bibliotecas dinámicas de Apple Developer Connection (dirigidos a macOS)
  • API de C/C++ para Unix:
    • dlopen
    • dlsym
    • dlclose
  • API de Windows para C/C++:
    • Cargar biblioteca
    • Dirección del proceso
    • Biblioteca gratuita
    • DLL de carga retardada
  • API de Java:
    • Cargador de clases
    • Clase