【Apollo】Apollo 8.0 Mainboard 架构与工作原理详解

概述

什么是Mainboard?

Mainboard 是Apollo Cyber RT框架的核心启动器(Launcher),负责加载和启动所有自动驾驶模块。它是一个通用的模块加载工具,位于 /apollo/cyber/mainboard/

核心作用

  • 统一启动入口: 所有Apollo模块(Planning、Control、Perception等)都通过mainboard启动
  • 动态加载: 运行时动态加载.so共享库文件
  • 配置驱动: 通过DAG配置文件定义要加载的模块和组件
  • 生命周期管理: 管理组件的初始化、运行和关闭流程

基本使用方式

mainboard -d /apollo/modules/planning/dag/planning.dag

参数说明:

  • -d: 指定DAG配置文件路径(可以指定多个)
  • -p: 指定进程组名称(可选)
  • -s: 指定调度策略名称(可选)

核心组件架构

Mainboard由以下几个核心组件构成:

1. 主程序 (mainboard.cc)

源码位置: /apollo/cyber/mainboard/mainboard.cc

int main(int argc, char** argv) {
  // 1. 解析命令行参数
  ModuleArgument module_args;
  module_args.ParseArgument(argc, argv);

  // 2. 初始化Cyber RT框架
  apollo::cyber::Init(argv[0]);

  // 3. 创建模块控制器并初始化
  ModuleController controller(module_args);
  if (!controller.Init()) {
    controller.Clear();
    AERROR << "module start error.";
    return -1;
  }

  // 4. 等待关闭信号
  apollo::cyber::WaitForShutdown();

  // 5. 清理资源
  controller.Clear();
  AINFO << "exit mainboard.";

  return 0;
}

关键流程:

  1. 解析命令行参数(DAG文件路径等)
  2. 初始化Cyber RT框架
  3. 加载并初始化所有模块
  4. 阻塞等待直到收到关闭信号
  5. 清理所有资源并退出

2. 模块参数解析器 (ModuleArgument)

源码位置: /apollo/cyber/mainboard/module_argument.h|cc

职责:

  • 解析命令行参数
  • 验证参数合法性
  • 存储DAG文件路径列表

核心数据成员:

class ModuleArgument {
 private:
  std::list<std::string> dag_conf_list_;  // DAG文件列表
  std::string binary_name_;                // 二进制程序名
  std::string process_group_;              // 进程组名称
  std::string sched_name_;                 // 调度器名称
};

支持的命令行选项:

-h, --help              : 显示帮助信息
-d, --dag_conf=FILE     : 指定DAG配置文件(可多次使用)
-p, --process_group=NAME: 指定进程组(默认: mainboard_default)
-s, --sched_name=NAME   : 指定调度策略(默认: CYBER_DEFAULT)

示例:

mainboard -d planning.dag -d control.dag -p planning_group -s CYBER_DEFAULT

3. 模块控制器 (ModuleController)

源码位置: /apollo/cyber/mainboard/module_controller.h|cc

职责:

  • 加载DAG配置文件
  • 动态加载.so库文件
  • 创建和初始化Component实例
  • 管理Component生命周期

核心数据成员:

class ModuleController {
 private:
  ModuleArgument args_;                                      // 命令行参数
  class_loader::ClassLoaderManager class_loader_manager_;   // 类加载器管理器
  std::vector<std::shared_ptr<ComponentBase>> component_list_; // 组件列表
  int total_component_nums = 0;                             // 组件总数
  bool has_timer_component = false;                         // 是否有定时组件
};

核心方法:

bool Init();                                    // 初始化(调用LoadAll)
bool LoadAll();                                 // 加载所有DAG文件
bool LoadModule(const std::string& path);       // 加载单个DAG文件
bool LoadModule(const DagConfig& dag_config);   // 加载DAG配置
void Clear();                                   // 清理所有资源

4. 类加载器 (ClassLoader & ClassLoaderManager)

源码位置:

  • /apollo/cyber/class_loader/class_loader.h|cc
  • /apollo/cyber/class_loader/class_loader_manager.h|cc

