严正声明:本文系作者davidhopper原创,未经许可,不得转载。

Apollo 3.5彻底摒弃ROS,改用自研的Cyber作为底层通讯与调度平台。各功能模块的启动过程与之前版本天壤之别。本文对Apollo 3.5 各功能模块的启动过程进行解析(关闭过程可作类似分析,不再赘述),希望给感兴趣的同学带来一定的帮助。

一、DreamView模块启动过程

先从启动脚本文件scripts/bootstrap.sh开始剖析。服务启动命令bash scripts/bootstrap.sh start实际上执行了scripts/bootstrap.sh脚本中的start函数:

function start() {./scripts/monitor.sh start./scripts/dreamview.sh startif [ $? -eq 0 ]; thenhttp_status="$(curl -o -I -L -s -w '%{http_code}' ${DREAMVIEW_URL})"if [ $http_status -eq 200 ]; thenecho "Dreamview is running at" $DREAMVIEW_URLelseecho "Failed to start Dreamview. Please check /apollo/data/log or /apollo/data/core for more information"fifi
}

start函数内部分别调用脚本文件scripts/monitor.shscripts/dreamview.sh内部的start函数启动monitordreamview模块。monitor模块的启动过程暂且按下不表,下面专门研究dreamview模块的start函数。scripts/dreamview.sh文件内容如下:

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"cd "${DIR}/.."source "${DIR}/apollo_base.sh"# run function from apollo_base.sh
# run command_name module_name
run dreamview "$@"

里面压根没有start函数,但我们找到一个apollo_base.sh脚本文件,并且有一条调用语句:run dreamview "$@"(展开以后就是run dreamview start)。我们有理由判断,run函数存在于apollo_base.sh脚本文件,现在到里面一探究竟,不出意外果然有一个run函数:

function run() {local module=$1shiftrun_customized_path $module $module "$@"
}

上述代码中,module的值为dreamview$@的值为start,因此后面继续调用run_customized_path dreamview dreamview start。继续顺藤摸瓜查看run_customized_path函数:

function run_customized_path() {local module_path=$1local module=$2local cmd=$3shift 3case $cmd instart)start_customized_path $module_path $module "$@";;# ...
}

实际调用的是start_customized_path dreamview dreamview。再来查看start_customized_path函数:

function start_customized_path() {MODULE_PATH=$1MODULE=$2shift 2is_stopped_customized_path "${MODULE_PATH}" "${MODULE}"if [ $? -eq 1 ]; theneval "nohup cyber_launch start /apollo/modules/${MODULE_PATH}/launch/${MODULE}.launch &"sleep 0.5is_stopped_customized_path "${MODULE_PATH}" "${MODULE}"if [ $? -eq 0 ]; thenecho "Launched module ${MODULE}."return 0elseecho "Could not launch module ${MODULE}. Is it already built?"return 1fielseecho "Module ${MODULE} is already running - skipping."return 2fi
}

start_customized_path函数内部,首先调用is_stopped_customized_path函数来判断(在内部实际通过指令$(pgrep -c -f "modules/dreamview/launch/dreamview.launch")来判断)dreamview模块是否已启动。若该模块未启动,则使用指令nohup cyber_launch start /apollo/modules/dreamview/launch/dreamview.launch &以非挂断方式启动后台进程模块dreamviewcyber_launchCyber平台提供的一个python工具程序,其完整路径为:${APOLLO_HOME}/cyber/tools/cyber_launch/cyber_launch(可通过sudo find / -name cyber_launch查找,${APOLLO_HOME}表示Apollo项目的根目录,以我的机器为例,Docker外部为/home/davidhopper/code/apollo,Docker内部自不必说,全部为/apollo。为描述简单起见,下文全部以Docker内部的路径/apollo为准)。下面继续研究cyber_launch中的main函数:

def main():""" main function """cyber_path = os.getenv('CYBER_PATH')if cyber_path == None:logger.error('Error: environment variable CYBER_PATH not found, set environment first.')sys.exit(1)os.chdir(cyber_path)parser = argparse.ArgumentParser(description='cyber launcher')subparsers = parser.add_subparsers(help='sub-command help')start_parser = subparsers.add_parser('start', help='launch/benchmark.launch')start_parser.add_argument('file', nargs='?', action='store', help='launch file, default is cyber.launch')stop_parser = subparsers.add_parser('stop', help='stop all the module in launch file')stop_parser.add_argument('file', nargs='?', action='store', help='launch file, default stop all the launcher')#restart_parser = subparsers.add_parser('restart', help='restart the module')#restart_parser.add_argument('file', nargs='?', action='store', help='launch file, default is cyber.launch')params = parser.parse_args(sys.argv[1:])command = sys.argv[1]if command == 'start':start(params.file)elif command == 'stop':stop_launch(params.file)#elif command == 'restart':#    restart(params.file)else:logger.error('Invalid command %s' % command)sys.exit(1)

