CyberRT源码剖析

目录

CyberRT代码总览:

系统初始化

mainBoard初始化

Component初始化

总结


CyberRT代码总览:

base:提供一些基础功能接口的封装,主要包括原子hash_map,读写锁,有界无锁队列

blocker:待定

class_loader:类加载器,基于用户提供的动态库路径,通过反射机制将用户类加载至内存

common:提供获取环境变量,操作文件接口,全局配置信息等

component:提供component基类,用户可集成该基类实现自己的内置逻辑,定时组件能力

conf:配置文件示例,主要是针对于任务的调度属性所设置

coroutine:协程基础接口的封装

data:消息缓存以及消息分发

event:待定。和性能评测相关?

examples:使用示例

io:基于协程封装的网络IO接口

logger:异步日志系统

mainboard:程序加载器,加载动态库,基于用户配置设置调度属性,拉起进程

message:消息相关,消息格式,消息序列化方式

Node:封装了进程内通讯所需的Reader/Writer创建接口,跨进程通讯的Service/Client接口

Parameter:参数相关,基于node的Service/Client方式实现。

proto:内置消息的消息属性

record:待定

scheduler:协程调度器相关

service:跨进程通信接口

service_discovery:服务发现,通讯接口

sysmo:系统检测,提供诊断能力

task:异步任务创建能力

timer:定时器能力

tools:相关诊断检测/记录回放/控制进程加载器等工具

transport:通讯基本接口

Cyber.h:创建node节点接口

init.h:初始化接口

state.h:全局cyber系统状态管理接口

下面针对上述重点模块分别介绍。

系统初始化

CyberRT的状态共有

enum State : std::uint8_t {
  STATE_UNINITIALIZED = 0,
  STATE_INITIALIZED,
  STATE_SHUTTING_DOWN,
  STATE_SHUTDOWN,
};
  • 初始时处于STATE_UNINITIALIZED 态。在Init函数仅会对未初始化的系统进行初始化,通过g_mutex来保证初始化的线程安全。

  • 初始化日志系统线程,根据async_log名称,找到用户在配置文件里设置对应的线程属性,并设置。

  • 启动诊断功能

  • 捕获SIGINT即CTRL+C信号

  • std::atexit(ExitHandle),注册退出后执行函数。释放申请资源。

  • 判断全局配置中是否启动模拟时钟功能,若启动则基于Reader/Writer模式,构建模拟时钟的回调。

模拟时钟可能很多人并没有这个概念,模拟时钟依赖于"/clock"的topic。pub端与sub端基于该topic可以在建立通信链路。

大致的实现逻辑:由pub端定时发送一个时间消息至sub端,该消息里内置时间的大小信息,通过pub/sub机制,触发sub端的回调,而sub端解析该消息,获取pub端期望的时间并设置对应的模拟时钟源的时间。

一般可用于fillback回灌场景,通过模拟时钟的方式控制回灌的速率/位置/方向等。

bool Init(const char* binary_name) {
  std::lock_guard<std::mutex> lg(g_mutex);
  if (GetState() != STATE_UNINITIALIZED) {
    return false;
  }

  InitLogger(binary_name);
  auto thread = const_cast<std::thread*>(async_logger->LogThread());
  scheduler::Instance()->SetInnerThreadAttr("async_log", thread);
  SysMo::Instance();
  std::signal(SIGINT, OnShutdown);
  // Register exit handlers
  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);

  auto global_data = GlobalData::Instance();
  if (global_data->IsMockTimeMode()) {
    auto node_name = kClockNode + std::to_string(getpid());
    clock_node = std::unique_ptr<Node>(new Node(node_name));
    auto cb =
        [](const std::shared_ptr<const apollo::cyber::proto::Clock>& msg) {
          if (msg->has_clock()) {
            Clock::Instance()->SetNow(Time(msg->clock()));
          }
        };
    clock_node->CreateReader<apollo::cyber::proto::Clock>(kClockChannel, cb);
  }
  return true;
}

mainBoard初始化

对于用户而言,采用CyberRT的大致开发流程如下:

继承component类->复写component接口所提供的Init/Proc等方法->编译成动态库->采用conf文件,设置多任务调度属性。

mainboard作为程序启动器,是程序main函数的入口。承担了加载用户动态库,加载用户配置,并设置相关属性等功能。

  • 解析文件参数

  • 调用系统初始化函数

  • 加载所有的动态库

  • 加载配置信息

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;
}

基于配置的动态库路径加载component:

