2011-12-24 7 views
28

Mi pregunta, como el título mencionado, es obvia, y describo el escenario en detalle. Hay una clase llamada Singleton implementado por el patrón singleton de la siguiente manera, en singleton.h archivo:Múltiples instancias de singleton en bibliotecas compartidas en Linux

/* 
* singleton.h 
* 
* Created on: 2011-12-24 
*  Author: bourneli 
*/ 

#ifndef SINGLETON_H_ 
#define SINGLETON_H_ 

class singleton 
{ 
private: 
    singleton() {num = -1;} 
    static singleton* pInstance; 
public: 
    static singleton& instance() 
    { 
     if (NULL == pInstance) 
     { 
      pInstance = new singleton(); 
     } 
     return *pInstance; 
    } 
public: 
    int num; 
}; 

singleton* singleton::pInstance = NULL; 

#endif /* SINGLETON_H_ */ 

a continuación, hay un plugin llamado hello.cpp de la siguiente manera:

#include <iostream> 
#include "singleton.h" 

extern "C" void hello() { 
    std::cout << "singleton.num in hello.so : " << singleton::instance().num << std::endl; 
    ++singleton::instance().num; 
    std::cout << "singleton.num in hello.so after ++ : " << singleton::instance().num << std::endl; 
} 

se puede ver que el complemento llame al singleton y cambie el atributo num en el singleton.

pasado, hay una función principal de utilizar el producto único y el plugin de la siguiente manera:

#include <iostream> 
#include <dlfcn.h> 
#include "singleton.h" 

int main() { 
    using std::cout; 
    using std::cerr; 
    using std::endl; 

    singleton::instance().num = 100; // call singleton 
    cout << "singleton.num in main : " << singleton::instance().num << endl;// call singleton 

    // open the library 
    void* handle = dlopen("./hello.so", RTLD_LAZY); 

    if (!handle) { 
     cerr << "Cannot open library: " << dlerror() << '\n'; 
     return 1; 
    } 

    // load the symbol 
    typedef void (*hello_t)(); 

    // reset errors 
    dlerror(); 
    hello_t hello = (hello_t) dlsym(handle, "hello"); 
    const char *dlsym_error = dlerror(); 
    if (dlsym_error) { 
     cerr << "Cannot load symbol 'hello': " << dlerror() << '\n'; 
     dlclose(handle); 
     return 1; 
    } 

    hello(); // call plugin function hello 

    cout << "singleton.num in main : " << singleton::instance().num << endl;// call singleton 
    dlclose(handle); 
} 

y el makefile es el siguiente:

example1: main.cpp hello.so 
    $(CXX) $(CXXFLAGS) -o example1 main.cpp -ldl 

hello.so: hello.cpp 
    $(CXX) $(CXXFLAGS) -shared -o hello.so hello.cpp 

clean: 
    rm -f example1 hello.so 

.PHONY: clean 

es así, ¿cuál es la salida? pensé que es siguiente:

singleton.num in main : 100 
singleton.num in hello.so : 100 
singleton.num in hello.so after ++ : 101 
singleton.num in main : 101 

sin embargo, la salida real está siguiendo:

singleton.num in main : 100 
singleton.num in hello.so : -1 
singleton.num in hello.so after ++ : 0 
singleton.num in main : 100 

Esto demuestra que hay dos instancias de la clase singleton.

¿Por qué?

+0

¿Desea que este singleton sea único para el único proceso que está ejecutando? ¿O un singleton "en todo el sistema" en violación de todo lo que la memoria protegida nos ofrece? – sarnold

+0

La pregunta, a menos que se indique explícitamente, generalmente es cualquier cosa menos obvia. ¿Desea que las bibliotecas compartidas compartan el singleton o no?¿Teorizas acerca de cualquier comportamiento o realmente lo experimentas? No hay forma de saberlo a menos que nos lo diga. – 9000

+0

@sarnold: hay un patrón bien conocido de singletons en todo el sistema que no están limitados por el espacio de direcciones: se llama servidor. Pero hasta que el cartel original diga el propósito de su código, es difícil decir si este patrón se ajusta. – 9000

Respuesta

45

En primer lugar, generalmente debe utilizar el indicador -fPIC al compilar bibliotecas compartidas.

