Wiele instancji Singletona w bibliotekach współdzielonych na Linuksie

Moje pytanie, jak wspomniano w tytule, jest oczywiste i opisuję scenariusz szczegółowo. Istnieje klasa o nazwie singleton zaimplementowana przez wzorzec singleton w następujący sposób, w pliku singleton.h:

/*
 * 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_ */

Następnie jest wtyczka o nazwie hello.cpp jak następuje:

#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;
}

Widać, że wtyczka wywołuje singleton i zmienia num atrybutu w singletonie.

Ostatni, jest główna funkcja użyj Singletona i wtyczki w następujący sposób:

#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);
}

I makefile jest następujące:

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

Więc, co to jest wyjście? Myślałem, że jest następujące:

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

Jednak rzeczywisty wynik jest następujący:

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

Dowodzi, że istnieją dwie instancje klasy singleton.

Dlaczego?
Author: Employed Russian, 2011-12-24

4 answers

Po pierwsze, powinieneś używać znacznika -fPIC podczas budowania bibliotek współdzielonych.

Nie używaj go "działa" na 32-bitowym Linuksie, ale zawiedzie na 64-bitowym z błędem podobnym do:]}
/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

Po drugie, twój program będzie działał zgodnie z oczekiwaniami po dodaniu -rdynamic do linku do głównego pliku wykonywalnego:

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

Aby zrozumieć, dlaczego -rdynamic jest wymagane, musisz wiedzieć o sposobie, w jaki dynamic linker rozwiązuje symbole, oraz o symbolu dynamic stolik.

Najpierw spójrzmy na dynamiczną tabelę symboli dla hello.so:

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

To mówi nam, że istnieją dwie słabe definicje funkcji i jedna zmienna globalna singleton::pInstance, które są widoczne dla linkera dynamicznego.

Spójrzmy teraz na statyczną i dynamiczną tabelę symboli dla oryginału example1 (połączonego Bez -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
$ 

To prawda: nawet jeśli singleton::pInstance jest obecny w pliku wykonywalnym jako zmienna globalna, symbol ten nie jest obecny w dynamiczna tabela symboli, a więc "niewidoczna" dla linkera dynamicznego.

Ponieważ linker dynamiczny "nie wie", że example1 zawiera już definicję singleton::pInstance, nie wiąże tej zmiennej wewnątrz hello.so z istniejącą definicją (która jest tym, czego naprawdę chcesz).

Gdy dodamy -rdynamic do linku:

$ 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()

Teraz definicja singleton::pInstance wewnątrz głównego pliku wykonywalnego jest widoczna dla linkera dynamicznego, a więc "użyje" tego, że definicja podczas ładowania hello.so:

LD_DEBUG=bindings ./example1-rdynamic |& grep pInstance
     31972: binding file ./hello.so [0] to ./example1-rdynamic [0]: normal symbol `_ZN9singleton9pInstanceE'
 48
Author: Employed Russian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-24 21:16:11

Należy zachować ostrożność podczas korzystania z bibliotek współdzielonych w trybie uruchomieniowym. Taka konstrukcja nie jest ściśle częścią standardu C++ i trzeba dokładnie rozważyć, jaka jest semantyka takiej procedury.

Po pierwsze, dzieje się tak, że biblioteka współdzielona widzi własną, oddzielną zmienną globalną singleton::pInstance. Dlaczego? Biblioteka, która jest ładowana w czasie wykonywania jest zasadniczo oddzielnym, niezależnym programem, który po prostu nie ma punktu wejścia. Ale wszystko inne jest tak naprawdę jak oddzielny program, a dynamiczny loader potraktuje go w ten sposób, np. zainicjalizuje zmienne globalne itp.

Dynamic loader jest narzędziem uruchomieniowym, które nie ma nic wspólnego ze statycznym loaderem. Statyczny program ładujący jest częścią standardowej implementacji C++ i rozwiązuje wszystkie symbole głównego programu przed uruchomieniem głównego programu. Z drugiej strony, dynamiczny loader uruchamia dopiero po , gdy główny program już się uruchomił. W szczególności, wszystkie symbole głównego programu muszą być już rozwiązane! Istnieje po prostu brak sposobu automatycznego zastępowania symboli z głównego programu dynamicznie. Natywne programy nie są "zarządzane" w żaden sposób, który pozwala na systematyczne ponowne łączenie. (Może coś można zhakować, ale nie w sposób systematyczny, przenośny.)

Więc prawdziwe pytanie brzmi, jak rozwiązać problem projektowy, który próbujesz. Rozwiązaniem jest przekazanie uchwytów do wszystkich zmiennych globalnych do wtyczka działa. Aby program główny zdefiniował oryginalną (i jedyną) kopię zmiennej globalnej i zainicjował bibliotekę wskaźnikiem do niej.

Na przykład Twoja biblioteka współdzielona może wyglądać tak. Po pierwsze, dodaj pointer-to-pointer do klasy singleton:

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

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

Teraz używaj *ppInstance zamiast pInstance wszędzie.

W wtyczce skonfiguruj singleton na wskaźnik z głównego programu:

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

I główną funkcję, wywołanie wtyczki intializacja:

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

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

Teraz wtyczka dzieli ten sam wskaźnik do instancji Singletona, co reszta programu.

 4
Author: Kerrek SB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-24 11:58:12

Myślę, że prosta odpowiedź jest tutaj: http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html

Jeśli masz zmienną statyczną, jest ona przechowywana w obiekcie (.na,,,a i / lub. so)

Jeśli ostateczny obiekt do wykonania zawiera dwie wersje obiektu, zachowanie jest nieoczekiwane, jak na przykład wywołanie destruktora obiektu Singleton.

Używając odpowiedniego projektu, np. deklarując statyczny element w głównym pliku i używając -rdynamic / fpic i użycie dyrektyw kompilatora "" zrobi to za Ciebie.

Przykładowe polecenie makefile:

$ g++ -rdynamic -o appexe $(OBJ) $(LINKFLAGS) -Wl,--whole-archive -L./Singleton/ -lsingleton -Wl,--no-whole-archive $(LIBS) 
Mam nadzieję, że to zadziała!
 2
Author: Paulo Lellis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-27 06:24:10

Dziękuję wszystkim za odpowiedzi!

Jako kontynuację Linuksa, możesz również użyć RTLD_GLOBAL z dlopen(...), per man dlopen (i przykładów, które ma). Wykonałem wariant przykładu OP w tym katalogu: drzewo github Przykładowe wyjście: output.txt

Szybkie i brudne:

  • jeśli nie chcesz ręcznie łączyć każdego symbolu z main, zachowaj udostępnione obiekty. (np. jeśli wykonałeś *.so obiekty do zaimportowania do Pythona)
  • Możesz wstępnie załaduj do globalnej tabeli symboli lub wykonaj NOLOAD + GLOBAL otwórz ponownie.

Kod:

#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

Tryby:

  • Tryb 0: nominalne leniwe ładowanie (nie działa)
  • Tryb 1: Dołącz plik do statycznej tabeli symboli.
  • Tryb 2: załaduj początkowo za pomocą RTLD_GLOBAL
  • Tryb 3: przeładowanie za pomocą RTLD_NOLOAD / RTLD_GLOBAL
 0
Author: Eric Cousineau,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-03 01:39:24