2011-12-24 24 views
28

Câu hỏi của tôi, như tiêu đề được đề cập, là hiển nhiên và tôi mô tả chi tiết về kịch bản. Có một lớp có tên singleton thực hiện bởi Singleton pattern như sau, trong tập tin singleton.h:Nhiều trường hợp singleton trên các thư viện được chia sẻ trên 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_ */ 

sau đó, có một plugin gọi hello.cpp như sau:

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

bạn có thể nhìn thấy rằng plugin gọi singleton và thay đổi thuộc tính num trong singleton.

cuối cùng, có một chức năng chính sử dụng singleton và các plugin như sau:

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

và makefile là sau đây:

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 

như vậy, đầu ra là bao nhiêu? Tôi nghĩ đó là sau đây:

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

Tuy nhiên, sản lượng thực tế là sau đây:

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

Nó chứng tỏ rằng có hai trường hợp của lớp singleton.

Tại sao?

+0

Bạn có muốn đĩa đơn này là singleton cho một quá trình bạn đang thực hiện không? Hoặc một singleton "toàn hệ thống" vi phạm tất cả bộ nhớ được bảo vệ đó cung cấp cho chúng ta? – sarnold

+0

Câu hỏi, trừ khi được tuyên bố rõ ràng, thường là bất cứ điều gì nhưng hiển nhiên. Bạn có muốn chia sẻ các thư viện để chia sẻ singleton hay không?Bạn có lý thuyết về một trong hai hành vi hoặc thực sự trải nghiệm nó? Không có cách nào để biết trừ khi bạn nói với chúng tôi. – 9000

+0

@ sarnold: có một mẫu nổi tiếng của các hệ thống toàn bộ hệ thống không bị giới hạn bởi không gian địa chỉ: nó được gọi là máy chủ. Nhưng cho đến khi áp phích ban đầu báo cho _purpose_ mã của anh ta, thật khó để nói liệu mẫu này có phù hợp hay không. – 9000

Trả lời

45

Trước tiên, bạn thường sử dụng cờ -fPIC khi tạo thư viện được chia sẻ.

Không sử dụng nó "tác phẩm" trên Linux 32-bit, nhưng sẽ thất bại trên một 64-bit với một lỗi tương tự như:

/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 

Thứ hai, chương trình của bạn sẽ làm việc như bạn mong đợi sau khi bạn thêm -rdynamic vào dòng liên kết cho thực thi chính:

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

để hiểu tại sao -rdynamic là cần thiết, bạn cần phải biết về cách giải quyết mối liên kết động biểu tượng, và về động symbo l bảng.

Đầu tiên, chúng ta hãy nhìn vào bảng biểu tượng động cho hello.so:

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

này cho chúng ta biết rằng có hai định nghĩa hàm yếu, và một thế giới biến singleton::pInstance rằng có thể nhìn thấy mối liên kết động.

Bây giờ chúng ta hãy nhìn vào bảng biểu tượng tĩnh và năng động cho bản gốc example1 (liên kết mà không -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 
$ 

Đúng vậy: mặc dù singleton::pInstance hiện diện trong thực thi như là một biến toàn cầu, biểu tượng đó không có mặt trong bảng biểu tượng động và do đó "ẩn" với trình liên kết động.

Vì liên kết động "không biết" example1 đã chứa định nghĩa singleton::pInstance, nó không ràng buộc biến đó bên trong hello.so với định nghĩa hiện tại (đó là những gì bạn thực sự muốn).

Khi chúng ta thêm -rdynamic vào dòng liên kết:

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

Bây giờ định nghĩa của singleton::pInstance bên trong thực thi chính là nhìn thấy để mối liên kết năng động, và vì vậy nó sẽ "tái sử dụng" định nghĩa rằng khi tải hello.so :

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

-rdynmaic giải quyết vấn đề này, cảm ơn bạn. Tôi đánh giá cao sự giúp đỡ của bạn :) – bourneli

+1

@BourneLi Nếu câu trả lời phù hợp với bạn, bạn phải chấp nhận nó. –

+0

Bạn có thể giải thích cách làm ngược lại không? Tôi có một thư viện cơ sở chia sẻ mà hai plugin được liên kết đến, thư viện được chia sẻ xuất khẩu một singleton; nhưng tôi muốn mỗi plugin duy trì bản sao riêng của bản singleton. Tôi không có -rdynamic được liệt kê như là một lá cờ biên dịch. –

4

Bạn phải cẩn thận khi sử dụng thư viện được chia sẻ trong thời gian chạy. Việc xây dựng như vậy không phải là một phần của tiêu chuẩn C++, và bạn phải xem xét cẩn thận ngữ nghĩa của một quy trình như thế nào.

