I also tried "dynamic library" feature in Linux to load C++ binary file at runtime dynamically. This means the base program doesn't need to be recompiled with modified CPP files. Instead, it can load/unload a Position Independent Code, or PIC, at runtime. You can easily find more information from the manual page: "man dlopen".
I made five files for the base program:
pi@fileserver 20:57:15 cron_native$ ls cron_native.cpp dynamic_library.hpp plugins daemon.hpp Makefile stdafx.h pi@fileserver 20:57:17 cron_native$
The main logic of the program is in the file, "cron_native.cpp":
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 | // cron_native.cpp written by JaeHyukKwak
#include "stdafx.h" #include "dynamic_library.hpp" #include "daemon.hpp" static bool s_reload_dl_requested = false; void request_reload_dl_cb( int ) { s_reload_dl_requested = true; } int main( int argc, const char **argv ) { const char *plugin_folder = ( argc >= 2 ? argv[1] : "./plugins" ); daemon_init( argv, request_reload_dl_cb ); dynamic_library_t dl( plugin_folder ); while ( true ) { dl.call( "dl_init" ); while ( true ) { dl.call( "dl_main" ); sleep( 1 ); if ( s_reload_dl_requested ) break; } s_reload_dl_requested = false; dl.call( "dl_shutdown" ); dl.reload(); } // infinite loop return 0; } |
Note that it is a good practice to have explicit "init/shutdown" calls on any code that can be dynamically loaded and shared. I am not going to explain the details of the aspect but you can easily find topics like "DLL hell" or something.
The Makefile looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 | # Makefile written by Jae Hyuk Kwak CC=g++-4.7 CFLAGS=-Wall -g -std=c++11 cron_native: daemon.hpp dynamic_library.hpp cron_native.cpp stdafx.h.gch $(CC) $(CFLAGS) -ldl -o cron_native cron_native.cpp stdafx.h.gch : stdafx.h $(CC) $(CFLAGS) -c stdafx.h clean: rm stdafx.h.gch cron_native |
The precompile header file, "stdafx.h", looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // stdafx.h written by JaeHyukKwak #include <dlfcn.h> #include <sys/types.h> #include <sys/stat.h> #include <syslog.h> #include <stdarg.h> #include <signal.h> #include <dirent.h> #include <list> #include <memory> // shared_ptr #include <unistd.h> // XXX_FILENO #include <stdexcept> // exception using namespace std; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // daemon.hpp written by JaeHyukKwak typedef void (*signal_handler_fptr_type)( int ); void daemon_init ( const char **argv , signal_handler_fptr_type handler ) { umask( 0 ); openlog( argv[0], LOG_NOWAIT | LOG_PID, LOG_USER ); syslog( LOG_NOTICE, "Successfully started daemon\n" ); close( STDIN_FILENO ); close( STDOUT_FILENO ); close( STDERR_FILENO ); signal( 1, handler ); } |
The long program, "dynamic_library.hpp", looks like this:
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 | // dynamic_library.hpp written by JaeHyukKwak class dynamic_library_t { const char *folder_path_; typedef shared_ptr< void > dl_handle_sptr_type; list< dl_handle_sptr_type > dl_handle_list_; public: dynamic_library_t( const char *folder_path ) : folder_path_( folder_path ) { load(); } void load() { list< dl_handle_sptr_type > l; dlerror(); // clear the pre-existing error messages. for ( string file : get_so_file_list_( folder_path_ ) ) { if ( void *dl_handle = dlopen( file.c_str(), RTLD_LAZY ) ) { l.push_back( dl_handle_sptr_type( dl_handle, dlclose ) ); syslog( LOG_NOTICE, ( file + " added." ).c_str() ); } else syslog( LOG_ERR, ( file + ": " + dlerror() ).c_str() ); } dl_handle_list_ = l; } void unload() { dl_handle_list_.clear(); } void reload() { unload(); load(); } void call( const char *name ) const { typedef void (*fptr_type)( uint32_t ); for ( auto sptr : dl_handle_list_ ) { if ( auto fptr = get_fptr_< fptr_type >( sptr.get(), name ) ) { try { (*fptr)( time( NULL ) ); } catch ( exception e ) { syslog( LOG_ERR, e.what() ); } } else syslog( LOG_ERR , ( string( dlerror() ) + ": " + name ).c_str() ); } } private: static list< string > get_so_file_list_( const char *folder_path ) { list< string > l; DIR *dir = opendir( folder_path ); for ( dirent *de = readdir( dir ); de; de = readdir( dir ) ) { if ( de->d_type != DT_REG ) continue; string fname = de->d_name; if ( fname.find( ".so" ) != fname.size() - 3 ) continue; string path = string( folder_path ) + "/" + fname; l.push_back( path ); } closedir( dir ); syslog( LOG_NOTICE, ( string( "folder " ) + folder_path + " has " + to_string( l.size() ) + " plugins" ).c_str() ); return l; } template< typename FptrType > static FptrType get_fptr_( void *dl_handle, const char *name ) { union { void *void_ptr; FptrType dl_fptr; } dl_fptr_union = { dlsym( dl_handle, name ) }; return dl_fptr_union.dl_fptr; } }; |
For the function, dynamic_library_t::call(), I was thinking of using thread. But I couldn't find a good use of it. The latest RaspberryPi 2 has four cores so I would like to utilize threads as much as possible. But I don't think the added complexity can be justified in this case. Non-thread style is much more straight-forward and less error-prone.
I noticed that when one of PIC crashes or causes "Segment fault" error, the base program crashes as well, which makes the cron unstable as a daemon process. One of ways to prevent it is to "fork" a child process and isolate the problem in the process. But then it may affect the performance and the source code will suffer from complexity. Another way is to have another monitoring process or cron job that checks if the program is still running; if it doesn't it can simply restart a new one. I think when a crash happens, it should look obvious rather than make it automatically recover without noticing.
As a simple example of PIC, I have a "plugin.hello.cpp". In order to compile the plugin, I made three files:
pi@fileserver 21:53:49 plugins$ ls Makefile plugin.hello.cpp stdafx.h pi@fileserver 21:53:49 plugins$
On PIC side, the program must be compiled with a compiler option, "-fPIC". I am not sure on "-shared"; probably both are needed. And the functions that will be found by "dlsym" must have a keyword "extern "C"" at the beginning of the function.
The file, "plugin.hello.cpp", looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // plugin.hello.cpp written by JaeHyukKwak #include "stdafx.h" static int s_max_count; extern "C" void dl_init( uint32_t ) { s_max_count = 10; } extern "C" void dl_shutdown( uint32_t ) {} extern "C" void dl_main( uint32_t ) { if ( s_max_count <= 0 ) return; s_max_count--; syslog( LOG_NOTICE, "Hello" ); } |
The Makefile shows how the program is compiled.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # Makefile written by Jae Hyuk Kwak CC=g++-4.7 CFLAGS=-Wall -g -std=c++11 all: plugin.hello.so plugin.hello.so: plugin.hello.cpp stdafx.h.gch -$(CC) $(CFLAGS) -shared -fPIC -o plugin.hello.so plugin.hello.cpp stdafx.h.gch : stdafx.h $(CC) $(CFLAGS) -c stdafx.h clean: rm stdafx.h.gch plugin.*.so |
Not important but for completeness, I like to show the file, "stdafx.h", as well:
1 2 3 4 | // stdafx.h written by JaeHyukKwak #include <syslog.h> #include <cstdint> // uint32_t using namespace std; |
No comments:
Post a Comment