ClassLoader职责:

  • 封装dlopen/dlsym等底层API
  • 加载.so动态库
  • 创建类实例
  • 管理库的引用计数

ClassLoaderManager职责:

  • 管理多个ClassLoader实例
  • 提供统一的类创建接口
  • 避免重复加载同一个库

核心接口:

class ClassLoaderManager {
 public:
  // 加载动态库
  bool LoadLibrary(const std::string& library_path);

  // 创建类实例(根据类名)
  template <typename Base>
  std::shared_ptr<Base> CreateClassObj(const std::string& class_name);

  // 卸载所有库
  void UnloadAllLibrary();
};

启动流程详解

完整启动流程图

用户执行命令
    ↓
mainboard启动
    ↓
1. ModuleArgument::ParseArgument()  ← 解析命令行参数
    ↓
2. apollo::cyber::Init()             ← 初始化Cyber RT
    ├─ InitLogger()                  ← 初始化日志系统
    ├─ SysMo::Instance()             ← 初始化系统监控
    ├─ 注册SIGINT信号处理器
    └─ SetState(STATE_INITIALIZED)
    ↓
3. ModuleController::Init()          ← 初始化模块控制器
    ↓
4. ModuleController::LoadAll()       ← 加载所有模块
    ├─ 解析DAG文件路径(支持相对/绝对路径)
    ├─ 统计Component数量
    └─ 遍历每个DAG文件
        ↓
5. ModuleController::LoadModule(path)
    ├─ 解析DAG配置(Protobuf)
    └─ 对每个module_config:
        ├─ ClassLoaderManager::LoadLibrary()  ← 加载.so文件
        └─ 对每个component:
            ├─ CreateClassObj<ComponentBase>()  ← 创建实例
            └─ component->Initialize(config)     ← 初始化组件
                ├─ 创建Node
                ├─ 加载配置文件
                ├─ 调用Component::Init()        ← 用户实现
                ├─ 创建Reader订阅消息
                └─ 注册到Scheduler调度器
    ↓
6. apollo::cyber::WaitForShutdown()  ← 阻塞等待关闭信号
    ↓
7. ModuleController::Clear()         ← 清理资源
    ├─ 调用所有component->Shutdown()
    └─ ClassLoaderManager::UnloadAllLibrary()

详细步骤说明

步骤1: 参数解析

void ModuleArgument::ParseArgument(const int argc, char* const argv[]) {
  binary_name_ = std::string(basename(argv[0]));
  GetOptions(argc, argv);  // 解析 -d, -p, -s 等参数

  // 设置默认值
  if (process_group_.empty()) {
    process_group_ = DEFAULT_process_group_;  // "mainboard_default"
  }
  if (sched_name_.empty()) {
    sched_name_ = DEFAULT_sched_name_;        // "CYBER_DEFAULT"
  }

  // 保存到全局数据
  GlobalData::Instance()->SetProcessGroup(process_group_);
  GlobalData::Instance()->SetSchedName(sched_name_);
}

步骤2: Cyber RT初始化

bool Init(const char* binary_name) {
  // 1. 初始化日志系统
  InitLogger(binary_name);

  // 2. 初始化系统监控
  SysMo::Instance();

  // 3. 注册信号处理器(响应Ctrl+C)
  std::signal(SIGINT, OnShutdown);

  // 4. 注册退出处理器
  std::atexit(ExitHandle);

  // 5. 设置状态为已初始化
  SetState(STATE_INITIALIZED);

  return true;
}

关键初始化:

  • 日志系统: glog + 异步日志
  • 调度器: Scheduler单例
  • 拓扑管理: TopologyManager(服务发现)
  • 传输层: Transport(通信基础设施)

步骤3-5: 加载模块