该函数无非进行一些命令行参数解析,然后调用start(/apollo/modules/dreamview/launch/dreamview.launch)函数启动dreamview模块。继续查看start函数,该函数内容很长,不再详细解释,其主要功能是解析XML文件/apollo/modules/dreamview/launch/dreamview.launch中的各项元素:namedag_conftypeprocess_nameexception_handler,其值分别为:dreamviewnullbinary/apollo/bazel-bin/modules/dreamview/dreamview --flagfile=/apollo/modules/common/data/global_flagfile.txtrespawn,然后调用ProcessWrapper(process_name.split()[0], 0, [""], process_name, process_type, exception_handler)创建一个ProcessWrapper对象pw,然后调用pw.start()函数启动dreamview模块:

def start(launch_file = ''):# ...process_list = []root = tree.getroot()for module in root.findall('module'):module_name = module.find('name').textdag_conf = module.find('dag_conf').textprocess_name = module.find('process_name').textsched_name = module.find('sched_name')process_type = module.find('type')exception_handler = module.find('exception_handler')# ...if process_name not in process_list:if process_type == 'binary':if len(process_name) == 0:logger.error('Start binary failed. Binary process_name is null')continuepw = ProcessWrapper(process_name.split()[0], 0, [""], process_name, process_type, exception_handler)# default is libraryelse:pw = ProcessWrapper(g_binary_name, 0, dag_dict[str(process_name)], process_name, process_type, sched_name, exception_handler)result = pw.start()if result != 0:logger.error('Start manager [%s] failed. Stop all!' % process_name)stop()pmon.register(pw)process_list.append(process_name)# no module in xmlif not process_list:logger.error("No module was found in xml config.")returnall_died = pmon.run()if not all_died:logger.info("Stop all processes...")stop()logger.info("Cyber exit.")

下面查看ProcessWrapper类里的start函数:

    def start(self):"""start a manager in process name"""if self.process_type == 'binary':args_list = self.name.split()else:args_list = [self.binary_path, '-d'] + self.dag_listif len(self.name) != 0:args_list.append('-p')args_list.append(self.name)if len(self.sched_name) != 0:args_list.append('-s')args_list.append(self.sched_name)self.args = args_listtry:self.popen = subprocess.Popen(args_list, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)except Exception, e:logger.error('Subprocess Popen exception: ' + str(e))return 2else:if self.popen.pid == 0 or self.popen.returncode is not None:logger.error('Start process [%s] failed.' % self.name)return 2th = threading.Thread(target=module_monitor, args=(self, ))th.setDaemon(True)th.start()self.started = Trueself.pid = self.popen.pidlogger.info('Start process [%s] successfully. pid: %d' % (self.name, self.popen.pid))logger.info('-' * 120)return 0

在该函数内部调用/apollo/bazel-bin/modules/dreamview/dreamview --flagfile=/apollo/modules/common/data/global_flagfile.txt最终启动了dreamview进程。dreamview进程的main函数位于/apollo/modules/dreamview/backend/main.cc中,内容如下所示:

int main(int argc, char *argv[]) {google::ParseCommandLineFlags(&argc, &argv, true);// add by caros for dv performance improveapollo::cyber::GlobalData::Instance()->SetProcessGroup("dreamview_sched");apollo::cyber::Init(argv[0]);apollo::dreamview::Dreamview dreamview;const bool init_success = dreamview.Init().ok() && dreamview.Start().ok();if (!init_success) {AERROR << "Failed to initialize dreamview server";return -1;}apollo::cyber::WaitForShutdown();dreamview.Stop();apollo::cyber::Clear();return 0;
}

该函数初始化Cyber环境,并调用Dreamview::Init()Dreamview::Start()函数,启动Dreamview后台监护进程。然后进入消息处理循环,直到等待cyber::WaitForShutdown()返回,清理资源并退出main函数。

二、功能模块(以Planning为例)启动过程

Apollo 3.5使用Cyber启动LocalizationPerceptionPredictionPlanningControl等功能模块。若只看各模块的BUILD文件,保证你无法找到该模块的启动入口main函数(Apollo 3.5之前的版本均是如此处理)。下面以Planning模块为例具体阐述。
Planning模块BUILD文件中生成binary文件的配置项如下:

cc_binary(name = "libplanning_component.so",linkshared = True,linkstatic = False,deps = [":planning_component_lib"],
)

该配置项中没有source文件,仅包含一个依赖项:planning_component_lib。又注意到后者的定义如下:

cc_library(name = "planning_component_lib",srcs = ["planning_component.cc",],hdrs = ["planning_component.h",],copts = ["-DMODULE_NAME=\\\"planning\\\"",],deps = [":planning_lib","//cyber","//modules/common/adapters:adapter_gflags","//modules/common/util:message_util","//modules/localization/proto:localization_proto","//modules/map/relative_map/proto:navigation_proto","//modules/perception/proto:perception_proto","//modules/planning/proto:planning_proto","//modules/prediction/proto:prediction_proto",],
)

