第四节:节点优先级与CPU亲和性

好,咱们接着聊实时性优化。前面讲了线程调度策略,今天要说的这两个东西——节点优先级和CPU亲和性,说白了就是给ROS节点分配“特权”。

我刚开始做机器人项目时,经常遇到一个现象:明明电机控制节点跑得挺快,但一开激光雷达和视觉处理,电机就开始抖。后来才发现,是CPU资源被抢了。嗯,这就是我们今天要解决的问题。

4.1 节点优先级:谁该先跑?

ROS节点本质上是Linux进程。每个进程都有优先级,Linux用nice值来控制。nice值范围是-20到19,数值越小优先级越高。默认是0。

我个人习惯把实时性要求高的节点(比如电机控制、IMU读取)设置成负nice值。但注意,普通用户只能调高nice值(变低优先级),要调低(变高优先级)需要root权限。

核心原则:

  • 控制类节点:nice值设为-10到-5
  • 传感器驱动节点:nice值设为-5到0
  • 规划、导航等计算密集型节点:保持默认0或调高到5-10

设置方法很简单,启动节点时加上nice命令:

# 启动电机控制节点,优先级调高
sudo nice -n -10 rosrun robot_base motor_controller.py

# 启动激光雷达驱动,优先级适中
sudo nice -n -5 rosrun lms1xx lms1xx_node

# 启动导航规划,优先级降低
nice -n 10 rosrun move_base move_base

我在项目中遇到过一个问题:把视觉处理节点优先级设得太高,结果CPU被它占满,电机控制反而卡顿。你想想看,视觉处理是计算密集型任务,优先级再高也架不住它吃CPU。所以优先级设置要结合任务特性,不是越高越好。

避坑指南:

我曾经在调试AGV时,把所有节点都设成高优先级,结果系统直接卡死。因为高优先级进程太多,内核调度器忙不过来了。记住:高优先级是稀缺资源,只给最关键的节点用。

4.2 CPU亲和性:给节点分配专属核心

CPU亲和性,说白了就是把某个进程绑定到指定的CPU核心上运行。为什么要这么做?

原因很简单:CPU核心之间切换是有代价的。如果进程频繁在不同核心间跳来跳去,缓存命中率会下降,性能就上不去。对于实时性要求高的节点,我们希望它独占一个核心,减少干扰。

设置CPU亲和性,我推荐用taskset命令:

# 把电机控制节点绑定到CPU0
taskset -c 0 rosrun robot_base motor_controller.py

# 把激光雷达节点绑定到CPU1
taskset -c 1 rosrun lms1xx lms1xx_node

# 把视觉处理绑定到CPU2和CPU3
taskset -c 2,3 rosrun usb_cam usb_cam_node

这里-c 0表示绑定到第0个核心。多核处理器核心编号从0开始。

我建议的分配策略是这样的:

核心编号 分配节点 说明
CPU0 电机控制、IMU驱动 最高实时性,独占核心
CPU1 激光雷达、里程计 中等实时性,可共享
CPU2 视觉处理、SLAM 计算密集型,可独占
CPU3 系统服务、日志 低优先级,后台任务

小技巧:

如果你用的是树莓派或Jetson这类嵌入式平台,核心数有限。我一般把核心0留给系统,核心1给控制节点,核心2给传感器,核心3给计算任务。这样分配比较均衡。

4.3 isolcpus内核参数:彻底隔离核心

光用taskset还不够。为什么?因为Linux内核默认会把各种中断、内核线程分配到所有核心上。就算你把节点绑到CPU0,系统中断也可能跑到CPU0上,打断你的实时任务。

这时候就需要isolcpus内核参数了。它告诉内核:某些核心你别碰,留给用户程序专用。

设置方法是在/boot/cmdline.txt(树莓派)或/etc/default/grub(Ubuntu)中添加:

# 在GRUB_CMDLINE_LINUX中添加
GRUB_CMDLINE_LINUX="isolcpus=0,1"

# 然后更新grub
sudo update-grub
sudo reboot

这个参数的意思是:CPU0和CPU1被隔离,内核不会把普通进程和中断分配到这两个核心上。只有你明确用taskset绑定的进程才能用它们。

我实际测试过,用了isolcpus之后,电机控制节点的抖动从原来的几百微秒降到了几十微秒。效果非常明显。

注意:

隔离核心后,系统可用的核心数会减少。比如4核CPU隔离了2个,系统就只剩2个核心可用。所以别贪心,留够核心给系统用。我一般隔离1-2个核心就够了。

4.4 综合配置示例

好了,我们把前面讲的整合一下。假设你有一个4核的Jetson Nano,要跑一个机器人系统:

# 1. 启动脚本示例
#!/bin/bash

# 设置实时调度和优先级
sudo chrt -f 80 taskset -c 0 rosrun robot_base motor_controller.py &
sudo chrt -f 70 taskset -c 1 rosrun robot_base imu_driver.py &

# 传感器节点,中等优先级
sudo chrt -f 60 taskset -c 2 rosrun lms1xx lms1xx_node &

# 计算节点,普通优先级
taskset -c 3 rosrun cartographer cartographer_node &

# 后台任务,低优先级
nice -n 10 rosrun rosbag record -a &

wait

这里我用了chrt命令设置实时调度策略(-f表示FIFO),配合taskset绑定核心。这样每个节点都有明确的优先级和专属核心。

总结一下我的经验:

  • 优先级:只给控制类节点高优先级,别贪多
  • CPU绑定:把实时节点和计算节点分开到不同核心
  • isolcpus:隔离1-2个核心给实时任务专用
  • 测试验证:用htopperf观察实际运行情况

我曾经在一个项目中,就因为没做这些优化,电机控制周期从1ms抖到了5ms,机器人走起来一瘸一拐的。后来做了优先级和CPU绑定,周期稳定在1ms以内。所以说,这些细节真的很重要。

下一节我们讲内存管理,包括锁内存、避免swap等技巧。这些和今天的内容配合起来,效果更好。