Cyber RT 代码分析
cyber base
xz@xiaqiu:~/study/apollo/cyber/base$ tree
.
├── atomic_hash_map.h
├── atomic_hash_map_test.cc
├── atomic_rw_lock.h
├── atomic_rw_lock_test.cc
├── bounded_queue.h
├── bounded_queue_test.cc
├── build
├── BUILD
├── CMakeLists.txt
├── concurrent_object_pool.h
├── for_each.h
├── for_each_test.cc
├── macros.h
├── main_test.cc
├── object_pool.h
├── object_pool_test.cc
├── reentrant_rw_lock.h
├── rw_lock_guard.h
├── signal.h
├── signal_test.cc
├── thread_pool.h
├── thread_safe_queue.h
├── unbounded_queue.h
├── unbounded_queue_test.cc
└── wait_strategy.h
1 directory, 24 files
测试的CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(CyberBase)
set(CMAKE_CXX_STANDARD 17)
set(PROJECT_SOURCE_DIR "../../")
INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR})
message(${PROJECT_SOURCE_DIR})
add_executable(atomic_hash_map_test main_test.cc atomic_hash_map_test.cc)
target_link_libraries(atomic_hash_map_test -lgtest -lpthread)
#target_link_libraries(signal_test Cyber::signal)
build目录为代码生成测试目录
cyber 入口
cyber 的入口在"cyber/mainboard"目录中:
xz@xiaqiu:~/study/apollo/cyber/mainboard$ tree
.
├── BUILD
├── mainboard.cc //主函数
├── module_argument.cc //模块输入参数
├── module_argument.h
├── module_controller.cc //模块加载,卸载
└── module_controller.h
0 directories, 6 files
mainboard 中的文件比较少,也很好理解,我们先从"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 初始化 cyber
apollo::cyber::Init(argv[0]);
// start module 加载模块
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.cc”的主函数,下面我们重点介绍下具体的过程。
解析参数
解析参数是在“ModuleArgument”类中实现的,主要是解析加载 DAG 文件时候带的参数。
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";
}
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_);
AINFO << "binary_name_ is " << binary_name_ << ", process_group_ is "
<< process_group_ << ", has " << dag_conf_list_.size() << " dag conf";
//打印 dag_conf配置,这里的 dag 是否可以设置多个?
for (std::string &dag : dag_conf_list_)
{
AINFO << "dag_conf: " << dag;
}
}
模块加载
在“ModuleController”实现 cyber 模块的加载在“ModuleController:Init0”中调用“LoadAlI() ”来加载所有模块,我们接着看 cyber 是如何加载模块。
首先是找到模块的路径。
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_ ”加载模块,后面我们会接着分析“ClassLoaderManager”的具体实现,加载好对应的类之后在创建对应的允象,并且初始化对象.〈调用对象的 Initialize(方法,也就是说所有的 cyber 模块都是通过 Initialize()方法启动的,后面们会接团分析 Initialize 具体干了什么) 。这里的“classloader” 其实类似 java 中的 classloader,即 java 虚拟机在运行时加载对应的类,并且实例化人_” 对象。cyber 中其实也是实现了类型通过动态加载并且实例化类的功能,好处是可以动态加载和关闭单个 cyber模块(定位,感知,规划等),也就是在 dreamview 中的模块开关按钮,实际上就是动态的加载和印载对应的模块。
//通过类加载器加载.1oad_path 下的模class_1oader_manager_.LoadLibrary(load path);
//加载模块
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 mainboard 的整个流程,cyber main 函数中先解析 dag 参数滞然后根据解析的参数,通过类加载器动态的加载对应的模块,然后调用 Initialize方法初始化模块。
动态加载对象
类加载器(class_loader)类加载器的作用就是动态的加载动态库然后实例化对象。我们先来解释下, 首先 apollo 中的各个 module都会编译为一个动态库,拿 planning 模块来举例子,在“planning/dag/planning.dag”中,会加载:
module_config {
module_library : "/apollo/bazel-bin/modules/planning/libplanning_component.so"
也就是说,apollo 中的模块都会通过类加载器以动态库的方式加载,然后实例化,之后再调用 Initialize 方法初始化。也就是说,我们讲清楚下面 3 个问题,也就是讲清楚了类加载器的原理。
1、cyber 如何加载 apollo 模块?
2、如何实例化模块?
3、如何初始化模块?
类加载器的实现在“cyber/class_loader”目录中,通过“Poco/SharedLibraryh”库来实现动态库的加载,关于 Poco 动态库的加载可以[参考](Class Poco::SharedLibrary)
xz@xiaqiu:~/study/apollo/cyber/class_loader$ tree
.
├── BUILD //编译文件
├── class_loader.cc//类加载器
├── class_loader.h
├── class_loader_manager.cc//类加载器管理
├── class_loader_manager.h
├── class_loader_register_macro.h//类加载器注册宏定义
├── class_loader_test.cc
├── shared_library
│ ├── BUILD
│ ├── exceptions.h
│ ├── sample.cc
│ ├── sample.h
│ ├── shared_library.cc
│ ├── shared_library.h
│ └── shared_library_test.cc
├── test
│ ├── base.h
│ ├── BUILD
│ ├── plugin1.cc
│ └── plugin2.cc
└── utility
├── BUILD
├── class_factory.cc //类工厂
├── class_factory.h
├── class_loader_utility.cc//类加载器工具类
└── class_loader_utility.h
3 directories, 23 files
我们先从“class_loaderh”开始看起,首先我们分析下“class_loader”实现的具体方法:
/** * for library load,createclass object */class ClassLoader { public: explicit ClassLoader(const std::string& library_path); virtual ~ClassLoader(); bool IsLibraryLoaded();//库是否已经加载 bool LoadLibrary();// 贡载库 int UnloadLibrary();// 卸载库 const std::string GetLibraryPath() const;// 获取库路径 template <typename Base> std::vector<std::string> GetValidClassNames();// 获取类名称 template <typename Base> std::shared_ptr<Base> CreateClassObj(const std::string& class_name);// 实例化类对象 template <typename Base> bool IsClassValid(const std::string& class_name);//判断类是否有效 private: template <typename Base> void OnClassObjDeleter(Base* obj); private: std::string library_path_;// 类路径 int loadlib_ref_count_;// 类加载引用次数 std::mutex loadlib_ref_count_mutex_;// 类加载引用次数锁 int classobj_ref_count_;// 类引用次数 std::mutex classobj_ref_count_mutex_;// 类引用次数锁};