Linux下C++熱加載實例
Friday, 3. June 2011 - 16:21 |1 comment ?
假設一種環(huán)境,我們要對服務熱拔插一個動態(tài)庫(.so文件),所要考慮的是多線程環(huán)境的兼容,不會因為動態(tài)庫替換后造成棧損毀而崩潰。
這邊想到的方法就是封裝一個dlopen過程作為對象實例加載(見load_so.h),當發(fā)出更新動態(tài)庫時重新dlopen過程,替換原先的實例,注意這個替換過程必須是溫和的、無逢的,這邊我們使用智能指針實現(xiàn)。
具體更新的實現(xiàn)通過一個單例(見do_sth.h),調用Reload重新加載動態(tài)庫。
我們構造一個極簡單的動態(tài)庫測試:
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); ? //在這里動態(tài)庫又過來調用了主程序的單件 ? ? } } |
say.h 打印消息,這邊只是聲明一個單例,具體實現(xiàn)于主程序當中
|
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 ,用來測試這個動態(tài)庫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; } ////////////////////////// 測試代碼 ////////////////////////////////////// //更新動態(tài)庫 void test() { ? ? for(int i=0;i<100;++i) ? ? { ? ? ? ? sleep(1); ? ? ? ? DoSth::instance().Reload(); ? ? } } //運行動態(tài)庫 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”以便導出主程序的符號供動態(tài)庫回調。
編譯成功后生成 test 可執(zhí)行文件,運行:
#./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
….
說明熱替換是成功的。
在實際運用中,當要替換動態(tài)庫時可以在程序中使用
DoSth::instance().Reload();
具體方法很多,可以通過socket、中斷信號、監(jiān)聽文件系統(tǒng)、定時更新等方式。
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 |
說在最后,我在一些測試中發(fā)現(xiàn)有時并不自動切換到新的動態(tài)庫的情況,另一個做法就是備用兩個動態(tài)庫,那么在Reload時在兩個so文件之間切換,這樣可以確保更新,額外給自己找的好處是可以保留舊版本的so。


