使用Systemd设置ROS 2节点开机自启

梦想不抛弃苦心追求的人,只要不停止追求,你们会沐浴在梦想的光辉之中。再美好的梦想与目标,再完美的计划和方案,如果不能尽快在行动中落实,最终只能是纸上谈兵,空想一番。只要瞄准了大方向,坚持不懈地做下去,才能够扫除挡在梦想前面的障碍,实现美好的人生蓝图。使用Systemd设置ROS 2节点开机自启,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文


http://www.jinbuguo.com/systemd/systemd.html#

Systemd 是 Linux 系统工具,用来启动守护进程。pm2、supervisor等也是守护进程工具,但不是系统工具。

下图为Systemd架构图:在这里插入图片描述

参考资料

systemd 中文手册(金步国)
Linux 守护进程的启动方法(阮一峰)
Systemd 入门教程:命令篇( 阮一峰)
Systemd 入门教程:实战篇( 阮一峰)
journalctl 中文手册(金步国)

ROS 2节点开机自启模板

在vscode中编辑时推荐安装 Systemd Helper 扩展
在这里插入图片描述
更多通用模板见http://www.jinbuguo.com/systemd/systemd.service.html#%E4%BE%8B%E5%AD%90

[Unit]
Description=服务描述
Before=设置服务启动先后顺序
After=设置服务启动先后顺序

[Service]
Environment="ROS_HOME=你的代码路径"
Type=simple(最简单的类型)
User=进程在执行时使用的用户
ExecStart=开启服务后需要执行的命令
ExecStop=关闭服务后需要执行的命令,例如 /bin/kill -s INT $MAINPID
KillSignal=设置杀死进程时使用什么信号
RestartSec=服务重启间隔时间
Restart=服务是否重启

[Install]
WantedBy=multi-user.target

例如写一个/usr/lib/systemd/system/xxx.service(必须在这个路径下写,因为enable的时候systemd会在/etc/systemd/system中建立名为xxx.service的软链接,链接到这个目录的xxx.service,这样才会开机自启这个服务),让这个服务在ExecStart中执行启动一个脚本的命令,然后在脚本里面再去写执行启动各个节点的命令,这样可以实现批量开启节点和关闭节点,在Restart=always的情况下还能保证任何一个节点退出后都能重启。

脚本写法举例如下,可能还需要设置一些环境变量,见http://wiki.ros.org/action/fullsearch/ROS/EnvironmentVariables

#! /bin/bash

source /opt/ros/humble/setup.bash
source 你的代码中setup.bash的路径

然后下面就写启动各个节点需要的命令
如果有些命令需要sudo,可以通过管道的方式输入密码:echo "密码" | sudo -S 命令。
例如 echo "a" | sudo -S chmod 777 /dev/ttyUSB0

在这里插入图片描述

示例

某机器人需要运行多个ros2程序,这些程序有些是拿.sh启动,有些是拿.py启动,启动时还会调用.launch来启动,关闭时使用SIGTERM又很难关掉,并且因为频繁修改调试,需要快捷地安装进系统,这些文件内容如下:

others.service

[Unit]
Description=others
Before=navigation.service deep.service

[Service]
Environment="ROS_HOME=/home/sentry/Sentry2023"
Type=simple
User=sentry
ExecStart=/usr/bin/python3 /home/sentry/Sentry2023/start_others.py
ExecStop=/bin/kill -s INT $MAINPID
ExecReload=/bin/kill -s INT $MAINPID && /usr/bin/python3 /home/sentry/Sentry2023/start_others.py
KillSignal = SIGKILL
RestartSec=2
Restart=always
StartLimitInterval=0

[Install]
WantedBy=multi-user.target

deep.service

[Unit]
Description=deep detector
After=others.service

[Service]
Environment="ROS_HOME=/home/sentry/Sentry2023"
Type=simple
User=sentry
ExecStart=/home/sentry/Sentry2023/start_deep.sh
ExecStop=/bin/kill -s INT $MAINPID
ExecReload=/bin/kill -s INT $MAINPID && /home/sentry/Sentry2023/start_deep.sh
KillSignal=SIGKILL
RestartSec=2
Restart=always

[Install]
WantedBy=multi-user.target

navigation.service

[Unit]
Description=navigation
After=others.service

[Service]
Environment="ROS_HOME=/home/sentry/Sentry2023"
Type=simple
User=sentry
ExecStart=/home/sentry/Sentry2023/start_navigation.sh
ExecStop=/bin/kill -s INT $MAINPID
ExecReload=/bin/kill -s INT $MAINPID && /home/sentry/Sentry2023/start_navigation.sh
KillSignal = SIGKILL
RestartSec=2
Restart=always

