Apollo:源码分析之cyber/mainboard启动入口介绍
源代码:上图中每个魔抗都以独立的ros node运行,相互之间的消息传递ROS的消息发布与订阅机制。消息订阅等同于数据输入,消息发布等于数据输出。
软件结构图
cyber入口
cyber的入口在"cyber/mainboard"目录中:
├── mainboard.cc // 主函数
├── module_argument.cc // 模块输入参数
├── module_argument.h
├── module_controller.cc // 模块加载,卸载
└── module_controller.h
mainboard中的文件比较少,也很好理解,我们先从"mainboard.cc"中开始分析:
int main(int argc, char** argv) {
// 解析参数
ModuleArgument module_args;
module_args.ParseArgument(argc, argv);
// 初始化cyber
apollo::cyber::Init(argv[0]);
// 加载模块
ModuleController controller(module_args);
if (!controller.Init()) {
controller.Clear();
AERROR << "module start error.";
return -1;
}
// 等待cyber关闭
apollo::cyber::WaitForShutdown();
// 卸载模块
controller.Clear();
AINFO << "exit mainboard.";
return 0;
}
mainboard 是 cyberRT 的入口,init 方法都在这里触发。Module 也在这里启动。
解析参数ModuleArgument
解析参数是在"ModuleArgument"类中实现的,主要是解析加载DAG文件时候带的参数
void ModuleArgument::ParseArgument(const int argc, char* const argv[]) {
// 二进制模块名称
binary_name_ = std::string(basename(argv[0]));
// 解析参数
GetOptions(argc, argv);
// 如果没有process_group_和sched_name_,则赋值为默认值
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_);
// 打印dag_conf配置
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;
}
}
模块加载ModuleController
在"ModuleController"实现cyber模块的加载,在"ModuleController::Init()"中调用"LoadAll()"来加载所有模块
我们接着看cyber是如何加载模块。
- 首先是找到模块的路径
if (module_config.module_library().front() == '/') {
load_path = module_config.module_library();
} else {
load_path =
common::GetAbsolutePath(work_root, module_config.module_library());
}
- 调用LoadModule载入模块。
if (!LoadModule(module_path)) {
}
在这里面,是通过class_loader_manager_加载模块
(1) 然后调用类加载器class_loader_manager_加载好对应的类(通过load_path)
(2)加载好对应的类之后在创建对应的对象,并且初始化对象(调用对象的Initialize()方法,也就是说所有的cyber模块都是通过Initialize()方法启动的)
// 加载模块
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);
// 调用对象的Initialize方法
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));
}
cyber中实现了类型通过动态加载并且实例化类的功能,好处是可以动态加载和关闭单个cyber模块(定位,感知,规划等),也就是在dreamview中的模块开关按钮,实际上就是动态的加载和卸载对应的模块。
小结
cyber mainboard总流程如下
- cyber main函数中先解析dag参数
- 然后根据解析的参数,通过类加载器动态的加载对应的模块
- 最后调用Initialize方法初始化模块
utility
ClassFactory
可以看到有如下继承关系"ClassFactory -> AbstractClassFactory -> AbstractClassFactoryBase",其中"ClassFactory"和"AbstractClassFactory"为模板类,主要的实现在"AbstractClassFactoryBase"中,我们逐个分析:
首先是类初始化,指定了"relative_library_path_", “base_class_name_”, “class_name_”
AbstractClassFactoryBase::AbstractClassFactoryBase(
const std::string& class_name, const std::string& base_class_name)
: relative_library_path_(""),
base_class_name_(base_class_name),
class_name_(class_name) {}
设置OwnedClassLoader,而"RemoveOwnedClassLoader"同理。
void AbstractClassFactoryBase::AddOwnedClassLoader(ClassLoader* loader) {
if (std::find(relative_class_loaders_.begin(), relative_class_loaders_.end(),
loader) == relative_class_loaders_.end()) {
relative_class_loaders_.emplace_back(loader);
}
}
classloader是否属于该classFactory
bool AbstractClassFactoryBase::IsOwnedBy(const ClassLoader* loader) {
std::vector<ClassLoader*>::iterator itr = std::find(
relative_class_loaders_.begin(), relative_class_loaders_.end(), loader);
return itr != relative_class_loaders_.end();
}
也就是说一个ClassFactory能够生产一个路径下的所有类,一个ClassFactory可能会有好几个ClassLoader,分为base_class_name和class_name。
工具函数
接下来我们看"class_loader_utility.cc"的实现,文件中实现了很多函数,这个分析如下:
创建对象(CreateClassObj)的具体实现如下,先找到类对应的factory,然后通过factory创建对象。
template <typename Base>
Base* CreateClassObj(const std::string& class_name, ClassLoader* loader) {
GetClassFactoryMapMapMutex().lock();
ClassClassFactoryMap& factoryMap =
GetClassFactoryMapByBaseClass(typeid(Base).name());
AbstractClassFactory<Base>* factory = nullptr;
if (factoryMap.find(class_name) != factoryMap.end()) {
factory = dynamic_cast<utility::AbstractClassFactory<Base>*>(
factoryMap[class_name]);
}
GetClassFactoryMapMapMutex().unlock();
Base* classobj = nullptr;
if (factory && factory->IsOwnedBy(loader)) {
classobj = factory->CreateObj();
}
return classobj;
}
注册类到factory
template <typename Derived, typename Base>
void RegisterClass(const std::string& class_name,
const std::string& base_class_name)
查找classloader中所有类的名称
template <typename Base>
std::vector<std::string> GetValidClassNames(ClassLoader* loader)
加载类,通过指定的classloader加载指定路径下的库。
bool LoadLibrary(const std::string& library_path, ClassLoader* loader) {
// 类是否已经被加载,如果被加载则对应的class_factory加上依赖的class_loader
if (IsLibraryLoadedByAnybody(library_path)) {
ClassFactoryVector lib_class_factory_objs =
GetAllClassFactoryObjectsOfLibrary(library_path);
for (auto& class_factory_obj : lib_class_factory_objs) {
class_factory_obj->AddOwnedClassLoader(loader);
}
return true;
}
PocoLibraryPtr poco_library = nullptr;
static std::recursive_mutex loader_mutex;
{
std::lock_guard<std::recursive_mutex> lck(loader_mutex);
try {
// 设置当前激活的classloader, 当前加载库路径
SetCurActiveClassLoader(loader);
SetCurLoadingLibraryName(library_path);
// 创建poco_library
poco_library = PocoLibraryPtr(new Poco::SharedLibrary(library_path));
} catch (const Poco::LibraryLoadException& e) {
SetCurLoadingLibraryName("");
SetCurActiveClassLoader(nullptr);
AERROR << "poco LibraryLoadException: " << e.message();
} catch (const Poco::LibraryAlreadyLoadedException& e) {
SetCurLoadingLibraryName("");
SetCurActiveClassLoader(nullptr);
AERROR << "poco LibraryAlreadyLoadedException: " << e.message();
} catch (const Poco::NotFoundException& e) {
SetCurLoadingLibraryName("");
SetCurActiveClassLoader(nullptr);
AERROR << "poco NotFoundException: " << e.message();
}
SetCurLoadingLibraryName("");
SetCurActiveClassLoader(nullptr);
}
if (poco_library == nullptr) {
AERROR << "poco shared library failed: " << library_path;
return false;
}
auto num_lib_objs = GetAllClassFactoryObjectsOfLibrary(library_path).size();
if (num_lib_objs == 0) {
AWARN << "Class factory objs counts is 0, maybe registerclass failed.";
}
std::lock_guard<std::recursive_mutex> lck(GetLibPathPocoShareLibMutex());
LibpathPocolibVector& opened_libraries = GetLibPathPocoShareLibVector();
// 保存加载路径和对应的poco_library
opened_libraries.emplace_back(
std::pair<std::string, PocoLibraryPtr>(library_path, poco_library));
return true;
}
Component
- Component实际上是cyber为了帮助我们特意实现的对象,component加载的时候会自动帮助我们创建一个Node,通过Node来订阅和发布对象,每个Component只能有一个Node
- component对用户提供2个接口"Init()“和"Proc()”,用户在Init中初始化,在Proc中执行具体的算法。对用户隐藏的部分包括component的"Initialize()"的初始化,以及"Process()"调用执行
- component还可以动态的加载和卸载,这也可以对应到在dreamviewer上动态的打开关系模块。
component的工作流程大致如下:
- 通过继承"cyber::Component",用户自定义一个模块,并且实现"Init()"和"Proc()“函数。编译生成”.so"文件。
- 通过classloader加载component模块到内存,创建component对象,调用"Initialize()"初始化。(Initialize中会调用Init)
- 创建协程任务,并且注册"Process()"回调,当数据到来的时候,唤醒对象的协程任务执行"Process()"处理数据。(Process会调用Proc)
综上所述,component帮助用户把初始化和数据收发的流程进行了封装,减少了用户的工作量,component封装了整个数据的收发流程,component本身并不是单独的一个线程执行,模块的初始化都在主线程中执行,而具体的任务则是在协程池中执行。
目录结构
我们先看下component的目录结构。
.
├── BUILD
├── component_base.h // 基础组件
├── component.h // 组件
├── component_test.cc
├── timer_component.cc // 定时组件
├── timer_component.h
└── timer_component_test.cc
https://github.com/daohu527/dig-into-apollo/tree/main/cyber/source
更多推荐
所有评论(0)