【ROS】源码分析-roscore与Master启动
本文从源码层面分析ros Master是如何启动的,以及Master(服务注册和发布、参数服务)到底是什么高大尚的东东。
说明
本文从源码层面分析ros Master是如何启动的,以及Master(服务注册和发布、参数服务)到底是什么高大尚的东东。
Node启动过程分析以小海龟为例,操作步骤如下
- S1: 启动 roscore
- S2: 启动 turtlesim_node
- S3: 启动 turtlesim_teleop_key
这几个操作过程中master、publisher、subscriber到底发生了什么?
源码目录
- ros_comm/tools : ROS工具
roslaunch
、rosmaster
等 - ros_tutorials/turtlsim:小海龟的PubNode和SubNode。
源码分析
S1: 启动roscore (MasterNode)
此命令对应的可执行文件是/opt/ros/noetic/bin/roscore
, 这是一个python脚本,主要做了2个事情:
- 解析roscore入参
- 调用
roslaunch.main(['roscore', '--core'] + sys.argv[1:])
import roslaunch
roslaunch.main(['roscore', '--core'] + sys.argv[1:])
进入启动入口 roslanch.main() 方法: 创建ROSLaunchParent并start
(源码文件:ros_comm/tools/roslaunch/src/roslaunch/_init_.py)
p = roslaunch_parent.ROSLaunchParent(uuid, args, roslaunch_strs=roslaunch_strs,
is_core=options.core, port=options.port, local_only=options.local_only,
verbose=options.verbose, force_screen=options.force_screen,
force_log=options.force_log,
num_workers=options.num_workers, timeout=options.timeout,
master_logger_level=options.master_logger_level,
show_summary=not options.no_summary,
force_required=options.force_required,
sigint_timeout=options.sigint_timeout,
sigterm_timeout=options.sigterm_timeout)
p.start()
p.spin()
进入ROSLaunchParent
的start
方法实现: 启动launchServer
(pm是ProcessMonitor
进程管理组件)和ROSLaunchRunner
(源码文件:ros_comm/tools/roslaunch/src/roslaunch/parent.py)
self.server = roslaunch.server.ROSLaunchParentNode(self.config, self.pm)
self.server.start()
self.runner = roslaunch.launch.ROSLaunchRunner(self.run_id, self.config, server_uri=self.server.uri, pmon=self.pm, is_core=self.is_core,
remote_runner=self.remote_runner, is_rostest=self.is_rostest, num_workers=self.num_workers, timeout=self.timeout, master_logger_level=self.master_logger_level,
sigint_timeout=self.sigint_timeout, sigterm_timeout=self.sigterm_timeout)
self.runner.launch()
进入ROSLaunchRunner
的launch
方法实现: 这里才真正开始创建Master
进程
(源码文件:ros_comm/tools/roslaunch/src/roslaunch/launch.py)
def launch(self):
launched = self._launch_master()
if launched:
self._launch_core_nodes()
self._launch_nodes()
def _launch_master(self):
p = create_master_process(
self.run_id, m.type, get_ros_root(), m.get_port(), self.num_workers,
self.timeout, master_logger_level=self.master_logger_level,
sigint_timeout=self.sigint_timeout, sigterm_timeout=self.sigterm_timeout)
self.pm.register_core_proc(p) # 这里会提交进程给pm,然后进行启动
success = p.start() # 这里实际是`LocalProcess.start()`
看一下create_master_process
是如何创建master进程的: 创建了一个LocalProcess
实例,然后启动LocalProcess.start()
(源码文件:ros_comm/tools/roslaunch/src/roslaunch/nodeprocessor.py)
def create_master_process():
if type_ in [Master.ROSMASTER, Master.ZENMASTER]:
package = 'rosmaster'
args = [master, '--core', '-p', str(port), '-w', str(num_workers)]
if timeout is not None:
args += ['-t', str(timeout)]
if master_logger_level:
args += ['--master-logger-level', str(master_logger_level)]
else:
raise RLException("unknown master typ_: %s"%type_)
_logger.info("process[master]: launching with args [%s]"%args)
log_output = False
return LocalProcess(run_id, package, 'master', args, os.environ, log_output, None, required=True,
sigint_timeout=sigint_timeout, sigterm_timeout=sigterm_timeout)
上面代码中的一些关键变量值:
- package=“rosmaster”
- args[0](master)=“rosmaster” # 即 Master.ROSMASTER
- type_=Master.ROSMASTER
然后看一下真是的进程是如何启动的class LocalProcess(Process).start
(源码文件:ros_comm/tools/roslaunch/src/roslaunch/nodeprocessor.py)
class LocalProcess(Process):
super(LocalProcess, self).start()
self.popen = subprocess.Popen(self.args, cwd=cwd, stdout=logfileout, stderr=logfileerr, env=full_env, close_fds=close_file_descriptor, preexec_fn=preexec_function)
subprocess.Popen 真实的启动了一个子进程,其中args变量从上面create_master_process
得知进程可执行的命令(文件)是rosmaster
self.args=["rosmaster", "--core", "-p", str(port), '-w', str(num_workers)]
所以这个子进程的命令文件是rosmaster
,即源码中的ros_comm/tools/rosmaster/script/rosmaster
,这是一个python脚本文件, 非常简洁的调用 rosmaster.rosmaster_main()
import rosmaster
rosmaster.rosmaster_main()
(源码文件:ros_comm/tools/rosmaster/src/rosmaster/main.py)
def rosmaster_main(argv=sys.argv, stdout=sys.stdout, env=os.environ):
master = rosmaster.master.Master(port, options.num_workers)
master.start()
(源码文件:ros_comm/tools/rosmaster/src/rosmaster/master.py)
class Master(object):
def start(self):
handler = rosmaster.master_api.ROSMasterHandler(self.num_workers)
master_node = rosgraph.xmlrpc.XmlRpcNode(self.port, handler)
master_node.start()
(源码文件:ros_comm/tools/rosgraph/src/rosgraph/xmlrpc.py)
class XmlRpcNode(object):
def start(self):
#这里启动了一个线程
_thread.start_new_thread(self.run, ())
def run(self):
self.server = ThreadingXMLRPCServer((bind_address, port), log_requests)
self.server.serve_forever()
class ThreadingXMLRPCServer(socketserver.ThreadingMixIn, SimpleXMLRPCServer):
所以master进程使用python的SimpleXMLRPCServer
启动了http服务器,对应的服务处理是rosmaster.master_api.ROSMasterHandler
,这里就是大家开发中经常调用的几个方法(这几个方法如何对应到常用的advertise
、subscribe
等方法在后文中会详细讲解)
class ROSMasterHandler(object):
def registerService(self, caller_id, service, service_api, caller_api):
def registerSubscriber(self, caller_id, topic, topic_type, caller_api):
def searchParam(self, caller_id, key):
def getParam(self, caller_id, key):
补充一点:master服务是如何被发现的?
答:是master约定了一个默认的端口(源码文件:ros_comm/tools/rosmaster/src/rosmaster/master.py) DEFAULT_MASTER_PORT=11311 #default port for master's to bind to
到此master已启动,然后大家在控制台就看到了数学的页面
S2: 启动 turtlesim_node/S3: 启动 turtlesim_teleop_key
因为这2个Node启动过程是一样的,所以放到一起分析,先看turtlesim_teleop_key
源码:ros_tutorials/turlesim/tutorials/teleop_turtle_key.cpp
,这里定义了一个Node,即ros::Publisher twist_pub_;
然后发布Topic声明turtle1/cmd_vel
,数据类型是geometry_msgs::Twist
,队列大小是1
。
然后在keyloop方法中不断读取键盘方向键的输入,并转换为线速度或角速度
- LEFT: 角速度=1.0 (弧度)
- RIGHT: 角速度=-1.0 (弧度)
- UP: 线速度=1.0
- DOWN: 线速度=-1.0
最后将线速度(x)和角速度(z)信息发布(publish)给订阅者(即turlesim_node)。
turtlesim_node 订阅topic,并更新角速度和线速度
为什么subscribe的topic并没有写
turtle1/cmd_vel
? 这与ROS的命名空间、Node命名空间、NodeName相关,具体参考这里,然后查看代码turtle_frame.cpp
中的方法spawnTurtle
应该就能命名了。
Master启动汇总
roscore脚本调用launch主方法,launch是主进程,然后启动子进程master,master启动HTTP服务。
- launch:是使用python编写实现的一个工具,是roscore启动的主进程;
- master: 是使用python编写实现的一个HTTP服务,属于launch的一个子进程;
- 参数服务:使用字典类型的内存对象来保存;
- Topic发布订阅信息:使用字典类型的内存对象来保存;
- 服务通讯:使用python的
SimpleXMLRPCServer
启动了http服务器(默认端口号11311
),以接受PubNode&SubNode的服务注册和参数服务处理,具体的master所有提供的功能实现在rosmaster.master_api.ROSMasterHandler
。
Master中的参数服务、Topic订阅和注册信息都是使用最基本的
字典
内存对象维护
self.parameters = {}
.## { key: [(caller_id, caller_api)] }
self.map = {}
self.service_api_map = None
更多推荐
所有评论(0)