夜雨聆风学习资料网

ARTICLE · 1118122

Ros2源码(9) rclcpp::init() 中的信号处理

Ros2源码(9) rclcpp::init() 中的信号处理
一,背景
    在 Ros2源码(7)rclcpp::init() 和 Ros2源码(8) 从 init 看 Ros2 SDK 架构设计 这两篇里,始终没有把初始阶段的信号处理相关代码加入进来,这里单独写一篇记录。
二,回顾一下 rclcpp::init()
代码块 1
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);}
这篇要看的是第 10 行的信号处理部分。
三,信号处理器
    我们先把信号处理器 class SignalHandler 贴出来,部分成员先简单看看,然后在按照接口调用的顺序,看看每一步做了什么。
代码块 2
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(SignalHandlerOptions           signal_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::SignalHandlerOptions     get_current_signal_handler_options();private:  /// Signal handler type, platform dependent.#if defined(RCLCPP_HAS_SIGACTION)  using signal_handler_type = struct sigaction;#else  using signal_handler_type = void (*)(int);#endif  SignalHandler() = 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 instance  rclcpp::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// posix  sem_t signal_handler_sem_;#endif};
第 5 行,一个 static 函数,返回一个 static 对象,相当于一个单例;
第 17 行,根据选项安装信号处理器;
第 40 行,自定义一个类型别名,后面这个别名会经常出现。这里做了一个跨平台的判断;
第 103 ~ 104 行,保留旧的信号处理器,这个挺重要的,以前我就没有想到过这个;
第 113 行,一个独立的 std::thread 线程,是个信号处理的线程,待会儿就会看到它怎么工作的;
四,按调用顺序看代码
    看一下 代码块 1 的第 10 行:
代码块 3
boolinstall_signal_handlers(SignalHandlerOptions signal_handler_options){  return SignalHandler::get_global_signal_handler().install(signal_handler_options);}
第 2 行,传入的参数 signal_handler_options 是个 ALL,知道就行了。
代码块 4
SignalHandler &SignalHandler::get_global_signal_handler(){  static SignalHandler & signal_handler = *new SignalHandler();  return signal_handler;}
前面我们提到过,这里返回一个 static SignalHandler&,相当于一个单例了。 
代码块 5
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;#else    handler_argument = &this->signal_handler;#endif    if (      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;}
第 12 行,保存信号处理选项;
第 14 行,初始化信号量,SignalHandler::signal_handler_sem_,这个成员我们可以从代码块 2中找到;
第 16~23 行,设置信号处理参数和信号处理函数;
第 25~36 行,针对不同的信号,设置信号处理函数;
    注意,第 29 和 35 行,保留旧的信号处理信息。注册自己的信号处理信息之后,还要保留旧的信号处理信息,以防自己的覆盖旧的。
    如果 SIGINT 和 SIGTERM 一个都注册不上,Ctrl+C 可能杀不掉节点。
第 37 行,创建一个独立线程,执行信号处理函数。
    接下来,我们看 代码块 5中涉及到的几个接口
代码块 6
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();}
第 4 行,获取全局的 SignalHandler 实例,这个前面提到过,它是个全局的 static;
第 5 行,获取旧的信号处理器,前面我们也提到过。我觉得这个做法还挺好,有没有什么隐患,还真说不好,感觉多少会出点问题,目前还没想好。
第 6 ~ 22 行,根据 sa_flags 的值判断,应该怎样调用旧的信号处理器。
sa_flags 可以搜一下 AI,就知道它的用法。这里就知道它还考虑了旧信号处理器,我觉得就够了。旧信号处理器可能是其他进程的,也可能是 Ros2 的,总之,它考虑到了。
第 23 行,signal_handler_common() ,我们看一下它的代码:
代码块 7 
voidSignalHandler::signal_handler_common(){  auto & instance = SignalHandler::get_global_signal_handler();  instance.signal_received_.store(true);  instance.notify_signal_handler();}
第 4 行,获取全局 SignalHandler 实例;
第 5 行,signal_received_ 设置 true,说明已经收到信号,要触发信号处理函数了;
第 6 行,notify_signal_handler(),唤醒信号处理器执行;
代码块 8
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// posix  if (-1 == sem_post(&signal_handler_sem_)) {    RCLCPP_ERROR(get_logger(), "sem_post failed in notify_signal_handler()");  }#endif}
第 15 行,调用 sem_post() 唤醒信号量,触发信号处理函数;
接下来我们看一下代码块5 的第38行:
代码块 9
signal_handler_thread_ = std::thread(          &SignalHandler::deferred_signal_handler, this);
在看线程处理函数实现之前,我们先看另一个成员函数:
代码块 10
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// posix  int s;  do {    s = sem_wait(&signal_handler_sem_);  } while (-1 == s && EINTR == errno);#endif}
第 15 行,调用 sem_wait() 等待唤醒;
    也就是说,wait_for_signal() 这个接口就在这等待信号被唤醒,如果被唤醒,继续想想执行。
好了,我们接下来看线程处理函数:
代码块 11
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 instead            RCLCPP_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");  }}
第 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 信号处理接口了
就这样

相关学习资料