Shallow analysis of cyberRT source code mainboard

Posted by akshay on Mon, 14 Feb 2022 13:32:21 +0100

The mainboard module is the program entry and startup module of cyberRT. We can start it in a way similar to roslaunch. The launch of cyberRT also encapsulates the mainboard module, or we can directly use mainboard - P < process_ Group > - D... & start a module.
There are five files in the minaboard module, two classes (module_argument, module_controller) and a main file mainboard cc.
First look at mainboard cc

#include "cyber/common/global_data.h"
#include "cyber/common/log.h"
#include "cyber/init.h"
#include "cyber/mainboard/module_argument.h"
#include "cyber/mainboard/module_controller.h"
#include "cyber/state.h"

using apollo::cyber::mainboard::ModuleArgument;
using apollo::cyber::mainboard::ModuleController;

int main(int argc, char** argv) {
  // parse the argument
  ModuleArgument module_args;
  module_args.ParseArgument(argc, argv);

  // initialize cyber
  apollo::cyber::Init(argv[0]);

  // start module
  ModuleController controller(module_args);
  if (!controller.Init()) {
    controller.Clear();
    AERROR << "module start error.";
    return -1;
  }

  apollo::cyber::WaitForShutdown();
  controller.Clear();
  AINFO << "exit mainboard.";

  return 0;
}

From the perspective of include, the global quantity and log functions are imported, and the init class and state class under cyber, as well as the ModuleArgument parameter class and ModuleController module class under its own mainboard module are imported.

Function () main

  • Instantiate ModuleArgument and call ParseArgument to resolve parameters
  • Apollo:: cell:: init (argv [0]), pass the file name, initialize the log thread, and join the scheduler
scheduler::Instance()->SetInnerThreadAttr("async_log", thread);

Register the exit handle and set the state (the state can be seen in the state class)

if (!g_atexit_registered) {
    if (std::atexit(ExitHandle) != 0) {
      AERROR << "Register exit handle failed";
      return false;
    }
    AINFO << "Register exit handle succ.";
    g_atexit_registered = true;
  }
  SetState(STATE_INITIALIZED);
  • Instantiate template controller using parameter class
  ModuleController controller(module_args);
  if (!controller.Init()) {
    controller.Clear();
    AERROR << "module start error.";
    return -1;
  }
  • Wait for the program to finish and clear the controller

For state, init, cell and binary, I will analyze them in detail in (III). Here, just look at the code to know the general functions.

ModuleArgument

class ModuleArgument {
public:
 ModuleArgument() = default;
 virtual ~ModuleArgument() = default;
 void DisplayUsage();
 void ParseArgument(int argc, char* const argv[]);
 void GetOptions(const int argc, char* const argv[]);
 const std::string& GetBinaryName() const;
 const std::string& GetProcessGroup() const;
 const std::string& GetSchedName() const;
 const std::list<std::string>& GetDAGConfList() const;

private:
 std::list<std::string> dag_conf_list_;
 std::string binary_name_;
 std::string process_group_;
 std::string sched_name_;
};

inline const std::string& ModuleArgument::GetBinaryName() const {
 return binary_name_;
}

inline const std::string& ModuleArgument::GetProcessGroup() const {
 return process_group_;
}

inline const std::string& ModuleArgument::GetSchedName() const {
 return sched_name_;
}

inline const std::list<std::string>& ModuleArgument::GetDAGConfList() const {
 return dag_conf_list_;
}

ModuleArgement implements DisplayUsage(), ParseArgument(), GetOptions(), and other functions.

|–DisplayUsage()

It is very simple to call Google stream to output the key information.

void ModuleArgument::DisplayUsage() {
  AINFO << "Usage: \n    " << binary_name_ << " [OPTION]...\n"
        << "Description: \n"
        << "    -h, --help : help information \n"
        << "    -d, --dag_conf=CONFIG_FILE : module dag config file\n"
        << "    -p, --process_group=process_group: the process "
           "namespace for running this module, default in manager process\n"
        << "    -s, --sched_name=sched_name: sched policy "
           "conf for hole process, sched_name should be conf in cyber.pb.conf\n"
        << "Example:\n"
        << "    " << binary_name_ << " -h\n"
        << "    " << binary_name_ << " -d dag_conf_file1 -d dag_conf_file2 "
        << "-p process_group -s sched_name\n";
}