bool ModuleController::LoadModule(const DagConfig& dag_config) {
  const std::string work_root = common::WorkRoot();

  for (auto module_config : dag_config.module_config()) {
    std::string load_path;
    if (module_config.module_library().front() == '/') {
      load_path = module_config.module_library();
    } else {
      load_path =
          common::GetAbsolutePath(work_root, module_config.module_library());
    }

    if (!common::PathExists(load_path)) {
      AERROR << "Path does not exist: " << load_path;
      return false;
    }
    // 加载动态库
    class_loader_manager_.LoadLibrary(load_path);
    // 创建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的Initialize函数,component组合一个node_
      // 在Initialize初始化时构建node对象,依赖node接口实现具体的功能。
      if (base == nullptr || !base->Initialize(component.config())) {
        return false;
      }
      component_list_.emplace_back(std::move(base));
    }

    for (auto& component : module_config.timer_components()) {
      const std::string& class_name = component.class_name();
      std::shared_ptr<ComponentBase> base =
          class_loader_manager_.CreateClassObj<ComponentBase>(class_name);
      if (base == nullptr || !base->Initialize(component.config())) {
        return false;
      }
      component_list_.emplace_back(std::move(base));
    }
  }
  return true;
}

Component初始化

template <typename M0>
bool Component<M0, NullType, NullType, NullType>::Initialize(
    const ComponentConfig& config) {
  // 构建node对象,负责Reader/Service功能实现
  node_.reset(new Node(config.name()));
  LoadConfigFiles(config);

  if (config.readers_size() < 1) {
    AERROR << "Invalid config file: too few readers.";
    return false;
  }
  // Init类由用户集成component而实现
  // 用了template method模式,先确定了软件框架,而后由用户实现具体功能。
  if (!Init()) {
    AERROR << "Component Init() failed.";
    return false;
  }

  bool is_reality_mode = GlobalData::Instance()->IsRealityMode();
  // 解析组件配置构建reader对象
  ReaderConfig reader_cfg;
  reader_cfg.channel_name = config.readers(0).channel();
  reader_cfg.qos_profile.CopyFrom(config.readers(0).qos_profile());
  reader_cfg.pending_queue_size = config.readers(0).pending_queue_size();
  
  // 创建异步回调,shared_from_this返回当前对象的shared_ptr
  std::weak_ptr<Component<M0>> self =
      std::dynamic_pointer_cast<Component<M0>>(shared_from_this());
  // 定义func的含义是,reader收到msg之后,当该component未被析构,那么将触发Process函数
  // 提前注册好用户回调,由msg到达事件触发,类似于reactor模式。
  // 
  auto func = [self](const std::shared_ptr<M0>& msg) {
    // weak_ptr.lock标识类是否被析构,如果未被析构则调用Process接口
    auto ptr = self.lock();
    if (ptr) {
      // 用户实现
      ptr->Process(msg);
    } else {
      AERROR << "Component object has been destroyed.";
    }
  };

  std::shared_ptr<Reader<M0>> reader = nullptr;
  // 创建reader对象
  if (cyber_likely(is_reality_mode)) {
    reader = node_->CreateReader<M0>(reader_cfg);
  } else {
    reader = node_->CreateReader<M0>(reader_cfg, func);
  }

  if (reader == nullptr) {
    AERROR << "Component create reader failed.";
    return false;
  }
  readers_.emplace_back(std::move(reader));

  if (cyber_unlikely(!is_reality_mode)) {
    return true;
  }
  // 创建Datavisitor消息缓冲区
  // 路由至CacheBuffer,本质上是由vector所组成的ringbuffer
  data::VisitorConfig conf = {readers_[0]->ChannelId(),
                              readers_[0]->PendingQueueSize()};
  auto dv = std::make_shared<data::DataVisitor<M0>>(conf);
  // 创建协程任务。将协程任务与消息缓冲区关联起来
  // 后续就可以通过协程对消息驱动型任务进行调度
  croutine::RoutineFactory factory =
      croutine::CreateRoutineFactory<M0>(func, dv);
  auto sched = scheduler::Instance();
  // 调度器创建协程任务
  return sched->CreateTask(factory, node_->Name());
}

总结

Cyber应用程序由mainboard作为入口进行启动,启动时需要通过ClassLoaderManager,通过反射机制,按照类名,加载各个动态库至内存中的类,并初始化各个component。

初始化component会定义和msg相关联的回调函数func,func会在收到msg的时候执行,执行函数为用户自定义的proc函数。

创建消息缓冲区,即Datavisitor。

创建协程,该协程主要用于对消息的处理以及func的调度。具体调度逻辑将由调度器Scheduler实现。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值