Apollo的启动过程1——启动命令解析

1、构建Apollo

在运行系统之前,必须先构建系统。请注意,bootstrap.sh实际上会成功,但是如果跳过构建步骤,则不会显示用户界面。以下步骤需要在apollo/目录下运行:

1.1、进入容器

1
2
bash docker/scripts/dev_start.sh     //启动container
bash docker/scripts/dev_into.sh //进入container

1.2、编译整个系统

1
2
3
4
5
6
# To get a list of build commands
./apollo.sh
# To make sure you start clean
./apollo.sh clean
# This will build the full system and requires that you have an nVidia GPU with nVidia drivers loaded
bash apollo.sh build

2、启动命令分析

面控制。而Dreamview的界面内容根选项是由modules/dreamview/conf决定。Dreamview中提供了不同的HMI(human machine interface)模式,不同模式的可选功能模块不一。

1
bash scripts/bootstrip.sh start    #启动Dreamview

在浏览器里登录网址http://localhost:8888/,就可以看到dreamview的界面:

上述命令实际执行了scripts/bootstrap.sh中的start()函数:(Linux下的shell脚本)

上述命令实际执行了scripts/bootstrap.sh中的start()函数:(Linux下的shell脚本)

1
2
3
4
5
6
7
8
9
10
11
12
13
dfunction start() {
./scripts/monitor.sh start
./scripts/dreamview.sh start
if [ $? -eq 0 ]; then
http_status="$(curl -o -I -L -s -w '%{http_code}' ${DREAMVIEW_URL})"
if [ $http_status -eq 200 ]; then
echo "Dreamview is running at" $DREAMVIEW_URL
else
echo "Failed to start Dreamview. Please check /apollo/data/log or /apollo/data/core for more information"
fi
fi
}
#case $1 in ...

start()函数内部分别调用脚本文件scripts/monitor.shscripts/dreamview.sh内部的start()函数启动monitor与dreamview模块。scripts/dreamview.sh文件的内容:

1
2
3
4
5
6
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(命令行参数)

里面没有start()函数,但是这里包含一个apollo_bash.sh脚本,和一条调用语句run dreamview start;断定run()函数存在于apollo_bash.sh脚本中,内容如下:

1
2
3
4
5
function run() {
local module=$1
shift
run_customized_path $module $module "$@"
}

其中module = dreamview$@ = start。继续查看run_customized_path函数:

1
2
3
4
5
6
7
8
9
10
11
1function run_customized_path() {
local module_path=$1
local module=$2
local cmd=$3
shift 3
case $cmd in
start)
start_customized_path $module_path $module "$@"
;;
# ...
}

实际调用的是start_customized_path dreamview dreamview,再来探究此函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function start_customized_path() {
MODULE_PATH=$1
MODULE=$2
shift 2

is_stopped_customized_path "${MODULE_PATH}" "${MODULE}"
if [ $? -eq 1 ]; then
eval "nohup cyber_launch start /apollo/modules/${MODULE_PATH}/launch/${MODULE}.launch &"
sleep 0.5
is_stopped_customized_path "${MODULE_PATH}" "${MODULE}"
if [ $? -eq 0 ]; then
echo "Launched module ${MODULE}."
return 0
else
echo "Could not launch module ${MODULE}. Is it already built?"
return 1
fi
else
echo "Module ${MODULE} is already running - skipping."
return 2
fi
}

在函数内部,调用is_stopped_customed_path()函数来判断dreamview模块是否启动。若未启动则通过,nohup cyber_launch start /apollo/modules/dreamview/launch/dreamview.launch &指令以后台非挂断方式启动该模块。其中,cyber_launch是Cyber RT平台提供的一个python工具,其完整路径为${APOLLO_HOME}/cyber/tools/cyber_launch/cyber_launch。主函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def main():
"""
Main function
"""
# 在source scripts/apollo_base.sh时候会调用source cyber/setup.sh,进行自动是设置
if cyber_path is None:
logger.error(
'Error: environment variable CYBER_PATH not found, set environment first.')
sys.exit(1)
# 改变工作目录到当前目录cyber_path下(cyber/)
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:])
# 调用start()启动launch文件
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()函数启动dreamview的module模块。继续查看start()函数,此函数的主要功能是解析XML文件/apollo/modules/dreamview/launch/dreamview.launch这其中涉及到了启动文件(.launch)文件如何编写。然后调用ProcessWrapper(process_name.split()[0], 0, [""], process_name, process_type, exception_handler)创建一个ProcessWrapper对象pw,然后调用pw.start()函数启动dreamview模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def start(launch_file = ''):
# ...
process_list = []
root = tree.getroot()
for env in root.findall('environment'):
for var in env.getchildren():
os.environ[var.tag] = str(var.text)
# 对应.launch文件的编写规则
for module in root.findall('module'):
module_name = module.find('name').text
dag_conf = module.find('dag_conf').text
process_name = module.find('process_name').text
sched_name = module.find('sched_name')
# 分为binary与library,不设定默认为binary
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')
continue
# <type>是binary时的启动程序(只看到dreamview与v2x的type是binary,无dag文件)
# 此时传入的是可执行文件的路径(process_name.split()[0])
pw = ProcessWrapper(process_name.split()[0], 0, [""], process_name, process_type, exception_handler)
# default is library
else:
# <type>是library时的binary_path是mainboard
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 xml
if not process_list:
logger.error("No module was found in xml config.")
return
all_died = pmon.run()
if not all_died:
logger.info("Stop all processes...")
stop()
logger.info("Cyber exit.")

查看ProcessWrapperl类中的start()函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def start(self):
"""
start a manager in process name
"""
# <type>是binary时的启动arg_list
if self.process_type == 'binary':
args_list = self.name.split()
# <type>是library(实际上是非binary)时的启动arg_list
# mainboard -d <daf_files> -p <process_name> -s <sched_name>
else:
args_list = [self.binary_path, '-d'] + self.dag_list
if 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_list
# 产生子进程
try:
self.popen = subprocess.Popen(args_list, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
except Exception, e:
logger.error('Subprocess Popen exception: ' + str(e))
return 2
else:
if self.popen.pid == 0 or self.popen.returncode is not None:
logger.error('Start process [%s] failed.' % self.name)
return 2

th = threading.Thread(target=module_monitor, args=(self, ))
th.setDaemon(True)
th.start()
self.started = True
self.pid = self.popen.pid
logger.info('Start process [%s] successfully. pid: %d' % (self.name, self.popen.pid))
logger.info('-' * 120)
return 0

当.launch文件中的标签是binary时候(例如dreamview模块),在该函数内部调用/apollo/bazel-bin/modules/dreamview/dreamview --flagfile=/apollo/modules/common/data/global_flagfile.txt最终启动了dreamview进程。当标签是默认的library时,在该函数内部调用mainboard -d <daf_files> -p <process_name> -s <sched_name>启动对应进程。dreamview进程的main函数位于/apollo/modules/dreamview/backend/main.cc中,内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main(int argc, char *argv[]) {
google::ParseCommandLineFlags(&argc, &argv, true);
// add by caros for dv performance improve
apollo::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函数。

参考文章

https://blog.csdn.net/weixin_44450715/article/details/86593962

https://blog.csdn.net/davidhopper/article/details/85248799

听说打赏我的人,最后都找到了真爱。