云计算百科
云计算领域专业知识百科平台

ROS机器人-从零开始每日日志记录day6

一、什么是 Launch?

简单说,Launch 就是 ROS2 的"一键启动器"。你写一个 Python 脚本,把要启动的节点、参数、话题重映射全声明在里面,一条 ros2 launch 命令全部拉起来。

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
return LaunchDescription([
Node(package='robot_driver', executable='odom_node'),
Node(package='cartographer_ros', executable='cartographer_node'),
Node(package='rviz2', executable='rviz2'),
])

本质上,ros2 launch 启动后会在系统层面创建一个进程组,所有子节点都挂在这个进程组下面,由 Launch 统一管理生命周期。

当我们按下ctrl+c时:

Ctrl+C (SIGINT)


┌─────────────────────────────────────────────────┐
│ ① Python signal handler 被触发 │
│ signal.signal(SIGINT, handler) │
│ → 不直接退出,而是向 asyncio 事件循环 │
│ 投递一个 "shutdown" 事件 │
└──────────────────────┬──────────────────────────┘

┌─────────────────────────────────────────────────┐
│ ② LaunchService._shutdown() 被调用 │
│ → 停止接受新的 Action │
│ → 遍历所有【正在运行的子进程】列表 │
└──────────────────────┬──────────────────────────┘

┌─────────────────────────────────────────────────┐
│ ③ 向每个子进程发送 SIGINT(第一轮) │
│ process.send_signal(signal.SIGINT) │
│ → 给子进程一个"优雅退出"的机会 │
│ → 子进程可以:保存地图、关闭设备、flush日志 │
└──────────────────────┬──────────────────────────┘

┌─────────────────────────────────────────────────┐
│ ④ 等待子进程退出(带超时) │
│ asyncio.wait_for(proc.wait(), timeout) │
│ → 默认超时约 5~10 秒(版本不同有差异) │
│ → 期间持续监控每个子进程的退出状态 │
└──────────────────────┬──────────────────────────┘

子进程是否全部退出?
/ \\
是 否(超时了还没死)
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────────────┐
│ ⑤a 清理完毕 │ │ ⑤b 升级:发送 SIGTERM │
│ 关闭事件循环 │ │ process.terminate() │
│ Launch 退出 │ │ → 再等一小段时间 │
└──────────────────┘ │ → 还不行?SIGKILL 强杀 │
│ process.kill() │
└──────────────────────────┘


二、什么情况下杀死 Launch 会导致孤儿节点?

直接 kill -9 <pid> 把 launch 进程杀了,结果后台还跑着三四个节点,ros2 node list 一看全是"幽灵"。

原因:kill -9 发的是 SIGKILL,这个信号不可捕获、不可拦截。 Launch 进程瞬间暴毙,根本来不及向子进程转发退出信号,子进程就成了孤儿,被系统 init 进程接管,继续在那跑。

除了 kill -9,还有几种情况也会产生孤儿:

  • Launch 进程自己段错误崩了(父进程异常退出)
  • 子进程通过 shell=True 或 setsid() 启动,脱离了进程组
  • 节点代码里自己屏蔽了 SIGINT/SIGTERM

正确姿势: 永远用 Ctrl+C(SIGINT)或 kill -15(SIGTERM),给 Launch 一个"善后"的机会。


三、什么是流控反压(Backpressure)?

一句话:消费端太慢 → 缓冲区堆满 → 压力反向传导回生产端 → 生产端被迫停发。

生活类比:你拿水管往杯子里灌水,杯子满了水溢出来。如果杯子有个密封盖(缓冲区有限),水灌不进去,你就被迫关水龙头——这个"被迫关"就是反压。

在 DDS 里,Reliable 模式 + 有限队列深度 可能导致流控反压:

  • Publisher 发数据进队列
  • Subscriber 消费后回 ACK
  • 如果 Subscriber 太慢,ACK 迟迟不来,队列堆满
  • Publisher 的 publish() 被阻塞(或返回失败)
  • 生产端被消费端拖死了

Best Effort 模式没有 ACK 机制,队列满了直接丢最旧的,不存在反压,但也意味着可能丢数据。


四、Cartographer 崩溃问题

(ROS机器人-从零开始每日日志记录day3-CSDN博客)文中问题

我的 odom 发送端配置:

auto qos = rclcpp::QoS(rclcpp::KeepLast(10)).reliable();
odom_pub_ = create_publisher<nav_msgs::msg::Odometry>("/odom", qos);

odom发送端的发送模式是reliable且队列长度为10,当carto对odom的消费不及时时,odom数据积压,导致odom的发送端停发odom数据,odom数据停发又会导致carto拿不到odom数据进而形成恶性循环。

Cartographer 内部计算量大(大角度转弯后扫描匹配、子图构建)
→ 消费 /odom 速度下降
→ DDS ACK 延迟
→ Publisher 队列(depth=10)堆满
→ Reliable 模式触发反压,odom 停发
→ Cartographer 拿不到新 odom
→ 处理更加停滞 → 消费更慢 → 队列更满
→ 恶性循环

解决

将odom 发送模式改成 Best Effort:

auto qos = rclcpp::QoS(rclcpp::KeepLast(5))
.best_effort()
.durability_volatile();


五、DDS 两节点互通的三个前提

当上层UDP消息无法到达小车时,要考虑是不是消息在转换的时候DDS不通。

条件说明
同 ROS_DOMAIN_ID 不同 domain 逻辑隔离,互相不可见(默认 0)
同 RMW 实现 FastDDS 和 CycloneDDS 之间不互通
QoS Profile 兼容 Best Effort 的 Pub 无法被 Reliable 的 Sub 订阅

排查命令:

echo $ROS_DOMAIN_ID
echo $RMW_IMPLEMENTATION
ros2 topic info /odom –verbose

赞(0)
未经允许不得转载:网硕互联帮助中心 » ROS机器人-从零开始每日日志记录day6
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!