假設一種環境,我們要對服務熱拔插一個動態庫(.so文件),所要考慮的是多線程環境的兼容,不會因為動態庫替換后造成棧損毀而崩潰。
這邊想到的方法就是封裝一個dlopen過程作為對象實例加載(見load_so.h),當發出更新動態庫時重新dlopen過程,替換原先的實例,注意這個替換過程必須是溫和的、無逢的,這邊我們使用智能指針實現。
具體更新的實現通過一個單例(見do_sth.h),調用Reload重新加載動態庫。
我們構造一個極簡單的動態庫測試:
make_so.h
1 2 3 4 5 6 7 8
|
#include "say.h"
extern
"C"
{
? ? void Enter(const std::string&str) ? ? { ? ? ? ? Say::instance().Sth(str); ? //在這里動態庫又過來調用了主程序的單件 ? ? } }
|
say.h 打印消息,這邊只是聲明一個單例,具體實現于主程序當中
1 2 3 4 5 6
|
#include "singleton.h"
class Say :public Singleton <Say> { ? ? public: ? ? ? ? void Sth(const std::string&str); };
|
通過編譯生test.so:
g++ make_so.cpp -fPIC -shared -pthread -rdynamic -lboost_thread -lboost_system -o test.so -L[boost庫目錄]
主程序 test.cpp ,用來測試這個動態庫test.so
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
|
#include <boost/thread.hpp>
#include "load_so.h"
#include "say.h"
class DoSth :public Singleton { ? ? public: ? ? ? ? DoSth():m_so(new LoadSo("./test.so")) ? ? ? ? { ? ? ? ? }
? ? ? ? DynamicSo_ptr Get() ? ? ? ? { ? ? ? ? ? ? boost::mutex::scoped_lock lock(m_mtx); ? ? ? ? ? ? return m_so; ? ? ? ? }
? ? ? ? void Reload() ? ? ? ? { ? ? ? ? ? ? boost::mutex::scoped_lock lock(m_mtx); ? ? ? ? ? ? m_so.reset(new LoadSo("./test.so")); ? ? ? ? } ? ? private: ? ? ? ? DynamicSo_ptr m_so; ? ? ? ? boost::mutex m_mtx; };
/// Say void Say::Sth(const std::string&str) { ? ? std::cout<< str << std::endl; }
////////////////////////// 測試代碼 //////////////////////////////////////
//更新動態庫 void test() { ? ? for(int i=0;i<100;++i) ? ? { ? ? ? ? sleep(1); ? ? ? ? DoSth::instance().Reload(); ? ? } }
//運行動態庫 void test2() { ? ? for(int i=0;irun("12"); ? ? } }
int main() { ? ? std::cout<<"run\n";
? ? boost::thread thread1(&test2); ? ? boost::thread thread2(&test);
? ? thread1.join(); ? ? thread2.join();
? ? return0; }
|
編譯主程序:
g++ -Wall test2.cpp -o test2 -pipe -pthread -ldl -Wl,–export-dynamic -lboost_system -lboost_thread -L[boost庫目錄]
這邊一定要加“-Wl,–export-dynamic”以便導出主程序的符號供動態庫回調。
編譯成功后生成 test 可執行文件,運行:
#./test
run
12
12
12
12
….
假設這時我們修改 make_so.h
1 2 3 4 5 6 7 8
|
#include "say.h"
extern
"C"
{
? ? void Enter(const std::string&str) ? ? { ? ? ? ? Say::instance().Sth(str +",ab"); ? //修改輸出 ? ? } }
|
g++ make_so.cpp -fPIC -shared -pthread -rdynamic -lboost_thread -lboost_system -o test.so -L[boost庫目錄]
重新編譯后,這時我們主程序會馬上響應,輸出:
12,ab
12,ab
12,ab
12,ab
….
說明熱替換是成功的。
在實際運用中,當要替換動態庫時可以在程序中使用
DoSth::instance().Reload();
具體方法很多,可以通過socket、中斷信號、監聽文件系統、定時更新等方式。
load_so.h 代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
|
#ifndef LOAD_SO_H
#define LOAD_SO_H
#include
#include
#include
using
namespace std;
class LoadSo { ? ? typedefvoid(*Func)(const std::string&suid); ? ? public: ? ? ? ? LoadSo(constchar* so_file) ? ? ? ? { ? ? ? ? ? ? load(so_file); ? ? ? ? }
? ? ? ? ~LoadSo() ? ? ? ? { ? ? ? ? ? ? Close(); ? ? ? ? }
? ? ? ? void Close() ? ? ? ? { ? ? ? ? ? ? dlclose(m_handle); ? ? ? ? }
? ? ? ? void run(const std::string&str ) ? ? ? ? { ? ? ? ? ? ? m_func(str); ? ? ? ? } ? ? private: ? ? ? ? bool load(constchar* so_file) ? ? ? ? { ? ? ? ? ? ? m_handle = dlopen(so_file, RTLD_LAZY); ? ? ? ? ? ? if(!m_handle){ ? ? ? ? ? ? ? ? std::string error("Cannot open library: "); ? ? ? ? ? ? ? ? throw std::runtime_error(error + dlerror()); ? ? ? ? ? ? ? ? returnfalse; ? ? ? ? ? ? }
? ? ? ? ? ? dlerror(); ? ? ? ? ? ? m_func =(Func) dlsym(m_handle, "Enter"); ? ? ? ? ? ? constchar*dlsym_error = dlerror(); ? ? ? ? ? ? if(dlsym_error){ ? ? ? ? ? ? ? ? dlclose(m_handle); ? ? ? ? ? ? ? ? std::string error("Cannot load symbol: "); ? ? ? ? ? ? ? ? throw std::runtime_error(error + dlsym_error); ? ? ? ? ? ? ? ? returnfalse; ? ? ? ? ? ? } ? ? ? ? ? ? returntrue; ? ? ? ? }
? ? ? ? Func m_func; ? ? ? ? void* m_handle; }; typedef boost::shared_ptr DynamicSo_ptr;
#endif
|
說在最后,我在一些測試中發現有時并不自動切換到新的動態庫的情況,另一個做法就是備用兩個動態庫,那么在Reload時在兩個so文件之間切換,這樣可以確保更新,額外給自己找的好處是可以保留舊版本的so。