bool ModuleController::LoadAll() {
  const std::string work_root = common::WorkRoot();  // /apollo
  std::vector<std::string> paths;

  // 解析每个DAG文件的完整路径
  for (auto& dag_conf : args_.GetDAGConfList()) {
    std::string module_path = "";
    if (dag_conf == common::GetFileName(dag_conf)) {
      // 情况1: 纯文件名 -> 到 /apollo/dag 下查找
      module_path = common::GetAbsolutePath(dag_root_path, dag_conf);
    } else if (dag_conf[0] == '/') {
      // 情况2: 绝对路径
      module_path = dag_conf;
    } else {
      // 情况3: 相对路径 -> 相对于当前目录或work_root
      module_path = common::GetAbsolutePath(current_path, dag_conf);
      if (!common::PathExists(module_path)) {
        module_path = common::GetAbsolutePath(work_root, dag_conf);
      }
    }
    paths.emplace_back(std::move(module_path));
  }

  // 加载每个模块
  for (auto module_path : paths) {
    if (!LoadModule(module_path)) {
      AERROR << "Failed to load module: " << module_path;
      return false;
    }
  }
  return true;
}

LoadModule详细过程:

bool ModuleController::LoadModule(const std::string& path) {
  // 1. 解析DAG文件(Protobuf格式)
  DagConfig dag_config;
  if (!common::GetProtoFromFile(path, &dag_config)) {
    return false;
  }

  // 2. 遍历每个module_config
  for (auto module_config : dag_config.module_config()) {
    // 3. 加载动态库
    std::string load_path = module_config.module_library();
    class_loader_manager_.LoadLibrary(load_path);

    // 4. 创建普通Component
    for (auto& component : module_config.components()) {
      const std::string& class_name = component.class_name();

      // 通过类名创建实例(动态库中已注册)
      std::shared_ptr<ComponentBase> base =
          class_loader_manager_.CreateClassObj<ComponentBase>(class_name);

      // 初始化Component
      if (base == nullptr || !base->Initialize(component.config())) {
        return false;
      }

      // 保存到列表
      component_list_.emplace_back(std::move(base));
    }

    // 5. 创建定时Component
    for (auto& component : module_config.timer_components()) {
      // 类似处理...
    }
  }
  return true;
}

步骤6: 等待关闭

inline void WaitForShutdown() {
  while (!IsShutdown()) {
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
  }
}

主线程在此阻塞,等待以下事件之一:

  • 收到SIGINT信号(Ctrl+C)
  • 其他线程调用SetState(STATE_SHUTTING_DOWN)

步骤7: 清理资源

void ModuleController::Clear() {
  // 1. 关闭所有Component
  for (auto& component : component_list_) {
    component->Shutdown();  // 停止Reader、清理资源
  }

  // 2. 清空Component列表
  component_list_.clear();

  // 3. 卸载所有动态库
  class_loader_manager_.UnloadAllLibrary();
}

动态库加载机制

ClassLoader工作原理

ClassLoader封装了Linux的动态链接库加载机制(dlopen/dlsym)。

1. 库加载流程

bool ClassLoader::LoadLibrary() {
  // 使用dlopen打开.so文件
  void* handle = dlopen(library_path_.c_str(), RTLD_LAZY | RTLD_LOCAL);

  if (!handle) {
    AERROR << "Failed to load library: " << dlerror();
    return false;
  }

  loadlib_ref_count_++;
  return true;
}

2. 类创建机制

Component类需要通过宏注册到全局注册表:

// 在Component实现文件中
CYBER_REGISTER_COMPONENT(PlanningComponent)

// 宏展开后实际上是:
CLASS_LOADER_REGISTER_CLASS(PlanningComponent, apollo::cyber::ComponentBase)

这个宏的作用:

  • 在动态库加载时,向全局注册表添加类的创建函数
  • 类名 → 工厂函数的映射关系

3. 实例创建

template <typename Base>
std::shared_ptr<Base> ClassLoader::CreateClassObj(
    const std::string& class_name) {

  // 从全局注册表中查找类名对应的工厂函数
  Base* class_object = utility::CreateClassObj<Base>(class_name, this);

  if (class_object == nullptr) {
    return std::shared_ptr<Base>();
  }

  // 使用自定义删除器管理引用计数
  std::shared_ptr<Base> classObjSharePtr(
      class_object,
      std::bind(&ClassLoader::OnClassObjDeleter<Base>, this, std::placeholders::_1)
  );

  return classObjSharePtr;
}