/cyber/common/log.h
#define AINFO ALOG_MODULE(MODULE_NAME, INFO)

#ifndef ALOG_MODULE
#define ALOG_MODULE(module, log_severity) \
  ALOG_MODULE_STREAM(log_severity)(module)
#endif

#ifndef ALOG_MODULE_STREAM
#define ALOG_MODULE_STREAM(log_severity) ALOG_MODULE_STREAM_##log_severity
#endif

#define ALOG_MODULE_STREAM_INFO(module)                         \
  google::LogMessage(__FILE__, __LINE__, google::INFO).stream() \
      << LEFT_BRACKET << module << RIGHT_BRACKET

#define ALOG_MODULE_STREAM_WARN(module)                            \
  google::LogMessage(__FILE__, __LINE__, google::WARNING).stream() \
      << LEFT_BRACKET << module << RIGHT_BRACKET

#define ALOG_MODULE_STREAM_ERROR(module)                         \
  google::LogMessage(__FILE__, __LINE__, google::ERROR).stream() \
      << LEFT_BRACKET << module << RIGHT_BRACKET

#define ALOG_MODULE_STREAM_FATAL(module)                         \
  google::LogMessage(__FILE__, __LINE__, google::FATAL).stream() \
      << LEFT_BRACKET << module << RIGHT_BRACKET

#define ALOG_MODULE_STREAM(log_severity) ALOG_MODULE_STREAM_## log_ In severity, log_ Severity and ALOG_MODULE_STREAM_ Splice them together and call the macro definition stream() of different severity levels.

|–ParseArgument()

This function will call the GetOptions function to read argc and argv. If you don't say much, look at the code first

void ModuleArgument::ParseArgument(const int argc, char* const argv[]) {
  binary_name_ = std::string(basename(argv[0]));
  GetOptions(argc, argv);

  if (process_group_.empty()) {
    process_group_ = DEFAULT_process_group_;
  }

  if (sched_name_.empty()) {
    sched_name_ = DEFAULT_sched_name_;
  }

  GlobalData::Instance()->SetProcessGroup(process_group_);
  GlobalData::Instance()->SetSchedName(sched_name_);
  AINFO << "binary_name_ is " << binary_name_ << ", process_group_ is "
        << process_group_ << ", has " << dag_conf_list_.size() << " dag conf";
  for (std::string& dag : dag_conf_list_) {
    AINFO << "dag_conf: " << dag;
  }
}

ParseArgument is very similar to constructor. Firstly, binary is assigned_ name_, The basename function is in libgen H, get the last / following string

#include<iostream>
#include <libgen.h>
int main(int argc, char** argv) {
  std::cout << basename(argv[0]) << '\n';
}

input ./../code-test/test.o
 output test.o

Mainboard - P < process in cyber_ Group > - D... & start module binary_name_ Expected mainboard. Then call GetOptions() to read the parameters. Set process_group_ sched_name_, And set it in GlobalData. process_group_ Is the module name, sched_name_ It is a scheduling policy that outputs and displays information

|–GetOptions()

It mainly analyzes the following command line, show me code

void ModuleArgument::GetOptions(const int argc, char* const argv[]) {
  opterr = 0;  // extern int opterr
  int long_index = 0;
  const std::string short_opts = "hd:p:s:";
  static const struct option long_opts[] = {
      {"help", no_argument, nullptr, 'h'},
      {"dag_conf", required_argument, nullptr, 'd'},
      {"process_name", required_argument, nullptr, 'p'},
      {"sched_name", required_argument, nullptr, 's'},
      {NULL, no_argument, nullptr, 0}};

  // log command for info
  std::string cmd("");
  for (int i = 0; i < argc; ++i) {
    cmd += argv[i];
    cmd += " ";
  }
  AINFO << "command: " << cmd;

  if (1 == argc) {
    DisplayUsage();
    exit(0);
  }

  do {
    int opt =
        getopt_long(argc, argv, short_opts.c_str(), long_opts, &long_index);
    if (opt == -1) {
      break;
    }
    switch (opt) {
      case 'd':
        dag_conf_list_.emplace_back(std::string(optarg));
        for (int i = optind; i < argc; i++) {
          if (*argv[i] != '-') {
            dag_conf_list_.emplace_back(std::string(argv[i]));
          } else {
            break;
          }
        }
        break;
      case 'p':
        process_group_ = std::string(optarg);
        break;
      case 's':
        sched_name_ = std::string(optarg);
        break;
      case 'h':
        DisplayUsage();
        exit(0);
      default:
        break;
    }
  } while (true);

  if (optind < argc) {
    AINFO << "Found non-option ARGV-element \"" << argv[optind++] << "\"";
    DisplayUsage();
    exit(1);
  }

  if (dag_conf_list_.empty()) {
    AINFO << "-d parameter must be specified";
    DisplayUsage();
    exit(1);
  }
}

