stringtranslate.com

Carga dinámica

La carga dinámica es un mecanismo por el cual un programa de computadora puede, en tiempo de ejecución , cargar una biblioteca (u otro binario ) en la memoria, recuperar las direcciones de funciones y variables contenidas en la biblioteca, ejecutar esas funciones o acceder a esas variables y descargar la biblioteca de la memoria. Es uno de los tres mecanismos por los cuales un programa de computadora puede usar algún 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 de computadora se inicie en ausencia de estas bibliotecas, descubra bibliotecas disponibles y potencialmente obtenga funcionalidad adicional. [1] [2]

Historia

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

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

Las bibliotecas compartidas se agregaron a Unix en la década de 1980, pero inicialmente sin la capacidad de permitir que un programa cargue 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 con 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 necesaria 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 , ofrecen carga dinámica con la biblioteca "dl" del lenguaje de programación C. El sistema operativo Windows ofrece 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 == NULL ) { // informar error ... } else { // usar el resultado en una llamada a dlsym }            

macOS

Como biblioteca Unix :

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

Como marco de macOS :

void * sdl_library = dlopen ( "/Library/Frameworks/SDL.framework/SDL" , RTLD_LAZY ); if ( sdl_library == NULL ) { // informar 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 ]) { // Utilizar las clases y funciones en el paquete. } else { // Manejar el error. }           

Ventanas

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

Extraer contenidos 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 == NULL ) { // informar error ... } else { // convertir initializer a su tipo apropiado y usarlo }            

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

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

Ventanas

Inicializador FARPROC = GetProcAddress ( sdl_library , "SDL_Init" ); if ( inicializador == NULL ) { // informar error ... } else { // convertir el inicializador a su tipo y uso adecuados }           

Convertir un puntero a una función de biblioteca

El resultado de dlsym()o GetProcAddress()debe convertirse en un puntero del tipo apropiado antes de poder usarse.

Ventanas

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

tipo definido INT_PTR ( * FARPROC )( vacío );  

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

typedef void ( * sdl_init_function_type )( void ); sdl_init_function_type init_func = ( sdl_init_function_type ) inicializador ;      

Sistema operativo Unix (POSIX)

Según la especificación POSIX, el resultado de dlsym()es un voidpuntero. Sin embargo, no es necesario que un puntero de función tenga el mismo tamaño que un puntero de objeto de datos y, por lo tanto, una conversión válida entre un 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 que se utilizan hoy en día, los punteros a funciones y objetos son convertibles de facto . El siguiente fragmento de código demuestra una solución alternativa que permite realizar la conversión de todos modos en muchos sistemas:

typedef void ( * sdl_init_function_type )( void ); sdl_init_function_type init_func = ( sdl_init_function_type ) inicializador ;     

El fragmento anterior emitirá una advertencia sobre algunos compiladores: warning: dereferencing type-punned pointer will break strict-aliasing rulesOtra solución alternativa es:

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

que desactiva la advertencia incluso si el alias estricto está en vigor. Esto hace uso del hecho de que la lectura de un miembro de la unión diferente al que se escribió más recientemente (llamado " juego de palabras de tipos ") es común y se permite explícitamente 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 estrictamente el caso 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 del puntero de función en sistemas POSIX

El hecho es que cualquier conversión entre punteros de funciones y objetos de datos debe considerarse como una extensión de implementación (inherentemente no portable), y que no existe una forma "correcta" para una conversión directa, ya que en este sentido los estándares POSIX e ISO se contradicen entre sí.

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

Para la versión posterior del estándar (número 7, 2008), se discutió el problema y la conclusión fue que los punteros de función deben ser convertibles para void*cumplir con el estándar 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 (es decir, 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 de objeto, este puntero siempre se puede recuperar legalmente mediante una llamada a dlsym()y una conversión posterior. Sin embargo, este enfoque requiere mantener punteros separados a todas las funciones que se van a utilizar externamente y los beneficios suelen ser pequeños.

Descargando la biblioteca

La carga de una biblioteca hace que se asigne memoria; la biblioteca debe desasignarse para evitar una fuga de memoria . Además, no descargar una biblioteca puede impedir las operaciones del sistema de archivos en el archivo que contiene la biblioteca. 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 del 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 en instancias de esa clase desde la aplicación principal probablemente provocará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 se cierre la DLL conduce a un comportamiento indefinido.

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

dlclose ( sdl_library );

Ventanas

Biblioteca gratuita ( sdl_library );

Biblioteca especial

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 tiene una API para buscar en varios módulos un símbolo por su nombre.

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

void * este_proceso = dlopen ( NULL , 0 );   

Ventanas

HMODULE este_proceso = GetModuleHandle ( NULL );   HMODULE este_proceso_de_nuevo ; GetModuleHandleEx ( 0 , 0 , & este_proceso_de_nuevo ); 

En Java

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

Tipo de clase = ClassLoader . getSystemClassLoader (). loadClass ( nombre ); Objeto obj = tipo . newInstance ();      

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

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

Sin embargo, no existe una forma sencilla de descargar una clase de forma controlada. Las clases cargadas solo se pueden descargar de forma controlada, es decir, cuando el programador así lo desea, si el cargador de clases utilizado para cargar la clase no es el cargador de clases del sistema y se descarga a su vez. Al hacerlo, es necesario observar varios detalles para garantizar que la clase se descargue realmente. Esto hace que la descarga de clases sea tediosa.

La descarga implícita de clases, es decir, de forma no controlada por el recolector de basura, ha cambiado algunas veces en Java. Hasta Java 1.2, el recolector de basura podía descargar una clase siempre que considerara que necesitaba el espacio, independientemente del cargador de clases que se utilizara para cargar la clase. A partir de Java 1.2, las clases cargadas a través del cargador de clases del sistema nunca se descargaban y las clases cargadas a través de otros cargadores de clases solo cuando se descargaba este otro cargador de clases. A partir de Java 6, las clases pueden contener un marcador interno que indique al recolector de basura que pueden descargarse si el recolector de basura desea hacerlo, independientemente del cargador de clases utilizado para cargar la clase. El recolector de basura es libre de ignorar esta sugerencia.

De manera similar, las bibliotecas que implementan métodos nativos se cargan dinámicamente mediante 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 "dañino". [9] El lenguaje de programación Go , de algunos de los mismos desarrolladores de 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 vinculan estáticamente al binario compilado. [10]

Véase también

Referencias

  1. ^ ab Autoconf, Automake y Libtool: carga dinámica
  2. ^ "Linux4U: Carga dinámica ELF". Archivado desde el original el 11-03-2011 . Consultado el 31-12-2007 .
  3. ^ "Uso de 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 de marzo de 2013.
  5. ^ Ho, W. Wilson; Olsson, Ronald A. (1991). "Un enfoque hacia la vinculación dinámica genuina". Software: práctica y experiencia . 21 (4): 375–390. CiteSeerX 10.1.1.37.933 . doi :10.1002/spe.4380210404. S2CID  9422227. 
  6. ^ "Compatibilidad con objetos compartidos dinámicos (DSO) de Apache 1.3". Archivado desde el 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. ^ Documentación de POSIX sobre dlopen() (números 6 y 7).
  9. ^ "Enlace dinámico". cat-v.org . 9front . Consultado el 22 de diciembre de 2014 .
  10. ^ "Ir a Preguntas frecuentes".

Lectura adicional

Enlaces externos