no usarlo "obras" en Linux de 32 bits, pero fallaría en 64 bits uno con un error similar al siguiente:

/usr/bin/ld: /tmp/ccUUrz9c.o: relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC 

En segundo lugar, el programa funcionará como se espera después de agregar -rdynamic a la línea de enlace para el ejecutable principal:

singleton.num in main : 100 
singleton.num in hello.so : 100 
singleton.num in hello.so after ++ : 101 
singleton.num in main : 101 

con el fin de entender por qué se requiere -rdynamic, lo que necesita saber acerca de la manera enlazador dinámico resuelve símbolos, y acerca de la dinámica symbo l mesa.

En primer lugar, echemos un vistazo a la tabla de símbolos dinámico para hello.so:

$ nm -C -D hello.so | grep singleton 
0000000000000b8c W singleton::instance() 
0000000000201068 B singleton::pInstance 
0000000000000b78 W singleton::singleton() 

Esto nos dice que hay dos definiciones de funciones débiles, y una variable global singleton::pInstance que son visibles al enlazador dinámico.

Ahora vamos a ver la tabla de símbolos estática y dinámica para el original example1 (vinculado sin -rdynamic):

$ nm -C example1 | grep singleton 
0000000000400d0f t global constructors keyed to singleton::pInstance 
0000000000400d38 W singleton::instance() 
00000000006022e0 B singleton::pInstance 
0000000000400d24 W singleton::singleton() 

$ nm -C -D example1 | grep singleton 
$ 

Así es: a pesar de que la singleton::pInstance está presente en el archivo ejecutable como una variable global, ese símbolo no está presente en la tabla de símbolos dinámica y, por lo tanto, es "invisible" para el vinculador dinámico.

Debido a que el enlazador dinámico "no sabe" que example1 ya contiene una definición de singleton::pInstance, que no es vinculante para esa variable en el interior hello.so a la definición existente (que es lo que realmente quiere).

Cuando añadimos -rdynamic a la línea de enlace:

$ nm -C example1-rdynamic | grep singleton 
0000000000400fdf t global constructors keyed to singleton::pInstance 
0000000000401008 W singleton::instance() 
00000000006022e0 B singleton::pInstance 
0000000000400ff4 W singleton::singleton() 

$ nm -C -D example1-rdynamic | grep singleton 
0000000000401008 W singleton::instance() 
00000000006022e0 B singleton::pInstance 
0000000000400ff4 W singleton::singleton() 

Ahora la definición de singleton::pInstance dentro del ejecutable principal es visibles al enlazador dinámico, y así será "reutilización" esa definición al cargar hello.so :