[Install]
WantedBy=multi-user.target

record_topic.service

[Unit]
Description=record topic
After=navigation.service

[Service]
Environment="ROS_HOME=/home/dino/Sentry2023"
Type=simple
User=dino
ExecStart=/home/dino/Sentry2023/start_record_topic.sh
KillSignal=SIGINT
RestartSec=2
Restart=always

[Install]
WantedBy=multi-user.target

start_others.py

import os
import subprocess as sp
from time import sleep

source_cmd = ". install/setup.sh"
start_control_cmd = "ros2 launch control control.launch.py"
start_scan_cmd = "ros2 run scan scan"
start_behaviortree_cmd = "ros2 run behavior_tree behaviortree"
andand=" && "

current_path = "/home/sentry/Sentry2023"

# 链接U转can
ip_link_cmd = "echo 'a' | sudo -S ip link set can0 down && sudo ip link set can0 type can bitrate 1000000 && sudo ip link set can0 up"
result = sp.Popen(ip_link_cmd, shell=True, stdout=sp.PIPE, stderr=sp.PIPE)
out, err= result.communicate()
for line in err.splitlines():
    if("Cannot find device" in str(line)):
        print("无法打开U转can, error=\n  " + str(line)[2:-1])
        exit()

# 打开节点
control = sp.Popen(args=source_cmd + andand + start_control_cmd, cwd=current_path, shell=True)
scan = sp.Popen(args=source_cmd + andand + start_scan_cmd, cwd=current_path, shell=True)
behaviortree = sp.Popen(args=source_cmd + andand + start_behaviortree_cmd, cwd=current_path, shell=True)

while True:
    sleep(10)


start_deep.sh

#!/bin/zsh

cd /home/sentry/Sentry2023
. install/setup.zsh
ros2 launch deepdetector deepdetector.launch.py

start_navigation.sh

#!/bin/zsh

cd /home/sentry/Sentry2023
. install/setup.zsh
ros2 launch navigation start_navigation.launch.py
#if [ -c /dev/ttyUSB0 ];then
#    echo 'a' | sudo -S chmod 777 /dev/ttyUSB0
#    . install/setup.zsh
#    ros2 launch navigation start_navigation.launch.py
#else
#    while true
#    do
#        echo "no device /dev/ttyUSB0"
#        sleep 1s
#    done
#fi

start_record.sh

#!/bin/zsh

cd /home/sentry/Sentry2023
. install/setup.zsh
current_time=`date +'%F_%H:%M:%S'`
ros2 bag record -o ${current_time} /Game /scan /ShootSome /SetGimbalAngle /SetGimbal_YawSpeed_PitchAngle /GetGimbalSpeed /BT_top /BT_navigation /ScanCtrlInfo /BT_shooter /Armors /tf /local_costmap/costmap /path /map

installservice.sh

#!/bin/zsh

if [ ! -d /usr/lib/systemd/system ];then
    mkdir /usr/lib/systemd/system
fi