ClassLoaderManager管理机制

class ClassLoaderManager {
 private:
  // 库路径 → ClassLoader的映射
  std::map<std::string, ClassLoader*> libpath_loader_map_;
};

bool ClassLoaderManager::LoadLibrary(const std::string& library_path) {
  // 检查是否已加载
  if (libpath_loader_map_.find(library_path) != libpath_loader_map_.end()) {
    return true;  // 避免重复加载
  }

  // 创建新的ClassLoader并加载库
  ClassLoader* loader = new ClassLoader(library_path);
  loader->LoadLibrary();

  libpath_loader_map_[library_path] = loader;
  return true;
}

好处:

  • 避免同一个库被加载多次
  • 统一管理所有ClassLoader
  • 支持按需卸载

Component生命周期管理

Component类层次结构

ComponentBase (抽象基类)
    ↑
    ├─ Component<M0, M1, M2, M3>  (消息驱动组件)
    └─ TimerComponent              (定时触发组件)

生命周期阶段

1. 创建阶段

// mainboard通过ClassLoader创建实例
std::shared_ptr<ComponentBase> base =
    class_loader_manager_.CreateClassObj<ComponentBase>("PlanningComponent");

2. 初始化阶段

bool Component<M0>::Initialize(const ComponentConfig& config) {
  // 1. 创建Node(通信节点)
  node_.reset(new Node(config.name()));

  // 2. 加载配置文件和Flag文件
  LoadConfigFiles(config);

  // 3. 调用用户实现的Init()方法
  if (!Init()) {
    AERROR << "Component Init() failed.";
    return false;
  }

  // 4. 创建Reader订阅消息
  ReaderConfig reader_cfg;
  reader_cfg.channel_name = config.readers(0).channel();
  reader_cfg.qos_profile.CopyFrom(config.readers(0).qos_profile());

  auto func = [self](const std::shared_ptr<M0>& msg) {
    auto ptr = self.lock();
    if (ptr) {
      ptr->Process(msg);  // 收到消息时调用Proc
    }
  };

  auto reader = node_->CreateReader<M0>(reader_cfg, func);
  readers_.emplace_back(std::move(reader));

  // 5. 注册到调度器
  auto sched = scheduler::Instance();
  return sched->CreateTask(factory, node_->Name());
}

关键步骤:

  • 创建Cyber Node(通信节点)
  • 加载配置文件(pb.txt)和Flag文件(.conf)
  • 调用用户的Init()方法进行自定义初始化
  • 创建Reader并注册消息回调
  • 将Component作为Task注册到Scheduler

3. 运行阶段

消息驱动模式 (Component):

// 当订阅的channel有新消息时
Reader回调 → Component::Process() → Component::Proc() (用户实现)

定时触发模式 (TimerComponent):

// 按固定时间间隔触发
Timer触发 → TimerComponent::Process() → TimerComponent::Proc() (用户实现)

4. 关闭阶段

virtual void Shutdown() {
  if (is_shutdown_.exchange(true)) {
    return;  // 避免重复关闭
  }

  // 1. 调用用户的清理方法
  Clear();

  // 2. 关闭所有Reader
  for (auto& reader : readers_) {
    reader->Shutdown();
  }

  // 3. 从调度器中移除Task
  scheduler::Instance()->RemoveTask(node_->Name());
}

Component示例

以Planning模块为例:

// planning_component.h
class PlanningComponent final
    : public cyber::Component<prediction::PredictionObstacles,
                              canbus::Chassis,
                              localization::LocalizationEstimate> {
 public:
  bool Init() override;

  bool Proc(const std::shared_ptr<prediction::PredictionObstacles>& pred,
            const std::shared_ptr<canbus::Chassis>& chassis,
            const std::shared_ptr<localization::LocalizationEstimate>& loc) override;
};

// 注册到类加载器
CYBER_REGISTER_COMPONENT(PlanningComponent)