srcs文件planning_component.cc以及deps文件中均找不到main函数。那么main函数被隐藏在哪里?如果没有main函数,binary文件libplanning_component.so又是如何启动的?答案很简单,planning模块的binary文件libplanning_component.so作为cyber的一个组件启动,不需要main函数。
下面详细阐述在DreamView界面中启动Planning模块的过程。DreamView前端界面操作此处不表,后端的消息响应函数HMI::RegisterMessageHandlers()位于/apollo/modules/dreamview/backend/hmi/hmi.cc文件中:

void HMI::RegisterMessageHandlers() {// ...websocket_->RegisterMessageHandler("HMIAction",[this](const Json& json, WebSocketHandler::Connection* conn) {// Run HMIWorker::Trigger(action) if json is {action: "<action>"}// Run HMIWorker::Trigger(action, value) if "value" field is provided.std::string action;if (!JsonUtil::GetStringFromJson(json, "action", &action)) {AERROR << "Truncated HMIAction request.";return;}HMIAction hmi_action;if (!HMIAction_Parse(action, &hmi_action)) {AERROR << "Invalid HMIAction string: " << action;}std::string value;if (JsonUtil::GetStringFromJson(json, "value", &value)) {hmi_worker_->Trigger(hmi_action, value);} else {hmi_worker_->Trigger(hmi_action);}// Extra works for current Dreamview.if (hmi_action == HMIAction::CHANGE_MAP) {// Reload simulation map after changing map.CHECK(map_service_->ReloadMap(true))<< "Failed to load new simulation map: " << value;} else if (hmi_action == HMIAction::CHANGE_VEHICLE) {// Reload lidar params for point cloud service.PointCloudUpdater::LoadLidarHeight(FLAGS_lidar_height_yaml);SendVehicleParam();}});// ...
}

其中,HMIAction_Parse(action, &hmi_action)用于解析动作参数,hmi_worker_->Trigger(hmi_action, value)用于执行相关动作。对于Planning模块的启动而言,hmi_action的值为HMIAction::START_MODULEvalue的值为Planning。实际上,DreamView将操作模式分为多种hmi mode,这些模式位于目录/apollo/modules/dreamview/conf/hmi_modes,每一个配置文件均对应一种hmi mode(更多关于hmi mode的介绍,请参见博客Apollo 3.5 Cyber - 如何為Dreamview新增hmi mode)。不管处于哪种hmi mode,对于Planning模块的启动而言,hmi_action的值均为HMIAction::START_MODULEvalue的值均为Planning。当然,Standard ModeNavigation Mode对应的dag_files不一样,Standard Modedag_files/apollo/modules/planning/dag/planning.dagNavigation Modedag_files/apollo/modules/planning/dag/planning_navi.dag
HMIWorker::Trigger(const HMIAction action, const std::string& value)函数位于文件/apollo/modules/dreamview/backend/hmi/hmi_worker.cc中,其内容如下:

