调用dlsym获取的MPI函数时如何处理段错误与进程终止问题
问题描述
我需要加载系统中的OpenMPI和IntelMPI的.so库,统计并获取它们的版本信息。但处理IntelMPI 2018时,调用MPI_Init会触发段错误(推测该版本是C++专用版本)。目前发现:
- 调用
MPI_Init触发段错误的版本,调用MPI_Get_version通常不会出错; - 调用
MPI_Init无段错误的版本,调用MPI_Get_version会导致进程终止。
想咨询:是否有办法捕获dlsym加载的MPI_Init抛出的段错误,或阻止MPI_Get_version终止进程?
加载库的代码片段:
this->opened_dll = dlopen(path.string().c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND ); if(this->opened_dll == nullptr) { throw std::exception(); } this->dll_MPI_Get_library_version = reinterpret_cast<MPI_Get_library_version>(dlsym(this->opened_dll, "MPI_Get_library_version")); this->dll_MPI_Get_version = reinterpret_cast<MPI_Get_version>(dlsym(this->opened_dll, "MPI_Get_version")); reinterpret_cast<MPI_Init>(dlsym(this->opened_dll, "MPI_Init"))(nullptr, nullptr);
触发段错误时MPI_Init的句柄不为空,调试显示所有相关值均非nullptr。由于是C代码,try/catch无法捕获异常。我考虑过fork进程,但这需要进程间通信来获取结果。
可行方案
1. 信号处理+fork捕获段错误
通过注册SIGSEGV信号处理函数,结合fork子进程来隔离崩溃风险:
- 在子进程中尝试调用
MPI_Init,捕获段错误信号; - 父进程根据子进程的退出状态判断是否触发崩溃,再选择后续调用的函数。
示例代码思路:
#include <signal.h> #include <unistd.h> #include <sys/wait.h> static volatile int segfault_triggered = 0; static struct sigaction old_sigaction; void segfault_handler(int sig) { segfault_triggered = 1; sigaction(SIGSEGV, &old_sigaction, nullptr); // 恢复原有信号处理 _exit(1); // 直接终止子进程,避免触发父进程的清理逻辑 } // 调用逻辑 void check_mpi_version(void* opened_dll, MPI_Get_version get_version, MPI_Get_library_version get_lib_version) { // 注册临时信号处理 struct sigaction sa; sa.sa_handler = segfault_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGSEGV, &sa, &old_sigaction); pid_t pid = fork(); if (pid == 0) { // 子进程尝试调用MPI_Init MPI_Init init_func = reinterpret_cast<MPI_Init>(dlsym(opened_dll, "MPI_Init")); if (init_func) init_func(nullptr, nullptr); _exit(0); } else { int status; waitpid(pid, &status, 0); segfault_triggered = WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV; } // 恢复原有信号处理 sigaction(SIGSEGV, &old_sigaction, nullptr); if (segfault_triggered) { // 触发段错误,调用MPI_Get_version if (get_version) { int ver[2]; get_version(ver, ver+1); // 处理版本信息 } } else { // MPI_Init成功,调用MPI_Get_library_version if (get_lib_version) { char ver_str[MPI_MAX_LIBRARY_VERSION_STRING]; int len; get_lib_version(ver_str, &len); // 处理版本信息 } } }
2. fork子进程+简单IPC传递结果
用fork彻底隔离风险,通过管道或临时文件传递版本信息:
- 父进程创建管道;
- 子进程加载库并执行目标函数,将版本信息写入管道/临时文件;
- 父进程等待子进程结束后读取结果,完全不受子进程崩溃影响。
示例简化逻辑:
#include <fcntl.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> void get_mpi_version_via_fork(const std::string& path) { int pipe_fd[2]; pipe(pipe_fd); pid_t pid = fork(); if (pid == 0) { close(pipe_fd[0]); // 关闭读端 void* dll = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND); if (!dll) _exit(1); // 先尝试调用MPI_Get_version MPI_Get_version get_ver = reinterpret_cast<MPI_Get_version>(dlsym(dll, "MPI_Get_version")); if (get_ver) { int ver[2]; get_ver(ver, ver+1); write(pipe_fd[1], ver, sizeof(ver)); _exit(0); } // 尝试调用MPI_Init后获取版本 MPI_Init init = reinterpret_cast<MPI_Init>(dlsym(dll, "MPI_Init")); if (init) init(nullptr, nullptr); MPI_Get_library_version get_lib_ver = reinterpret_cast<MPI_Get_library_version>(dlsym(dll, "MPI_Get_library_version")); if (get_lib_ver) { char ver_str[MPI_MAX_LIBRARY_VERSION_STRING]; int len; get_lib_ver(ver_str, &len); write(pipe_fd[1], ver_str, len); } _exit(0); } else { close(pipe_fd[1]); // 关闭写端 waitpid(pid, nullptr, 0); char buf[MPI_MAX_LIBRARY_VERSION_STRING]; ssize_t read_len = read(pipe_fd[0], buf, sizeof(buf)); if (read_len > 0) { // 处理读取到的版本信息 } close(pipe_fd[0]); } }
3. 优先尝试MPI_Get_version
通过fork子进程先执行MPI_Get_version,根据子进程是否正常退出选择后续逻辑:
- 若子进程正常返回,直接获取版本;
- 若子进程因信号终止,再尝试调用
MPI_Init获取版本。
内容的提问来源于stack exchange,提问作者Marc BARBIER
相关产品推荐
相关产品推荐