opterr is #include < getopt h> Introduced,

extern char *optarg;
extern int optind;
extern int opterr;
extern int optopt;

char *optarg: if there are parameters, the current option parameter string will be included
int optind: the current index value of argv. When the getopt function is used in the while loop, the remaining string is the operand with the subscript from optind to argc-1.
Int opter: when this variable is non-zero, the getopt() function is "invalid option" and "missing parameter option", and outputs its error message.
int optopt: when invalid option characters are found, getopt() function or return \ '? \' Character, or return character \ ': \', and optopt contains invalid option characters found.
String that defines optstring. Single character, followed by a colon description followed by an option parameter, followed by two colons description followed by an optional option parameter. See long for specific instructions_ Opts is a description of each parameter. The option class is also defined in getopt.

struct option
{
  const char *name;
  int has_arg;
  int *flag;
  int val;
};

#define no_argument		0
#define required_argument	1
#define optional_argument	2

Record the instruction. When argc==1, it indicates that there are no parameters. Call DisplayUsage() and exit. Otherwise, call getopt_long, keep getting parameters and setting - d dag_conf_list_,-p process_group_, -s sched_name_.

ModuleController

class ModuleController {
 public:
  explicit ModuleController(const ModuleArgument& args);
  virtual ~ModuleController() = default;

  bool Init();
  bool LoadAll();
  void Clear();

 private:
  bool LoadModule(const std::string& path);
  bool LoadModule(const DagConfig& dag_config);
  int GetComponentNum(const std::string& path);
  
  int total_component_nums = 0;
  bool has_timer_component = false;

  ModuleArgument args_;
  class_loader::ClassLoaderManager class_loader_manager_;
  std::vector<std::shared_ptr<ComponentBase>> component_list_;
};

There are five parameters in the whole class, including the number of components, whether there is a timer component, the ModuleArgument parameter, the module loading manager, and the component list. The factory mode is used to realize three functions: initialization, loading, establishment, and zero clearing. In order to realize the three functions, two ways of loading a single module are realized to obtain the number of components.
The constructor is very simple. Copy args directly_ Just. Init() will directly call the loadAll() function.

bool ModuleController::LoadAll() {
  const std::string work_root = common::WorkRoot();
  const std::string current_path = common::GetCurrentPath();
  const std::string dag_root_path = common::GetAbsolutePath(work_root, "dag");
  std::vector<std::string> paths;
  for (auto& dag_conf : args_.GetDAGConfList()) {
    std::string module_path = "";
    if (dag_conf == common::GetFileName(dag_conf)) {
      // case dag conf argument var is a filename
      module_path = common::GetAbsolutePath(dag_root_path, dag_conf);
    } else if (dag_conf[0] == '/') {
      // case dag conf argument var is an absolute path
      module_path = dag_conf;
    } else {
      // case dag conf argument var is a relative path
      module_path = common::GetAbsolutePath(current_path, dag_conf);
      if (!common::PathExists(module_path)) {
        module_path = common::GetAbsolutePath(work_root, dag_conf);
      }
    }
    total_component_nums += GetComponentNum(module_path);
    paths.emplace_back(std::move(module_path));
  }
  if (has_timer_component) {
    total_component_nums += scheduler::Instance()->TaskPoolSize();
  }
  common::GlobalData::Instance()->SetComponentNums(total_component_nums);
  for (auto module_path : paths) {
    AINFO << "Start initialize dag: " << module_path;
    if (!LoadModule(module_path)) {
      AERROR << "Failed to load module: " << module_path;
      return false;
    }
  }
  return true;
}

Topics: C++ Visual Studio Autonomous vehicles