echo 'a' | sudo -S cp src/bring_up/scripts/*.service /usr/lib/systemd/system

echo 'a' | sudo -S systemctl daemon-reload

echo 'a' | sudo -S systemctl disable navigation.service
echo 'a' | sudo -S systemctl disable others.service
echo 'a' | sudo -S systemctl disable deep.service
echo 'a' | sudo -S systemctl disable record_topic.service

echo 'a' | sudo -S systemctl enable navigation.service
echo 'a' | sudo -S systemctl enable others.service
echo 'a' | sudo -S systemctl enable deep.service
# echo 'a' | sudo -S systemctl enable record_topic.service

Systemd常用命令

启动服务

systemctl start xxx.service

关闭服务

systemctl stop xxx.service

重启服务

systemctl restart xxx.service

查看服务状态

systemctl status xxx.service

设置服务开机自启

systemctl enable xxx.service

查看服务日志(节点的输出都会保存到日志中,默认保存在/var/log/journal

journalctl -u xxx.service

删除日志

sudo rm -rf /var/log/journal/日志文件夹
sudo systemctl restart systemd-journald.service

参数解释

本文只给出简略解释,具体说明见文中给出的链接。

[Unit]

http://www.jinbuguo.com/systemd/systemd.unit.html#%5BUnit%5D%20%E5%B0%8F%E8%8A%82%E9%80%89%E9%A1%B9

Description

http://www.jinbuguo.com/systemd/systemd.unit.html#Description=

对单元进行简单描述的字符串。

Before, After

http://www.jinbuguo.com/systemd/systemd.unit.html#Before=

强制指定各service的启动顺序。

如果要启动多个节点,并且节点之间需要有启动顺序,就需要用before和after。

       假定 foo.service 单元包含 Before=bar.service 设置, 那么当两个单元都需要启动的时候, bar.service 将会一直延迟到 foo.service 启动完毕之后再启动。

       注意,停止顺序与启动顺序正好相反,也就是说, 只有当 bar.service 完全停止后,才会停止 foo.service 单元。

       After= 的含义与 Before= 正好相反。 假定 foo.service 单元包含 After=bar.service 设置, 那么当两个单元都需要启动的时候, foo.service 将会一直延迟到 bar.service 启动完毕之后再启动。 注意,停止顺序与启动顺序正好相反,也就是说, 只有当 foo.service 完全停止后,才会停止 bar.service 单元。

[Service]

http://www.jinbuguo.com/systemd/systemd.service.html#%E9%80%89%E9%A1%B9

Environment

http://www.jinbuguo.com/systemd/systemd.exec.html#Environment=
设置进程的环境变量, 接受一个空格分隔的 VAR=VALUE 列表。 可以多次使用此选项以增加新的变量或者修改已有的变量(同一个变量以最后一次设置为准)。 设为空表示清空先前所有已设置的变量。 注意: (1)不会在字符串内部进行变量展开(也就是”$“没有特殊含义); (2)如果值中包含空格或者等号,那么必须在字符串两边使用双引号(”)界定。

Type

http://www.jinbuguo.com/systemd/systemd.service.html#Type=

设置进程的启动类型。必须设为 simple, exec, forking, oneshot, dbus, notify, idle 之一。

  • 如果设为 simple (当设置了 ExecStart= 、 但是没有设置 Type= 与 BusName= 时,这是默认值), 那么 ExecStart= 进程就是该服务的主进程, 并且 systemd 会认为在创建了该服务的主服务进程之后,该服务就已经启动完成。 如果此进程需要为系统中的其他进程提供服务, 那么必须在该服务启动之前先建立好通信渠道(例如套接字), 这样,在创建主服务进程之后、执行主服务进程之前,即可启动后继单元, 从而加快了后继单元的启动速度。 这就意味着对于 simple 类型的服务来说, 即使不能成功调用主服务进程(例如 User= 不存在、或者二进制可执行文件不存在), systemctl start 也仍然会执行成功。

  • exec 与 simple 类似,不同之处在于, 只有在该服务的主服务进程执行完成之后,systemd 才会认为该服务启动完成。 其他后继单元必须一直阻塞到这个时间点之后才能继续启动。换句话说, simple 表示当 fork() 函数返回时,即算是启动完成,而 exec 则表示仅在 fork() 与 execve() 函数都执行成功时,才算是启动完成。 这就意味着对于 exec 类型的服务来说, 如果不能成功调用主服务进程(例如 User= 不存在、或者二进制可执行文件不存在), 那么 systemctl start 将会执行失败。

  • 如果设为 forking ,那么表示 ExecStart= 进程将会在启动过程中使用 fork() 系统调用。 也就是当所有通信渠道都已建好、启动亦已成功之后,父进程将会退出,而子进程将作为主服务进程继续运行。 这是传统UNIX守护进程的经典做法。 在这种情况下,systemd 会认为在父进程退出之后,该服务就已经启动完成。 如果使用了此种类型,那么建议同时设置 PIDFile= 选项,以帮助 systemd 准确可靠的定位该服务的主进程。 systemd 将会在父进程退出之后 立即开始启动后继单元。

  • oneshot 与 simple 类似,不同之处在于, 只有在该服务的主服务进程退出之后,systemd 才会认为该服务启动完成,才会开始启动后继单元。 此种类型的服务通常需要设置 RemainAfterExit= 选项。 当 Type= 与 ExecStart= 都没有设置时, Type=oneshot 就是默认值。

  • dbus 与 simple 类似,不同之处在于, 该服务只有获得了 BusName= 指定的 D-Bus 名称之后,systemd 才会认为该服务启动完成,才会开始启动后继单元。 设为此类型相当于隐含的依赖于 dbus.socket 单元。 当设置了 BusName= 时, 此类型就是默认值。

  • notify 与 exec 类似,不同之处在于, 该服务将会在启动完成之后通过 sd_notify(3) 之类的接口发送一个通知消息。systemd 将会在启动后继单元之前, 首先确保该进程已经成功的发送了这个消息。如果设为此类型,那么下文的 NotifyAccess= 将只能设为非 none 值。如果未设置 NotifyAccess= 选项、或者已经被明确设为 none ,那么将会被自动强制修改为 main 。注意,目前 Type=notify 尚不能与 PrivateNetwork=yes 一起使用。

  • idle 与 simple 类似,不同之处在于, 服务进程将会被延迟到所有活动任务都完成之后再执行。 这样可以避免控制台上的状态信息与shell脚本的输出混杂在一起。 注意:(1)仅可用于改善控制台输出,切勿将其用于不同单元之间的排序工具; (2)延迟最多不超过5秒, 超时后将无条件的启动服务进程。

       建议对长时间持续运行的服务尽可能使用 Type=simple (这是最简单和速度最快的选择)。 注意,因为 simple 类型的服务 无法报告启动失败、也无法在服务完成初始化后对其他单元进行排序, 所以,当客户端需要通过仅由该服务本身创建的IPC通道(而非由 systemd 创建的套接字或 D-bus 之类)连接到该服务的时候, simple 类型并不是最佳选择。在这种情况下, notifydbus(该服务必须提供 D-Bus 接口) 才是最佳选择, 因为这两种类型都允许服务进程精确的安排 何时算是服务启动成功、何时可以继续启动后继单元。

       notify 类型需要服务进程明确使用 sd_notify() 函数或类似的API, 否则,可以使用 forking 作为替代(它支持传统的UNIX服务启动协议)。

       最后,如果能够确保服务进程调用成功、服务进程自身不做或只做很少的初始化工作(且不大可能初始化失败), 那么 exec 将是最佳选择。 注意,因为使用任何 simple 之外的类型都需要等待服务完成初始化,所以可能会减慢系统启动速度。 因此,应该尽可能避免使用 simple 之外的类型(除非必须)。另外,也不建议对长时间持续运行的服务使用 idleoneshot 类型。

User

http://www.jinbuguo.com/systemd/systemd.exec.html#User=

进程在执行时使用的用户。

如果不指定用户,默认为root,启动各节点后只能切换到root用户才能通过ros2 node list等命令查看各节点的情况。

ExecStart

http://www.jinbuguo.com/systemd/systemd.service.html#ExecStart=

开启程序需要执行的命令。

可以是执行一个脚本,或者一个命令,来开启程序。其他Exec同理。

ExecStop

http://www.jinbuguo.com/systemd/systemd.service.html#ExecStop=

关闭程序需要执行的命令,例如 /bin/kill -s INT $MAINPID(INT即SIGINT)。

KillSignal

http://www.jinbuguo.com/systemd/systemd.kill.html#KillSignal=
设置杀死进程时使用什么信号。默认值为 SIGTERM 信号。
ros2bag命令需要Ctrl+C停止,即发送SIGINT信号,所以这时就需要设置KillSignal=SIGINT

RestartSec

http://www.jinbuguo.com/systemd/systemd.service.html#RestartSec=

程序重启间隔时间,即程序退出后,经过多长时间后重启。

默认值是100毫秒(100ms)。 如果未指定时间单位,那么将视为以秒为单位。 例如设为”20″等价于设为”20s”。

Restart

http://www.jinbuguo.com/systemd/systemd.service.html#Restart=

程序是否重启。取值和适用的场景如下表所示:

退出原因(↓) | Restart= (→) no always on-success on-failure on-abnormal on-abort on-watchdog
正常退出   X X        
退出码不为”0″   X   X      
进程被强制杀死   X   X X X  
systemd 操作超时   X   X X    
看门狗超时   X   X X   X

[Install]

http://www.jinbuguo.com/systemd/systemd.unit.html#%5BInstall%5D%20%E5%B0%8F%E8%8A%82%E9%80%89%E9%A1%B9

Systemd 入门教程:实战篇 七、[Install] 区块

WantedBy

http://www.jinbuguo.com/systemd/systemd.unit.html#WantedBy=

http://www.jinbuguo.com/systemd/systemd.unit.html#%E4%BE%8B%E5%AD%90

使用systemctl get-default命令可以查出systemd默认启动的target是multi-user.target,所以设置WantedBy=multi-user.target才会使得enbale后可以让服务成功开机自启。

关于target的更多介绍见Systemd 入门教程:实战篇 八、Target 的配置文件http://www.jinbuguo.com/systemd/systemd.target.html#

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/150900.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!