LD_DEBUG=bindings ./example1-rdynamic |& grep pInstance 
    31972: binding file ./hello.so [0] to ./example1-rdynamic [0]: normal symbol `_ZN9singleton9pInstanceE' 
+0

-rdynmaic opción resuelve este problema, gracias. Agradezco tu ayuda :) – bourneli

+1

@BourneLi Si la respuesta funciona para ti, se supone que debes aceptarla. –

+0

¿Podría explicar cómo hacer lo contrario? Tengo una biblioteca base compartida a la que están vinculados dos complementos, la biblioteca compartida exporta un singleton; pero quiero que cada complemento mantenga su propia copia del singleton. No tengo -dinámica listada como una bandera de compilación. –

4

Debe tener cuidado al usar las bibliotecas compartidas cargadas en tiempo de ejecución. Tal construcción no es estrictamente parte del estándar de C++, y usted debe considerar cuidadosamente cuál es la semántica de dicho procedimiento.

En primer lugar, lo que sucede es que la biblioteca compartida ve su propia variable global separada singleton::pInstance. ¿Porqué es eso? Una biblioteca que se carga en tiempo de ejecución es esencialmente un programa separado e independiente que simplemente no tiene un punto de entrada. Pero todo lo demás es realmente como un programa separado, y el cargador dinámico lo tratará así, p. inicializar variables globales, etc.

El cargador dinámico es un recurso de tiempo de ejecución que no tiene nada que ver con el cargador estático. El cargador estático es parte de la implementación estándar de C++ y resuelve todos los símbolos del programa principal antes de que comience el programa principal. El cargador dinámico, por otro lado, solo ejecuta después de el programa principal ya ha comenzado. ¡En particular, todos los símbolos del programa principal ya tienen que resolverse! Simplemente hay no manera de reemplazar automáticamente los símbolos del programa principal de forma dinámica. Los programas nativos no se "administran" de ninguna manera que permita una reenlazada sistemática. (Tal vez algo puede ser pirateado, pero no de manera sistemática, portátil).

Así que la verdadera pregunta es cómo resolver el problema de diseño que está intentando. La solución aquí es pasar identificadores a todas las variables globales a las funciones de complemento. Haga que su programa principal defina la copia original (y única) de la variable global, e inicialice su biblioteca con un puntero a eso.

Por ejemplo, su biblioteca compartida podría verse así.En primer lugar, añadir un puntero a puntero a la clase Singleton:

class singleton 
{ 
    static singleton * pInstance; 
public: 
    static singleton ** ppinstance; 
    // ... 
}; 

singleton ** singleton::ppInstance(&singleton::pInstance); 

Ahora usa *ppInstance en lugar de pInstance todas partes.

En el complemento, configurar el singleton al puntero desde el programa principal:

void init(singleton ** p) 
{ 
    singleton::ppInsance = p; 
} 

Y la función principal, llamar a la inicialización del plugin:

init_fn init; 
hello_fn hello; 
*reinterpret_cast<void**>(&init) = dlsym(lib, "init"); 
*reinterpret_cast<void**>(&hello) = dlsym(lib, "hello"); 

init(singleton::ppInstance); 
hello(); 

Ahora las acciones de plugins del mismo puntero a la instancia singleton como el resto del programa.

+2

Si fuerza que "singleton" se inicialice con una dirección global, entonces ya no es * singleton *. Su respuesta es incorrecta en muchos detalles, y la solución que usted propuso es (en mi humilde opinión) falsa. –

+0

Estoy de acuerdo. Todo el propósito de usar el patrón singleton se pierde con esta solución. – volpato

2

creo que la respuesta es sencilla aquí: http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html

Cuando se tiene una variable estática, se almacena en el objeto (.o, .a y/o .so)

Si el objeto final a ser ejecutado contiene dos versiones del objeto, el comportamiento es inesperado , como por ejemplo, llamar al Destructor de un objeto Singleton.

Utilizar el diseño adecuado, como declarar el miembro estático en el archivo principal y usar el -dinámico/fpic y usar las directivas de compilación "" hará la parte más difícil para usted.

Ejemplo comunicado makefile:

$ g++ -rdynamic -o appexe $(OBJ) $(LINKFLAGS) -Wl,--whole-archive -L./Singleton/ -lsingleton -Wl,--no-whole-archive $(LIBS) 

Hope esto funciona!

0

¡Gracias a todos por sus respuestas!

Como seguimiento para Linux, también puede usar RTLD_GLOBAL con dlopen(...), por man dlopen (y los ejemplos que tiene). He hecho una variante del ejemplo de la OP en este directorio: github tree Ejemplo de salida: output.txt

rápida y sucia:

  • Si no quieren tener que enlazar manualmente en cada símbolo a su main, mantenga los objetos compartidos alrededor. (por ejemplo, si creó *.so objetos para importar a Python)
  • Inicialmente puede cargar en la tabla de símbolos global, o hacer un NOLOAD + GLOBAL volver a abrir.

Código:

#if MODE == 1 
// Add to static symbol table. 
#include "producer.h" 
#endif 
... 
    #if MODE == 0 || MODE == 1 
     handle = dlopen(lib, RTLD_LAZY); 
    #elif MODE == 2 
     handle = dlopen(lib, RTLD_LAZY | RTLD_GLOBAL); 
    #elif MODE == 3 
     handle = dlopen(lib, RTLD_LAZY); 
     handle = dlopen(lib, RTLD_LAZY | RTLD_NOLOAD | RTLD_GLOBAL); 
    #endif 

Modos:

  • Modo 0: carga diferida nominal (no funciona)
  • Modo 1: Incluir archivo para agregar a la tabla de símbolos estática.
  • Modo 2: Cargar inicialmente con RTLD_GLOBAL
  • Modo 3: Recargar usando RTLD_NOLOAD | RTLD_GLOBAL
Cuestiones relacionadas