ARTICLE · 1118122
Ros2源码(9) rclcpp::init() 中的信号处理
Ros2源码(9) rclcpp::init() 中的信号处理一,背景 在 Ros2源码(7)rclcpp::init() 和 Ros2源码(8) 从 init 看 Ros2 SDK 架构设计 这两篇里,始终没有把初始阶段的信号处理相关代码加入进来,这里单独写一篇记录。 二,回顾一下 rclcpp::init() 代码块 1 这篇要看的是第 10 行的信号处理部分。 三,信号处理器 我们先把信号处理器 class SignalHandler 贴出来,部分成员先简单看看,然后在按照接口调用的顺序,看看每一步做了什么。 代码块 2 第 5 行,一个 static 函数,返回一个 static 对象,相当于一个单例; 第 17 行,根据选项安装信号处理器; 第 40 行,自定义一个类型别名,后面这个别名会经常出现。这里做了一个跨平台的判断; 第 103 ~ 104 行,保留旧的信号处理器,这个挺重要的,以前我就没有想到过这个; 第 113 行,一个独立的 std::thread 线程,是个信号处理的线程,待会儿就会看到它怎么工作的; 四,按调用顺序看代码 看一下 代码块 1 的第 10 行: 代码块 3 第 2 行,传入的参数 signal_handler_options 是个 ALL,知道就行了。 代码块 4 前面我们提到过,这里返回一个 static SignalHandler&,相当于一个单例了。 代码块 5 第 12 行,保存信号处理选项; 第 14 行,初始化信号量,SignalHandler::signal_handler_sem_,这个成员我们可以从代码块 2中找到; 第 16~23 行,设置信号处理参数和信号处理函数; 第 25~36 行,针对不同的信号,设置信号处理函数; 注意,第 29 和 35 行,保留旧的信号处理信息。注册自己的信号处理信息之后,还要保留旧的信号处理信息,以防自己的覆盖旧的。 如果 SIGINT 和 SIGTERM 一个都注册不上,Ctrl+C 可能杀不掉节点。 第 37 行,创建一个独立线程,执行信号处理函数。 接下来,我们看 代码块 5中涉及到的几个接口 代码块 6 第 4 行,获取全局的 SignalHandler 实例,这个前面提到过,它是个全局的 static; 第 5 行,获取旧的信号处理器,前面我们也提到过。我觉得这个做法还挺好,有没有什么隐患,还真说不好,感觉多少会出点问题,目前还没想好。 第 6 ~ 22 行,根据 sa_flags 的值判断,应该怎样调用旧的信号处理器。 sa_flags 可以搜一下 AI,就知道它的用法。这里就知道它还考虑了旧信号处理器,我觉得就够了。旧信号处理器可能是其他进程的,也可能是 Ros2 的,总之,它考虑到了。 第 23 行,signal_handler_common() ,我们看一下它的代码: 代码块 7 第 4 行,获取全局 SignalHandler 实例; 第 5 行,signal_received_ 设置 true,说明已经收到信号,要触发信号处理函数了; 第 6 行,notify_signal_handler(),唤醒信号处理器执行; 代码块 8 第 15 行,调用 sem_post() 唤醒信号量,触发信号处理函数; 接下来我们看一下代码块5 的第38行: 代码块 9 在看线程处理函数实现之前,我们先看另一个成员函数: 代码块 10 第 15 行,调用 sem_wait() 等待唤醒; 也就是说,wait_for_signal() 这个接口就在这等待信号被唤醒,如果被唤醒,继续想想执行。 好了,我们接下来看线程处理函数: 代码块 11 第 3 行,while(true) 死循环; 第 5 行,先判断一下是否收到信号,是的话执行进去,不是就向下。 我们先看一下第 50 行,如果没有收到信号,它就会走到这里。这个接口我们刚才提到了,如果信号量没有激活,它就会阻塞。 从前面的代码块 7中我们看到,如果收到信号,除了信号量激活之外,还有 signal_received_ 被设置 true。 接下来我们再看一下第5行,如果 signal_received_ 是 true: 第 9 行,这个我们在 Ros2源码(7)rclcpp::init()中提到过,有个全局的 context 数组 vector。这里就是遍历 context 数组; 第 11 行,标记判断,这个在 Ros2源码(7)rclcpp::init() 也提到过; 第 20 行,调用 context 的 shutdown() 接口;这个接口就不展开了; 总体来说,信号处理还比较容易理解。有两个亮点值得注意: 1)考虑到旧信号处理器了。调用当前信号处理接口之前,先调用旧的信号处理接口。 2)全局 context 数组。Ros2 在 components 的模式下,他就有可能会有多个 context 的情况,所以会有全局 vector。在信号处理的时候,还需要遍历。 这里有一点个人疑惑: 一个进程内,如果有多个 context,遍历 context vector,然后执行每个 context 的 shutdown(),会不会误杀。 针对这种情况,我没做过实验。找机会试试。 五,结语 分析信号处理过程中,有些我认为可以先不关注的代码就直接跳过了,比如跨平台的预处理部分。而且信号处理这部分本身也没什么太特殊的,所以这,篇看起来就是对Ros2源码(8) 从 init 看 Ros2 SDK 架构设计的一个补充。嗯,,其实也确实是,只是因为两部分放在一起,篇幅会比较大。 就当是复习 Linux 信号处理接口了 就这样
voidinit(int argc,char const * const * argv,const InitOptions & init_options,SignalHandlerOptions signal_handler_options){using rclcpp::contexts::get_global_default_context;get_global_default_context()->init(argc, argv, init_options);// Install the signal handlers.install_signal_handlers(signal_handler_options);}
class SignalHandler final{public:/// Return the global singleton of this class.static SignalHandler & get_global_signal_handler();/// Return a global singleton logger to avoid needing to create it everywhere.static rclcpp::Logger & get_logger();/// Install the signal handler for SIGINT/SIGTERM and start the dedicated signal handling thread./*** Also stores the current signal handler to be called on signal and to* restore when uninstalling this signal handler.** \param signal_handler_options option to indicate which signal handlers should be installed.*/boolinstall(SignalHandlerOptionssignal_handler_options = SignalHandlerOptions::All);/// Uninstall the signal handler for SIGINT/SIGTERM and join the dedicated singal handling/// thread./*** Also restores the previous signal handler.*/booluninstall();/// Return true if installed, false otherwise.boolis_installed();/// Get the current signal handler options./*** If no signal handler is installed, SignalHandlerOptions::None is returned.*/rclcpp::SignalHandlerOptionsget_current_signal_handler_options();private:/// Signal handler type, platform dependent.#if defined(RCLCPP_HAS_SIGACTION)using signal_handler_type = struct sigaction;#elseusing signal_handler_type = void (*)(int);#endifSignalHandler() = default;~SignalHandler();SignalHandler(const SignalHandler &) = delete;SignalHandler(SignalHandler &&) = delete;SignalHandler &operator=(const SignalHandler &) = delete;SignalHandler &&operator=(SignalHandler &&) = delete;/// Common signal handler code between sigaction and non-sigaction versions.voidsignal_handler_common();#if defined(RCLCPP_HAS_SIGACTION)/// Signal handler function.staticvoidsignal_handler(int signal_value, siginfo_t * siginfo, void * context);#else/// Signal handler function.staticvoidsignal_handler(int signal_value);#endif/// Target of the dedicated signal handling thread.voiddeferred_signal_handler();/// Setup anything that is necessary for wait_for_signal() or notify_signal_handler()./*** This must be called before wait_for_signal() or notify_signal_handler().* This is not thread-safe.*/voidsetup_wait_for_signal();/// Undo all setup done in setup_wait_for_signal()./*** Must not call wait_for_signal() or notify_signal_handler() after calling this.** This is not thread-safe.*/voidteardown_wait_for_signal()noexcept;/// Wait for a notification from notify_signal_handler() in a signal safe way./*** This static method may throw if posting the semaphore fails.** This is not thread-safe.*/voidwait_for_signal();/// Notify blocking wait_for_signal() calls in a signal safe way./*** This is used to notify the deferred_signal_handler() thread to start work* from the signal handler.** This is thread-safe.*/voidnotify_signal_handler()noexcept;static signal_handler_type set_signal_handler(int signal_value,const signal_handler_type & signal_handler);signal_handler_type get_old_signal_handler(int signum);rclcpp::SignalHandlerOptions signal_handlers_options_ = rclcpp::SignalHandlerOptions::None;signal_handler_type old_sigint_handler_;signal_handler_type old_sigterm_handler_;// logger instancerclcpp::Logger logger_ = rclcpp::get_logger("rclcpp");// Whether or not a signal has been received.std::atomic_bool signal_received_ = false;// A thread to which singal handling tasks are deferred.std::thread signal_handler_thread_;// A mutex used to synchronize the install() and uninstall() methods.std::mutex install_mutex_;// Whether or not the signal handler has been installed.std::atomic_bool installed_ = false;// Whether or not the semaphore for wait_for_signal is setup.std::atomic_bool wait_for_signal_is_setup_;// Storage for the wait_for_signal semaphore.#if defined(_WIN32)HANDLE signal_handler_sem_;#elif defined(__APPLE__)dispatch_semaphore_t signal_handler_sem_;#else// posixsem_t signal_handler_sem_;#endif};
boolinstall_signal_handlers(SignalHandlerOptions signal_handler_options){return SignalHandler::get_global_signal_handler().install(signal_handler_options);}
SignalHandler &SignalHandler::get_global_signal_handler(){static SignalHandler & signal_handler = *new SignalHandler();return signal_handler;}
boolSignalHandler::install(SignalHandlerOptions signal_handler_options){std::lock_guard<std::mutex> lock(install_mutex_);bool already_installed = installed_.exchange(true);if (already_installed) {return false;}if (signal_handler_options == SignalHandlerOptions::None) {return true;}signal_handlers_options_ = signal_handler_options;try {setup_wait_for_signal();signal_received_.store(false);SignalHandler::signal_handler_type handler_argument;#if defined(RCLCPP_HAS_SIGACTION)memset(&handler_argument, 0, sizeof(handler_argument));sigemptyset(&handler_argument.sa_mask);handler_argument.sa_sigaction = &this->signal_handler;handler_argument.sa_flags = SA_SIGINFO;#elsehandler_argument = &this->signal_handler;#endifif (signal_handler_options == SignalHandlerOptions::SigInt ||signal_handler_options == SignalHandlerOptions::All){old_sigint_handler_ = set_signal_handler(SIGINT, handler_argument);}if (signal_handler_options == SignalHandlerOptions::SigTerm ||signal_handler_options == SignalHandlerOptions::All){old_sigterm_handler_ = set_signal_handler(SIGTERM, handler_argument);}signal_handler_thread_ = std::thread(&SignalHandler::deferred_signal_handler, this);} catch (...) {installed_.store(false);throw;}RCLCPP_DEBUG(get_logger(), "signal handler installed");return true;}
voidSignalHandler::signal_handler(int signum, siginfo_t * siginfo, void * context){auto & instance = SignalHandler::get_global_signal_handler();auto old_signal_handler = instance.get_old_signal_handler(signum);if (old_signal_handler.sa_flags & SA_SIGINFO){if (old_signal_handler.sa_sigaction != NULL){old_signal_handler.sa_sigaction(signum, siginfo, context);}}else{if (old_signal_handler.sa_handler != NULL && /* Is set */old_signal_handler.sa_handler != SIG_DFL && /* Is not default*/old_signal_handler.sa_handler != SIG_IGN) /* Is not ignored */{old_signal_handler.sa_handler(signum);}}instance.signal_handler_common();}
voidSignalHandler::signal_handler_common(){auto & instance = SignalHandler::get_global_signal_handler();instance.signal_received_.store(true);instance.notify_signal_handler();}
voidSignalHandler::notify_signal_handler() noexcept{if (!wait_for_signal_is_setup_.load()) {return;}#if defined(_WIN32)if (!ReleaseSemaphore(signal_handler_sem_, 1, NULL)) {RCLCPP_ERROR(get_logger(), "ReleaseSemaphore() failed in notify_signal_handler(): %s", GetLastError());}#elif defined(__APPLE__)dispatch_semaphore_signal(signal_handler_sem_);#else// posixif (-1 == sem_post(&signal_handler_sem_)) {RCLCPP_ERROR(get_logger(), "sem_post failed in notify_signal_handler()");}#endif}
signal_handler_thread_ = std::thread(&SignalHandler::deferred_signal_handler, this);
voidSignalHandler::wait_for_signal(){if (!wait_for_signal_is_setup_.load()) {RCLCPP_ERROR(get_logger(), "called wait_for_signal() before setup_wait_for_signal()");return;}#if defined(_WIN32)//// 省略部分代码#elif defined(__APPLE__)dispatch_semaphore_wait(signal_handler_sem_, DISPATCH_TIME_FOREVER);#else// posixint s;do {s = sem_wait(&signal_handler_sem_);} while (-1 == s && EINTR == errno);#endif}
void SignalHandler::deferred_signal_handler(){while (true){if (signal_received_.exchange(false)){RCLCPP_INFO(SignalHandler::get_logger(), "signal_handler(SIGINT/SIGTERM)");RCLCPP_DEBUG(get_logger(), "deferred_signal_handler(): shutting down");for (auto context_ptr : rclcpp::get_contexts()){if (context_ptr->get_init_options().shutdown_on_signal){RCLCPP_DEBUG(get_logger(),"deferred_signal_handler(): ""shutting down rclcpp::Context @ %p, because it had shutdown_on_signal == true",static_cast<void *>(context_ptr.get()));try{context_ptr->shutdown("signal handler");}catch (const std::exception & exc){// an uncaught exception on this thread would call std::terminate(),// taking down the whole process, so log the failure insteadRCLCPP_ERROR(get_logger(),"deferred_signal_handler(): failed to shutdown rclcpp::Context @ %p: %s",static_cast<void *>(context_ptr.get()), exc.what());}catch (...){RCLCPP_ERROR(get_logger(),"deferred_signal_handler(): failed to shutdown rclcpp::Context @ %p",static_cast<void *>(context_ptr.get()));}}}}if (!is_installed()){RCLCPP_DEBUG(get_logger(), "deferred_signal_handler(): signal handling uninstalled");break;}RCLCPP_DEBUG(get_logger(), "deferred_signal_handler(): waiting for SIGINT/SIGTERM or uninstall");wait_for_signal();RCLCPP_DEBUG(get_logger(), "deferred_signal_handler(): woken up due to SIGINT/SIGTERM or uninstall");}}