信号处理与优雅关闭

信号处理机制

1. 注册信号处理器

在Cyber RT初始化时注册:

bool Init(const char* binary_name) {
  // 注册SIGINT处理器(Ctrl+C)
  std::signal(SIGINT, OnShutdown);

  // 注册退出处理器
  std::atexit(ExitHandle);

  return true;
}

2. 信号处理函数

void OnShutdown(int sig) {
  (void)sig;
  if (GetState() != STATE_SHUTDOWN) {
    SetState(STATE_SHUTTING_DOWN);  // 修改全局状态
  }
}

3. 退出处理函数

void ExitHandle() {
  Clear();  // 清理Cyber RT资源
}

关闭流程

用户按Ctrl+C
    ↓
SIGINT信号触发
    ↓
OnShutdown() → SetState(STATE_SHUTTING_DOWN)
    ↓
WaitForShutdown()检测到状态变化,退出循环
    ↓
mainboard主函数继续执行
    ↓
ModuleController::Clear()
    ├─ 遍历所有Component
    │   └─ component->Shutdown()
    │       ├─ 停止Reader接收消息
    │       ├─ 从Scheduler移除Task
    │       └─ 调用用户Clear()方法
    ↓
ClassLoaderManager::UnloadAllLibrary()
    └─ 卸载所有.so动态库
    ↓
程序退出时触发atexit
    ↓
ExitHandle() → cyber::Clear()
    ├─ TaskManager::CleanUp()
    ├─ TimingWheel::CleanUp()
    ├─ Scheduler::CleanUp()
    └─ TopologyManager::CleanUp()

优雅关闭的保证

  1. 状态同步: 使用原子变量is_shutdown_防止重复关闭
  2. 资源顺序释放: 先停止业务逻辑,再清理通信资源
  3. 异常安全: 即使某个Component关闭失败,也继续清理其他资源

完整调用链路

启动链路总览

main()
  └─ ModuleArgument::ParseArgument()
  └─ cyber::Init()
      ├─ InitLogger()
      ├─ SysMo::Instance()
      ├─ signal(SIGINT, OnShutdown)
      └─ atexit(ExitHandle)
  └─ ModuleController::Init()
      └─ LoadAll()
          └─ LoadModule(path)
              └─ GetProtoFromFile(path, &dag_config)
              └─ for each module_config:
                  ├─ ClassLoaderManager::LoadLibrary(lib_path)
                  │   └─ ClassLoader::LoadLibrary()
                  │       └─ dlopen(lib_path)
                  └─ for each component:
                      ├─ CreateClassObj<ComponentBase>(class_name)
                      │   └─ utility::CreateClassObj() [查找注册表]
                      └─ component->Initialize(config)
                          ├─ Node::Node(name)
                          ├─ LoadConfigFiles()
                          ├─ Init() [用户实现]
                          ├─ CreateReader<M0>(config, callback)
                          └─ Scheduler::CreateTask()
  └─ WaitForShutdown()
  └─ ModuleController::Clear()
      ├─ component->Shutdown()
      └─ UnloadAllLibrary()

消息处理链路

外部消息发布到Channel
    ↓
Transport层接收
    ↓
DataDispatcher分发给订阅者
    ↓
Reader收到消息
    ↓
Scheduler调度Task执行
    ↓
Component::Process(msg)
    ↓
Component::Proc(msg) [用户实现]
    └─ 执行业务逻辑
    └─ 通过Writer发布结果

实际使用示例

示例1: 启动Planning模块

DAG配置文件 (planning.dag)

module_config {
  module_library : "/apollo/bazel-bin/modules/planning/libplanning_component.so"

  components {
    class_name : "PlanningComponent"
    config {
      name: "planning"
      config_file_path: "/apollo/modules/planning/conf/planning_config.pb.txt"
      flag_file_path: "/apollo/modules/planning/conf/planning.conf"
      readers: [
        {
          channel: "/apollo/prediction"
        },
        {
          channel: "/apollo/canbus/chassis"
          qos_profile: {
            depth : 15
          }
        },
        {
          channel: "/apollo/localization/pose"
        }
      ]
    }
  }
}

