目录
CyberRT代码总览:
系统初始化
mainBoard初始化
Component初始化
总结
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 lg(g_mutex);
if (GetState() != STATE_UNINITIALIZED) {
return false;
}
InitLogger(binary_name);
auto thread = const_cast(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(new Node(node_name));
auto cb =
[](const std::shared_ptr& msg) {
if (msg->has_clock()) {
Clock::Instance()->SetNow(Time(msg->clock()));
}
};
clock_node->CreateReader(kClockChannel, cb);
}
return true;
}
对于用户而言,采用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 base =
class_loader_manager_.CreateClassObj(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 base =
class_loader_manager_.CreateClassObj(class_name);
if (base == nullptr || !base->Initialize(component.config())) {
return false;
}
component_list_.emplace_back(std::move(base));
}
}
return true;
}
template
bool Component::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> self =
std::dynamic_pointer_cast>(shared_from_this());
// 定义func的含义是,reader收到msg之后,当该component未被析构,那么将触发Process函数
// 提前注册好用户回调,由msg到达事件触发,类似于reactor模式。
//
auto func = [self](const std::shared_ptr& 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 = nullptr;
// 创建reader对象
if (cyber_likely(is_reality_mode)) {
reader = node_->CreateReader(reader_cfg);
} else {
reader = node_->CreateReader(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>(conf);
// 创建协程任务。将协程任务与消息缓冲区关联起来
// 后续就可以通过协程对消息驱动型任务进行调度
croutine::RoutineFactory factory =
croutine::CreateRoutineFactory(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实现。