bool HMIWorker::Trigger(const HMIAction action, const std::string& value) {AINFO << "HMIAction " << HMIAction_Name(action) << "(" << value<< ") was triggered!";switch (action) {// ...case HMIAction::START_MODULE:StartModule(value);break;// ...}return true;
}

继续研究HMIWorker::StartModule(const std::string& module)函数:

void HMIWorker::StartModule(const std::string& module) const {const Module* module_conf = FindOrNull(current_mode_.modules(), module);if (module_conf != nullptr) {System(module_conf->start_command());} else {AERROR << "Cannot find module " << module;}
}

上述函数中成员变量current_mode_保存着当前hmi mode对应配置文件包含的所有配置项。例如modules/dreamview/conf/hmi_modes/mkz_standard_debug.pb.txt里面就包含了MKZ标准调试模式下所有的功能模块,该配置文件通过HMIWorker::LoadMode(const std::string& mode_config_path)函数读入到成员变量current_mode_中。如果基于字符串module查找到了对应的模块名以及对应的启动配置文件dag_files,则调用System函数(内部实际调用std::system函数)基于命令module_conf->start_command()启动一个进程。这个start_command从何而来?需进一步分析HMIWorker::LoadMode(const std::string& mode_config_path)函数:

HMIMode HMIWorker::LoadMode(const std::string& mode_config_path) {HMIMode mode;CHECK(common::util::GetProtoFromFile(mode_config_path, &mode))<< "Unable to parse HMIMode from file " << mode_config_path;// Translate cyber_modules to regular modules.for (const auto& iter : mode.cyber_modules()) {const std::string& module_name = iter.first;const CyberModule& cyber_module = iter.second;// Each cyber module should have at least one dag file.CHECK(!cyber_module.dag_files().empty()) << "None dag file is provided for "<< module_name << " module in "<< mode_config_path;Module& module = LookupOrInsert(mode.mutable_modules(), module_name, {});module.set_required_for_safety(cyber_module.required_for_safety());// Construct start_command://     nohup mainboard -p <process_group> -d <dag> ... &module.set_start_command("nohup mainboard");const auto& process_group = cyber_module.process_group();if (!process_group.empty()) {StrAppend(module.mutable_start_command(), " -p ", process_group);}for (const std::string& dag : cyber_module.dag_files()) {StrAppend(module.mutable_start_command(), " -d ", dag);}StrAppend(module.mutable_start_command(), " &");// Construct stop_command: pkill -f '<dag[0]>'const std::string& first_dag = cyber_module.dag_files(0);module.set_stop_command(StrCat("pkill -f \"", first_dag, "\""));// Construct process_monitor_config.module.mutable_process_monitor_config()->add_command_keywords("mainboard");module.mutable_process_monitor_config()->add_command_keywords(first_dag);}mode.clear_cyber_modules();AINFO << "Loaded HMI mode: " << mode.DebugString();return mode;
}

通过该函数可以看到,构建出的start_command格式为nohup mainboard -p <process_group> -d <dag> ... &,其中,<process_group>dag均来自于当前hmi mode对应的配置文件。以modules/dreamview/conf/hmi_modes/mkz_close_loop.pb.txt为例,它包含两个cyber_modules配置项,对于Computer模块而言,它包含了11个dag_files文件(对应11个子功能模块),这些子功能模块全部属于名为compute_schedprocess_group。根据博客Apollo 3.5 Cyber - 如何為Dreamview新增hmi mode中的描述,process_group就是Cyber中调度配置文件scheduler conf的名字,process_group: "compute_sched"表明使用配置文件cyber/conf/compute_sched.conf 进行任务调度,process_group: "control_sched"表明使用配置文件control_sched.conf进行任务调度。
<dag>自不必言,表示一个DAG(Directed Acyclic Graph,有向无环图)节点,每个子功能模块对应一个dag_filesPlanning子功能模块对应的dag_files/apollo/modules/planning/dag/planning.dag

cyber_modules {key: "Computer"value: {dag_files: "/apollo/modules/drivers/camera/dag/camera_no_compress.dag"dag_files: "/apollo/modules/drivers/gnss/dag/gnss.dag"dag_files: "/apollo/modules/drivers/radar/conti_radar/dag/conti_radar.dag"dag_files: "/apollo/modules/drivers/velodyne/dag/velodyne.dag"dag_files: "/apollo/modules/localization/dag/dag_streaming_msf_localization.dag"dag_files: "/apollo/modules/perception/production/dag/dag_streaming_perception.dag"dag_files: "/apollo/modules/perception/production/dag/dag_streaming_perception_trafficlights.dag"dag_files: "/apollo/modules/planning/dag/planning.dag"dag_files: "/apollo/modules/prediction/dag/prediction.dag"dag_files: "/apollo/modules/routing/dag/routing.dag"dag_files: "/apollo/modules/transform/dag/static_transform.dag"process_group: "compute_sched"}
}
cyber_modules {key: "Controller"value: {dag_files: "/apollo/modules/canbus/dag/canbus.dag"dag_files: "/apollo/modules/control/dag/control.dag"dag_files: "/apollo/modules/guardian/dag/guardian.dag"process_group: "control_sched"}
}
# ...

至此,我们终于找到了Planning功能模块的启动命令为:

nohup mainboard -p compute_sched -d /apollo/modules/planning/dag/planning.dag &

说明:上述命令是个简化的说法,实际上对于配置文件modules/dreamview/conf/hmi_modes/mkz_close_loop.pb.txt而言,它包含两个大的启动命令,如下所示:

nohup mainboard -p compute_sched
-d /apollo/modules/drivers/camera/dag/camera_no_compress.dag
-d /apollo/modules/drivers/gnss/dag/gnss.dag
-d /apollo/modules/drivers/radar/conti_radar/dag/conti_radar.dag
-d /apollo/modules/drivers/velodyne/dag/velodyne.dag
-d /apollo/modules/localization/dag/dag_streaming_msf_localization.dag
-d /apollo/modules/perception/production/dag/dag_streaming_perception.dag
-d /apollo/modules/perception/production/dag/dag_streaming_perception_trafficlights.dag
-d /apollo/modules/planning/dag/planning.dag
-d /apollo/modules/prediction/dag/prediction.dag
-d /apollo/modules/routing/dag/routing.dag
-d /apollo/modules/transform/dag/static_transform.dag &
nohup mainboard -p control_sched
-d /apollo/modules/canbus/dag/canbus.dag
-d /apollo/modules/control/dag/control.dag
-d /apollo/modules/guardian/dag/guardian.dag &

上面作出简化,主要目的是让我们将精力集中于分析Planning功能模块的启动过程。
nohup表示非挂断方式启动,mainboard无疑是启动的主程序,入口main函数必定包含于其中。-p compute_sched-p control_sched是Cyber RT调度策略,-p compute_sched表示使用调度策略文件/apollo/cyber/conf/compute_sched.conf对加载的模块实施调度,-p control_sched表示使用调度策略文件/apollo/cyber/conf/control_sched.conf对加载的模块实施调度;一系列的-d ...才是我们启动相关功能模块的配置文件,符号&表示后台启动。
查看cyber模块的构建文件/apollo/cyber/BUILD,可发现如下内容:

cc_binary(name = "mainboard",srcs = ["mainboard/mainboard.cc","mainboard/module_argument.cc","mainboard/module_argument.h","mainboard/module_controller.cc","mainboard/module_controller.h",],copts = ["-pthread",],linkstatic = False,deps = [":cyber_core","//cyber/proto:dag_conf_cc_proto",],
)

至此,可执行文件mainboard的踪迹水落石出。果不其然,入口函数main位于文件cyber/mainboard/mainboard.cc中:

int main(int argc, char** argv) {google::SetUsageMessage("we use this program to load dag and run user apps.");// parse the argumentModuleArgument module_args;module_args.ParseArgument(argc, argv);// initialize cyberapollo::cyber::Init(argv[0]);// start moduleModuleController controller(module_args);if (!controller.Init()) {controller.Clear();AERROR << "module start error.";return -1;}apollo::cyber::WaitForShutdown();controller.Clear();AINFO << "exit mainboard.";return 0;
}

main函数十分简单,首先是解析参数,初始化cyber环境,接下来创建一个ModuleController类对象controller,之后调用controller.Init()启动相关功能模块。最后,一直等待cyber::WaitForShutdown()返回,清理资源并退出main函数。ModuleController::Init()函数十分简单,内部调用了ModuleController::LoadAll()函数:

bool ModuleController::LoadAll() {const std::string work_root = common::WorkRoot();const std::string current_path = common::GetCurrentPath();const std::string dag_root_path = common::GetAbsolutePath(work_root, "dag");for (auto& dag_conf : args_.GetDAGConfList()) {std::string module_path = "";if (dag_conf == common::GetFileName(dag_conf)) {// case dag conf argument var is a filenamemodule_path = common::GetAbsolutePath(dag_root_path, dag_conf);} else if (dag_conf[0] == '/') {// case dag conf argument var is an absolute pathmodule_path = dag_conf;} else {// case dag conf argument var is a relative pathmodule_path = common::GetAbsolutePath(current_path, dag_conf);if (!common::PathExists(module_path)) {module_path = common::GetAbsolutePath(work_root, dag_conf);}}AINFO << "Start initialize dag: " << module_path;if (!LoadModule(module_path)) {AERROR << "Failed to load module: " << module_path;return false;}}return true;
}

上述函数处理一个dag_conf配置文件循环,读取配置文件中的所有dag_conf,并逐一调用bool ModuleController::LoadModule(const std::string& path)函数加载功能模块:

bool ModuleController::LoadModule(const std::string& path) {DagConfig dag_config;if (!common::GetProtoFromFile(path, &dag_config)) {AERROR << "Get proto failed, file: " << path;return false;}return LoadModule(dag_config);
}

上述函数从磁盘配置文件读取配置信息,并调用bool ModuleController::LoadModule(const DagConfig& dag_config)函数加载功能模块:

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 not exist: " << load_path;return false;}class_loader_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);if (base == nullptr) {return false;}if (!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) {return false;}if (!base->Initialize(component.config())) {return false;}component_list_.emplace_back(std::move(base));}}return true;
}

上述函数看似很长,核心思想无非是调用class_loader_manager_.LoadLibrary(load_path);加载功能模块,创建并初始化功能模块类对象,并将该功能模块加入到cyber的组件列表中统一调度管理。

三、Planning模块作为Cyber组件注册并动态创建的过程

整个Planning模块的启动过程已阐述完毕,但仍有一个问题需要解决:Planning模块是如何作为Cyber的一个组件注册并动态创建的?

3.1 组件注册过程

首先看组件注册过程。注意到modules/planning/planning_component.h的组件类PlanningComponent继承自cyber::Component<prediction::PredictionObstacles, canbus::Chassis, localization::LocalizationEstimate>,里面管理着PlanningBase类对象指针(Apollo 3.5基于场景概念进行规划,目前从PlanningBase类派生出三个规划类:StdPlanning(高精地图模式)、NaviPlanning(实时相对地图模式)、OpenSpacePlanning(自由空间模式),可通过目录modules/planning/dag下的配置文件指定选用何种场景)。
同时,使用宏CYBER_REGISTER_COMPONENT(PlanningComponent)将规划组件PlanningComponent注册到Cyber的组件类管理器。查看源代码可知:

#define CYBER_REGISTER_COMPONENT(name) \CLASS_LOADER_REGISTER_CLASS(name, apollo::cyber::ComponentBase)

而后者的定义为:

#define CLASS_LOADER_REGISTER_CLASS(Derived, Base) \CLASS_LOADER_REGISTER_CLASS_INTERNAL_1(Derived, Base, __COUNTER__)

继续展开得到:

#define CLASS_LOADER_REGISTER_CLASS_INTERNAL_1(Derived, Base, UniqueID) \CLASS_LOADER_REGISTER_CLASS_INTERNAL(Derived, Base, UniqueID)

仍然需要进一步展开:

#define CLASS_LOADER_REGISTER_CLASS_INTERNAL(Derived, Base, UniqueID)         \namespace {                                                                 \struct ProxyType##UniqueID {                                                \ProxyType##UniqueID() {                                                   \apollo::cyber::class_loader::utility::RegisterClass<Derived, Base>(     \#Derived, #Base);                                                   \}                                                                         \};                                                                          \static ProxyType##UniqueID g_register_class_##UniqueID;                     \}

PlanningComponent代入上述宏,最终得到:

  namespace {                                                                 struct ProxyType__COUNTER__ {                                                ProxyType__COUNTER__() {                                                   apollo::cyber::class_loader::utility::RegisterClass<PlanningComponent, apollo::cyber::ComponentBase>( "PlanningComponent", "apollo::cyber::ComponentBase");                                                   }                                                                         };   // 通过该静态变量来触发RegisterClass函数,完成PlanningComponent对应的工厂类对象的注册                     static ProxyType__COUNTER__ g_register_class___COUNTER__;                     }

注意两点:第一,上述定义位于namespace apollo::planning内;第二,___COUNTER__是C语言的一个计数器宏,这里仅代表一个占位符,实际展开时可能就是1387之类的数字,亦即ProxyType__COUNTER__实际上应为ProxyType1387之类的命名。上述代码简洁明了,首先定义一个结构体ProxyType1387,该结构体仅包含一个构造函数,在内部调用apollo::cyber::class_loader::utility::RegisterClass<PlanningComponent, apollo::cyber::ComponentBase>注册apollo::cyber::ComponentBase类的派生类PlanningComponent。定义一个静态全局结构体对象ProxyType1387g_register_class1387,这样就能调用构造函数完成注册。
通过文本搜索可以发现,除了在此处定义了一个包含关键字g_register_class___的静态全局结构体对象外,其他没有任何地方调用这个静态全局结构体对象。为什么要定义这样一个看似多余的变量?因为我们需要调用该结构体的构造函数,再通过构造函数内部的RegisterClass函数来实现PlanningComponent对应的工厂类的注册。如果不定义这个静态全局结构体对象,那么RegisterClass函数就不会被调用,工厂类的注册也就无法完成。
继续观察apollo::cyber::class_loader::utility::RegisterClass函数:

template <typename Derived, typename Base>
void RegisterClass(const std::string& class_name,const std::string& base_class_name) {AINFO << "registerclass:" << class_name << "," << base_class_name << ","<< GetCurLoadingLibraryName();// 创建产品类Derived对应的工厂类对象指针utility::AbstractClassFactory<Base>* new_class_factrory_obj =new utility::ClassFactory<Derived, Base>(class_name, base_class_name);// 设置工厂类对象的加载器,内部是一个静态变量,初始值为空指针,会在之后加载动态库调用的LoadLibrary函数中设置正确的对象指针new_class_factrory_obj->AddOwnedClassLoader(GetCurActiveClassLoader());// 设置工厂类对象的加载库名称,内部是一个静态变量,初始值为空字符串,会在之后加载动态库调用的LoadLibrary函数中设置正确的名称new_class_factrory_obj->SetRelativeLibraryPath(GetCurLoadingLibraryName());GetClassFactoryMapMapMutex().lock();// 获取工厂映射,该映射对应的key是class_name,value是工厂对象指针ClassClassFactoryMap& factory_map =GetClassFactoryMapByBaseClass(typeid(Base).name());factory_map[class_name] = new_class_factrory_obj;GetClassFactoryMapMapMutex().unlock();
}

该函数创建一个模板类utility::ClassFactory<Derived, Base>对象new_class_factrory_obj,为其添加类加载器(new_class_factrory_obj->AddOwnedClassLoader(GetCurActiveClassLoader())),设置加载库的路径名(new_class_factrory_obj->SetRelativeLibraryPath(GetCurLoadingLibraryName())),获取工厂映射factory_map(类型为ClassClassFactoryMap,key为待创建类对象的基类名称字符串,value为创建类对象的工厂类对象指针),并将工厂类对象加入到factory_map统一管理。

GetCurActiveClassLoader()函数内部代码如下:

ClassLoader*& GetCurActiveClassLoaderReference() {static ClassLoader* loader = nullptr;return loader;
}ClassLoader* GetCurActiveClassLoader() {return (GetCurActiveClassLoaderReference());
}

可见GetCurActiveClassLoader()函数返回的是一个静态ClassLoader对象指针,该对象指针的初始值是nullptr,在其他地方一定有函数对该值进行了设值。果不其然,在SetCurActiveClassLoader()函数中进行了设值:

void SetCurActiveClassLoader(ClassLoader* loader) {ClassLoader*& loader_ref = GetCurActiveClassLoaderReference();loader_ref = loader;
}

GetCurLoadingLibraryName()函数内部代码如下:

std::string& GetCurLoadingLibraryNameReference() {static std::string library_name;return library_name;
}std::string GetCurLoadingLibraryName() {return GetCurLoadingLibraryNameReference();
}

可见GetCurLoadingLibraryName()函数返回的是一个静态std::string对象,该对象的初始值是空字符串,在其他地方一定有函数对该值进行了设值。果不其然,在SetCurLoadingLibraryName()函数中进行了设值:

void SetCurLoadingLibraryName(const std::string& library_name) {std::string& library_name_ref = GetCurLoadingLibraryNameReference();library_name_ref = library_name;
}

最终,在bool LoadLibrary(const std::string& library_path, ClassLoader* loader)函数中(位于cyber/class_loader/utility/class_loader_utility.cc文件)进行了设置类加载器和加载库路径名的调用:

bool LoadLibrary(const std::string& library_path, ClassLoader* loader) {// ...SharedLibraryPtr shared_library = nullptr;static std::recursive_mutex loader_mutex;{std::lock_guard<std::recursive_mutex> lck(loader_mutex);try {// 设置类加载器SetCurActiveClassLoader(loader);// 设置当前加载的库名称SetCurLoadingLibraryName(library_path);shared_library = SharedLibraryPtr(new SharedLibrary(library_path));} catch (const LibraryLoadException& e) {SetCurLoadingLibraryName("");SetCurActiveClassLoader(nullptr);AERROR << "LibraryLoadException: " << e.what();} catch (const LibraryAlreadyLoadedException& e) {SetCurLoadingLibraryName("");SetCurActiveClassLoader(nullptr);AERROR << "LibraryAlreadyLoadedException: " << e.what();} catch (const SymbolNotFoundException& e) {SetCurLoadingLibraryName("");SetCurActiveClassLoader(nullptr);AERROR << "SymbolNotFoundException: " << e.what();}SetCurLoadingLibraryName("");SetCurActiveClassLoader(nullptr);}// ...return true;
}

GetClassFactoryMapByBaseClass()函数内部代码如下:

using ClassClassFactoryMap =std::map<std::string, utility::AbstractClassFactoryBase*>;
using BaseToClassFactoryMapMap = std::map<std::string, ClassClassFactoryMap>;ClassClassFactoryMap& GetClassFactoryMapByBaseClass(const std::string& typeid_base_class_name) {BaseToClassFactoryMapMap& factoryMapMap = GetClassFactoryMapMap();std::string base_class_name = typeid_base_class_name;if (factoryMapMap.find(base_class_name) == factoryMapMap.end()) {factoryMapMap[base_class_name] = ClassClassFactoryMap();}return factoryMapMap[base_class_name];
}

BaseToClassFactoryMapMap是一个映射,key为待创建对象的基类名称字符串,value为ClassClassFactoryMap对象;ClassClassFactoryMap也是一个映射,key为待创建对象的自身名称字符串,value为待创建对象对应的抽象工厂基类对象指针。GetClassFactoryMapByBaseClass()函数的作用是返回待创建对象对应的ClassClassFactoryMap对象,以便将当前的工厂对象指针塞进映射中。

通过apollo::cyber::class_loader::utility::RegisterClass函数,我们可以清楚地看到,Cyber RT不直接创建产品类对象(例如PlanningComponent),而是使用工厂方法模式来完成产品类对象的创建,至于如何调用工厂对象创建PlanningComponent对象,这是下一节要讨论的内容。

3.2 动态创建过程

根据第二节内容,功能模块类PlanningComponent对象在bool ModuleController::LoadModule(const DagConfig& dag_config)函数内部创建:

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;// ...class_loader_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);if (base == nullptr) {return false;}if (!base->Initialize(component.config())) {return false;}component_list_.emplace_back(std::move(base));}// ...}return true;
}

已经知道,PlanningComponent对象是通过class_loader_manager_.CreateClassObj<ComponentBase>(class_name)创建出来的,而class_loader_manager_是一个class_loader::ClassLoaderManager类对象。现在的问题是:class_loader::ClassLoaderManager与3.1节中的工厂类utility::AbstractClassFactory<Base>如何联系起来的?
先看ClassLoaderManager::CreateClassObj函数(位于文件cyber/class_loader/class_loader_manager.h中):

template <typename Base>
std::shared_ptr<Base> ClassLoaderManager::CreateClassObj(const std::string& class_name) {std::vector<ClassLoader*> class_loaders = GetAllValidClassLoaders();for (auto class_loader : class_loaders) {if (class_loader->IsClassValid<Base>(class_name)) {return (class_loader->CreateClassObj<Base>(class_name));}}AERROR << "Invalid class name: " << class_name;return std::shared_ptr<Base>();
}

上述函数中,从所有class_loaders中找出一个正确的class_loader,并调用class_loader->CreateClassObj<Base>(class_name)(位于文件cyber/class_loader/class_loader.h中)创建功能模块组件类对象:

template <typename Base>
std::shared_ptr<Base> ClassLoader::CreateClassObj(const std::string& class_name) {if (!IsLibraryLoaded()) {LoadLibrary();}Base* class_object = utility::CreateClassObj<Base>(class_name, this);if (nullptr == class_object) {AWARN << "CreateClassObj failed, ensure class has been registered. "<< "classname: " << class_name << ",lib: " << GetLibraryPath();return std::shared_ptr<Base>();}std::lock_guard<std::mutex> lck(classobj_ref_count_mutex_);classobj_ref_count_ = classobj_ref_count_ + 1;std::shared_ptr<Base> classObjSharePtr(class_object, std::bind(&ClassLoader::OnClassObjDeleter<Base>, this,std::placeholders::_1));return classObjSharePtr;
}

上述函数继续调用utility::CreateClassObj<Base>(class_name, this)(位于文件cyber/class_loader/utility/class_loader_utility.h中)创建功能模块组件类对象:

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 = dynamic_cast<utility::AbstractClassFactory<Base>*>( factoryMap[class_name]);获取对应的工厂对象指针,至此终于将class_loader::ClassLoaderManager与3.1节中的工厂类utility::AbstractClassFactory<Base>联系起来了。工厂对象指针找到后,使用classobj = factory->CreateObj();就顺理成章地将功能模块类对象创建出来了。

Apollo 3.5 各功能模块的启动过程解析相关推荐

  1. Winsows VISTA启动过程解析

    Winsows VISTA启动过程解析 开启电源--       计算机系统将进行加电自检(POST).如果通过,之后BIOS会读取主引导记录(MBR)--被标记为启动设备的硬盘的首扇区,并传送被Wi ...

  2. STM32启动过程解析-2.02固件库启动文件分析

    源:STM32启动过程解析-2.02固件库启动文件分析 转载于:https://www.cnblogs.com/LittleTiger/p/9205372.html

  3. S5PV210之UBOOT-2011.06启动过程解析-基于u-boot for tiny210 ver3.1 (by liukun321咕唧咕唧)

    //主题:S5PV210之UBOOT-2011.06启动过程解析 //作者:kevinjz2010@gmail.com //版权:kevinjz原创 //平台:S5PV210 ARMV7 TINY21 ...

  4. laravel的启动过程解析(转)

    转载地址:https://www.cnblogs.com/lpfuture/p/5578274.html 如果没有使用过类似Yii之类的框架,直接去看laravel,会有点一脸迷糊的感觉,起码我是这样 ...

  5. Java虚拟机启动过程解析

    一.序言 当我们在编写Java应用的时候,很少会注意Java程序是如何被运行的,如何被操作系统管理和调度的.带着好奇心,探索一下Java虚拟机启动过程. 1.素材准备 从 Java源代码 . Java ...

  6. Android系统启动流程(一) init进程启动过程解析

    init进程是Android系统第一个用户态的进程,init被赋予了很多重要的职责,比如我们熟悉的Zygote孵化器进程就是由init进程启动的.今天我们就来分析init进程的启动过程. 1 init ...

  7. Android系统启动流程(四)Launcher进程启动过程解析(附带面试题)

    前面我们分析了init进程,zygote进程,SystemServer进程,本篇的Launcher是系统启动流程的最后一个进程. 1 Launcher概述 Launcher进程是一个系统的应用程序,位 ...

  8. Linux启动过程基本指南

    每次你上电你的Linux PC,在最终显示一个提示你输入用户名和密码的登录窗口前,它经过了一系列阶段.每钟Linux发行版在一般启动过程中经过了4个不同阶段. 用户登录提示 在这里,我们将重点突出Li ...

  9. openfire linux 启动,Openfire的启动过程与session管理

    说明 本文源码基于Openfire4.0.2. Openfire的启动 Openfire的启动过程非常的简单,通过一个入口初始化lib目录下的openfire.jar包,并启动一个XMPPServer ...

最新文章

  1. 在SQLserver数据库里设置作业的步骤
  2. SQL Server2008及以上 表分区操作详解
  3. 如何在企业推行OKR?
  4. java的知识点45——事务||测试时间处理(java.sql.date,time,timestamp)
  5. Final Cut Pro模版网站
  6. ImageJ Nikon_科研人必备图像处理软件—ImageJ软件分享
  7. 部署eureka和config
  8. Android 中SharedPreferences 使用
  9. 其实没有啥好说的公司组织去清远漂流
  10. 关于Arduino 步进电机Stepper库的一些想法
  11. git add 撤销_Git系列——Git添加提交(add)和查看状态(status)
  12. 问题:lapack.so
  13. MES系统源码 MES系统功能介绍
  14. 元宇宙游戏控制这几个因素,正确姿势解锁元宇宙游戏开发盈利痛点
  15. win10之缺少一个或者多个协议(只能上qq不能上网)
  16. 选择高防空间应该注意什么?
  17. AWD准备的一些脚本和工具及其使用方法
  18. asii和unicode格式字符串之间的相互转换
  19. ZooKeeper原理和实践
  20. vscode配置并运行swift

热门文章

  1. 使用Oracle VM Virtual BOX安装Solaris11.4和图形化界面
  2. mysql数据库表卡死怎么办
  3. 一文详解激光雷达原理之光学原理
  4. 奇遇网络,谈谈我三年建站的经历
  5. Alink在线学习(Online Learning)之Java示例【三】
  6. 光纤位移传感器工作原理
  7. SQL:计算某列累加合计
  8. 升级jenkins 导致jenkins启动失败_害你加班的Bug是我写的,记一次升级Jenkins插件引发的加班
  9. android wifi与连接设备通讯录,WiFi Direct设备与其他Android设备的连接
  10. TCPDF 文档尺寸标准