启动命令

mainboard -d /apollo/modules/planning/dag/planning.dag

启动流程

  1. Mainboard启动,解析DAG文件
  2. 加载libplanning_component.so
  3. 创建PlanningComponent实例
  4. 初始化Component:
    • 创建名为"planning"的Node
    • 加载planning_config.pb.txt和planning.conf
    • 调用PlanningComponent::Init()
    • 订阅3个channel的消息
  5. 等待消息到达,触发规划计算

示例2: 同时启动多个模块

mainboard -d /apollo/modules/planning/dag/planning.dag \
          -d /apollo/modules/control/dag/control.dag \
          -d /apollo/modules/prediction/dag/prediction.dag \
          -p autonomous_driving \
          -s CYBER_DEFAULT

所有模块在同一个进程中运行,共享:

  • 同一个Scheduler
  • 同一个Transport层
  • 同一个进程组

示例3: 创建自定义Component

步骤1: 定义Component类

// my_component.h
#include "cyber/component/component.h"
#include "cyber/examples/proto/examples.pb.h"

class MyComponent : public apollo::cyber::Component<Driver> {
 public:
  bool Init() override;
  bool Proc(const std::shared_ptr<Driver>& msg) override;
};

CYBER_REGISTER_COMPONENT(MyComponent)

步骤2: 实现Component

// my_component.cc
#include "my_component.h"

bool MyComponent::Init() {
  AINFO << "MyComponent Init";
  // 自定义初始化逻辑
  return true;
}

bool MyComponent::Proc(const std::shared_ptr<Driver>& msg) {
  AINFO << "Received message: " << msg->msg_id();
  // 处理消息
  return true;
}

步骤3: 编译为动态库

# BUILD
cc_binary(
    name = "libmy_component.so",
    linkshared = True,
    linkstatic = False,
    srcs = ["my_component.cc", "my_component.h"],
    deps = [
        "//cyber",
    ],
)

步骤4: 创建DAG配置

# my.dag
module_config {
  module_library : "/apollo/bazel-bin/mymodule/libmy_component.so"

  components {
    class_name : "MyComponent"
    config {
      name: "my_component"
      readers {
        channel: "/apollo/test_channel"
      }
    }
  }
}

步骤5: 启动

mainboard -d my.dag

总结

Mainboard核心特点

  1. 通用性: 一个工具启动所有模块
  2. 灵活性: 通过DAG配置文件定义加载内容
  3. 动态性: 运行时加载动态库,无需重新编译
  4. 解耦合: 业务逻辑与启动框架分离

关键设计模式

  • 工厂模式: ClassLoader使用工厂创建Component实例
  • 注册模式: CYBER_REGISTER_COMPONENT宏注册类到全局表
  • 单例模式: Scheduler、TopologyManager等核心服务
  • 观察者模式: Reader/Writer的发布订阅机制

最佳实践

  1. 一个模块一个DAG: 便于管理和复用
  2. 合理使用进程组: 隔离不同优先级的任务
  3. 优雅关闭: 在Component::Clear()中清理资源
  4. 错误处理: Init()和Proc()返回false时正确处理

参考文件

  • 源码文件:

    • /apollo/cyber/mainboard/mainboard.cc:27-48
    • /apollo/cyber/mainboard/module_controller.cc:29-141
    • /apollo/cyber/mainboard/module_argument.cc:28-135
    • /apollo/cyber/class_loader/class_loader_manager.h:32-103
    • /apollo/cyber/init.cc:98-152
    • /apollo/cyber/component/component_base.h:41-121
  • 配置示例:

    • /apollo/modules/planning/dag/planning.dag:1-31
    • /apollo/modules/control/dag/control.dag:1-12
本文作者:SakuraPuare
本文链接:https://blog.sakurapuare.com/archives/2025/10/apollo-8-0-mainboard/
版权声明:本文采用 CC BY-NC-SA 4.0 CN 协议进行许可
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