Trước hết, điều đang xảy ra là thư viện được chia sẻ thấy biến toàn cục riêng biệt của riêng mình singleton::pInstance. Tại sao vậy? Một thư viện được tải vào thời gian chạy về cơ bản là một chương trình độc lập riêng biệt, chỉ xảy ra không có điểm vào. Nhưng mọi thứ khác thực sự giống như một chương trình riêng biệt và trình tải động sẽ xử lý nó như vậy, ví dụ: khởi tạo biến toàn cục, v.v.

Trình tải động là cơ sở thời gian chạy không có gì liên quan đến trình tải tĩnh. Trình tải tĩnh là một phần của việc thực hiện tiêu chuẩn C++ và giải quyết tất cả các ký hiệu của chương trình chính trước khi bắt đầu chương trình chính. Trình nạp động, mặt khác, chỉ chạy sau chương trình chính đã bắt đầu. Cụ thể, tất cả các ký hiệu của chương trình chính đã được giải quyết! Chỉ đơn giản là không cách tự động thay thế các biểu tượng từ chương trình chính theo cách động. Các chương trình gốc không được "quản lý" theo bất kỳ cách nào cho phép tái phát có hệ thống. (Có thể một cái gì đó có thể bị tấn công, nhưng không theo một cách có hệ thống, di động.)

Câu hỏi thực tế là cách vấn đề thiết kế mà bạn đang cố gắng. Các giải pháp ở đây là để vượt qua xử lý cho tất cả các biến toàn cầu cho các chức năng plugin. Làm cho chương trình chính của bạn xác định bản sao gốc (và duy nhất) của biến toàn cầu, và khởi tạo thư viện của bạn với một con trỏ đến đó.

Ví dụ: thư viện được chia sẻ của bạn có thể trông như thế này.Đầu tiên, thêm một con trỏ-to-con trỏ tới lớp singleton:

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

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

Bây giờ sử dụng *ppInstance thay vì pInstance ở khắp mọi nơi.

Trong plugin, cấu hình singleton để con trỏ từ chương trình chính:

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

Và chức năng chính, hãy gọi intialization 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(); 

Bây giờ cổ phần Plugin con trỏ cùng cho ví dụ singleton như phần còn lại của chương trình.

+2

Nếu bạn buộc "singleton" được khởi tạo với địa chỉ của một số toàn cầu, thì nó không còn là * singleton * nữa. Câu trả lời của bạn không chính xác trong nhiều chi tiết và giải pháp bạn đề xuất là (IMHO) không có thật. –

+0

Tôi đồng ý. Toàn bộ mục đích sử dụng mẫu đơn bị mất với giải pháp này. Tùy chọn – volpato

2

tôi nghĩ câu trả lời đơn giản là ở đây: http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html

Khi bạn có biến tĩnh, nó được lưu trữ trong đối tượng (.o, .a và/hoặc .so)

Nếu đối tượng cuối cùng được thực thi chứa hai phiên bản của đối tượng, hành vi không mong muốn , ví dụ như, gọi Destructor của một đối tượng Singleton.

Sử dụng thiết kế phù hợp, chẳng hạn như khai báo thành viên tĩnh trong tệp chính và sử dụng thuật ngữ -rdynamic/fpic và sử dụng chỉ thị trình biên dịch sẽ làm phần lừa cho bạn.

Ví dụ tuyên bố makefile:

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

Hope làm việc này!

0

Cảm ơn tất cả các bạn đã trả lời!

Theo dõi cho Linux, bạn cũng có thể sử dụng RTLD_GLOBAL với dlopen(...), mỗi man dlopen (và các ví dụ có). Tôi đã thực hiện một biến thể của ví dụ của OP trong thư mục này: github tree Ví dụ đầu ra: output.txt

Nhanh chóng và bẩn:

  • Nếu bạn không muốn phải tự liên kết trong mỗi biểu tượng để bạn main, giữ các đối tượng được chia sẻ xung quanh. (ví dụ: nếu bạn đã thực hiện *.so đối tượng để nhập vào Python)
  • Ban đầu, bạn có thể tải vào bảng biểu tượng chung hoặc làm NOLOAD + GLOBAL mở lại.

Code:

#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 

Chế độ:

  • Chế độ 0: danh nghĩa lười tải (sẽ không hoạt động)
  • Chế độ 1: Bao gồm tập tin để thêm vào bảng biểu tượng tĩnh.
  • Chế độ 2: Tải ban đầu bằng RTLD_GLOBAL
  • Chế độ 3: Tải lại bằng RTLD_NOLOAD | RTLD_GLOBAL
Các vấn đề liên quan