"The "apollo_app" build target defines the abstract class ApolloApp, which is implemented by all modules, as well as the macro APOLLO_MAIN, used to launch each module."

上面是apollo2.5文档中关于common模块的apollo_app部分的介绍,因此我们主要来分析ApolloApp和APOLLO_MAIN,两者都定义在modules/common/apollo_app.h文件中。

  • ApolloApp

ApolloApp是用于定义Apollo应用程序接口的基本模块类, Apollo应用程序持续运行,直到被SIGINT信号或ROS停止, Apollo中的许多基本组件如定位和控制等,都是Apollo应用程序的例子。ApolloApp是一个抽象基类,定义了各个模块App类的公共接口。

class ApolloApp {
 public:
  /**
   * @brief module name. It is used to uniquely identify the app.
   */
  virtual std::string Name() const = 0;

  /**
   * @brief this is the entry point of an Apollo App. It initializes the app,
   * starts the app, and stop the app when the ros has shutdown.
   */
  virtual int Spin();

  /**
   * The default destructor.
   */
  virtual ~ApolloApp() = default;

  /**
   * @brief set the number of threads to handle ros message callbacks.
   * The default thread number is 1
   */
  void SetCallbackThreadNumber(uint32_t callback_thread_num);

 protected:
  /**
   * @brief The module initialization function. This is the first function being
   * called when the App starts. Usually this function loads the configurations,
   * subscribe the data from sensors or other modules.
   * @return Status initialization status
   */
  virtual apollo::common::Status Init() = 0;

  /**
   * @brief The module start function. Apollo app usually triggered to execute
   * in two ways: 1. Triggered by upstream messages, or 2. Triggered by timer.
   * If an app is triggered by upstream messages, the Start() function usually
   * register a call back function that will be called when an upstream message
   * is received. If an app is triggered by timer, the Start() function usually
   * register a timer callback function.
   * @return Status start status
   */
  virtual apollo::common::Status Start() = 0;

  /**
   * @brief The module stop function. This function will be called when
   * after ros::shutdown() has finished. In the default APOLLO_MAIN macro,
   * ros::shutdown() is called when SIGINT is received.
   */
  virtual void Stop() = 0;

  /** The callback thread number
   */
  uint32_t callback_thread_num_ = 1;

 private:
  /**
   * @brief Export flag values to <FLAGS_log_dir>/<name>.flags.
   */
  void ExportFlags() const;
};

首先,来说明一下三个比较重要的纯虚函数接口:

Init():模块初始化函数,这是应用程序启动时调用的第一个函数,通常此函数会加载配置和订阅来自传感器或其他模块的数据。
Start():模块启动函数,Apollo应用程序通常可以以两种方式被触发执行。一是由上游消息触发,二是由定时器触发。
如果应用程序由上游消息触发,则Start()函数通常会注册一个回调函数,该函数将在收到上游消息时被调用。
如果应用程序由计时器触发,则Start()函数通常会注册计时器回调函数。
Stop():模块停止函数,当ros::shutdown()完成后,将调用此函数。在默认的APOLLO_MAIN宏中,当捕获到SIGINT信号时调用ros::shutdown()。

接着,来着重分析一下Spin()函数:

int ApolloApp::Spin() {
  auto status = Init();
  if (!status.ok()) {
    AERROR << Name() << " Init failed: " << status;
    return -1;
  }

  std::unique_ptr<ros::AsyncSpinner> spinner;
  if (callback_thread_num_ > 1) {
    spinner = std::unique_ptr<ros::AsyncSpinner>(
        new ros::AsyncSpinner(callback_thread_num_));
  }

  status = Start();
  if (!status.ok()) {
    AERROR << Name() << " Start failed: " << status;
    return -2;
  }
  ExportFlags();
  if (spinner) {
    spinner->start();
  } else {
    ros::spin();
  }
  ros::waitForShutdown();
  Stop();
  AINFO << Name() << " exited.";
  return 0;
}

Spin()函数是模块节点的入口,用于初始化、启动、当ros关闭时停止模块节点,一个模块节点也就是一个Apollo应用程序。Spin()函数内部调用Init()、Start()和Stop()函数完成模块节点的实现,此函数一般不会被各个模块App类重写,也就是使用基类ApolloApp的实现。

  • APOLLO_MAIN

APOLLO_MAIN宏的定义如下:

#define APOLLO_MAIN(APP)                                       \
  int main(int argc, char **argv) {                            \
    google::InitGoogleLogging(argv[0]);                        \
    google::ParseCommandLineFlags(&argc, &argv, true);         \
    signal(SIGINT, apollo::common::apollo_app_sigint_handler); \
    APP apollo_app_;                                           \
    ros::init(argc, argv, apollo_app_.Name());                 \
    apollo_app_.Spin();                                        \
    return 0;                                                  \
  }

首先,使用signal函数注册SIGINT信号与信号处理函数apollo_app_sigint_handler,apollo_app_sigint_handler函数在程序捕获到SIGINT信号后会调用ros::shutdown()函数关闭当前模块节点。接着,实例化一个当前模块APP类对象apollo_app_,然后apollo_app_的模块名称会传入ros::init()函数中初始化当前模块节点。最后,调用apollo_app_的基类部分的Spin()函数。apollo_app_sigint_handler函数代码如下:

void apollo_app_sigint_handler(int signal_num) {
  AINFO << "Received signal: " << signal_num;
  // only response for ctrl + c
  if (signal_num != SIGINT) {
    return;
  }
  bool static is_stopping = false;
  if (is_stopping) {
    return;
  }
  is_stopping = true;
  ros::shutdown();
}
  • 示例

这里以localization模块为例,分析一下整个运行流程。首先,在localization模块的main.cc文件(modules/localization/main.cc)中,使用宏APOLLO_MAIN,实现了一个localization节点,这里的节点与ros中的node概念一致,相当于一个进程:

APOLLO_MAIN(apollo::localization::Localization)

宏展开之后的代码如下:

int main(int argc, char **argv) {                            
    google::InitGoogleLogging(argv[0]);                        
    google::ParseCommandLineFlags(&argc, &argv, true);         
    signal(SIGINT, apollo::common::apollo_app_sigint_handler); 
    apollo::localization::Localization apollo_app_;                                           
    ros::init(argc, argv, apollo_app_.Name());                 
    apollo_app_.Spin();                                        
    return 0;                                                  
  }