3. 任务划分与优先级设计

任务划分和优先级设计,说白了就是决定「谁先干、谁后干、谁可以打断谁」。我见过太多机器人项目,代码写得挺漂亮,一跑起来就卡顿、抖震,最后查下来,十有八九是任务划分不合理、优先级乱套了。

这一章,咱们就聊聊怎么把任务拆得干净利落,怎么分配优先级才不出乱子,以及怎么避开那些坑——优先级反转和死锁。

3.1 任务分解原则

任务分解不是拍脑袋。我个人的习惯是,先画一张功能框图,把机器人要干的事列出来:传感器采集、数据处理、控制计算、电机输出、通信、日志……然后按下面几个原则去切。

原则 说明 我踩过的坑
单一职责 一个任务只做一件事。比如「读取IMU数据」和「融合姿态」必须分开。 我曾经把IMU读取和姿态解算放一起,结果IMU驱动一卡,整个姿态都挂了。
时间解耦 不同频率的任务拆开。控制环路1kHz,日志100ms,混在一起谁也跑不好。 ——
资源隔离 共享同一硬件资源(如I2C、SPI)的操作尽量放一个任务里,避免争抢。 两个任务同时读写同一个I2C传感器,数据全乱套了。
可测试性 每个任务都能独立跑、独立测。别搞成「不启动整个系统就没法测」。 ——
小技巧: 我习惯用「任务职责矩阵」来检查——横轴是任务名,纵轴是功能/资源,交叉点打勾。如果某个格子有两个勾,说明职责没分干净。

3.2 优先级分配策略

优先级分配,核心就一句话:紧急且重要的给高优先级,不紧急不重要的给低优先级。但实际做起来,没那么简单。

我一般把任务分成三类:

  • 硬实时任务:比如电机电流环、急停检测。错过截止时间=系统崩溃。给最高优先级。
  • 软实时任务:比如里程计计算、路径规划。偶尔延迟一次还能忍。给中等优先级。
  • 非实时任务:比如日志记录、远程调试。晚几秒无所谓。给最低优先级。

你想想看,如果给日志任务一个高优先级,电机控制反而被抢了CPU,那机器人跑起来肯定一抖一抖的。嗯,这就是典型的「优先级错配」。

一个实用的分配流程:

  1. 列出所有任务,标注周期和截止时间。
  2. 按截止时间从短到长排序——截止时间越短,优先级越高。
  3. 检查是否有共享资源(比如同一个传感器),有的话适当调整,避免低优先级任务长时间霸占资源。
  4. 用速率单调调度(RMS)或最早截止时间优先(EDF)做理论验证。

3.3 避免优先级反转与死锁

这两个问题,是实时系统里的「隐形杀手」。我先说优先级反转。

3.3.1 优先级反转

什么叫优先级反转?简单说:高优先级任务在等低优先级任务释放资源,而低优先级任务又被中等优先级任务抢占了CPU。结果高优先级任务反而被「饿死」了。

我曾经在一个四轮机器人项目里遇到过:电机控制任务(高优先级)要读取IMU数据,但IMU驱动任务(低优先级)正在写数据,被一个日志任务(中等优先级)抢占了CPU。电机控制等了整整200ms——机器人直接撞墙了。

解决办法有三种:

方法 原理 适用场景
优先级继承 低优先级任务在持有锁时,临时提升到等待该锁的最高优先级任务的优先级。 RTOS原生支持(如FreeRTOS、RT-Thread)
优先级天花板 给每个资源设定一个「天花板优先级」,任何任务访问该资源时,优先级自动提升到天花板。 资源少、访问频繁的场景
关中断/关调度 访问共享资源时禁止任务切换。 临界区极短(几微秒)的场景
注意: 关中断虽然简单粗暴,但关太久会影响系统实时性。我建议关中断时间不要超过10微秒,否则其他硬实时任务会遭殃。

3.3.2 死锁

死锁就是两个任务互相等对方释放资源,谁也动不了。典型的场景:任务A持有锁1,等锁2;任务B持有锁2,等锁1。死锁了。

避免死锁,我总结了四条铁律:

  • 按固定顺序加锁:所有任务加锁的顺序必须一致。比如先锁A再锁B,不能有的先B后A。
  • 使用超时机制:加锁时设置超时时间,超时了就放弃并释放已有锁。
  • 尽量少用嵌套锁:能用一个锁解决的问题,别用两个。
  • 用无锁数据结构:比如环形缓冲区、原子操作,从根上避免死锁。
避坑指南: 我曾经在一个六轴机械臂项目里,三个任务互相嵌套加锁,调试了三天才发现是死锁。后来我强制要求所有任务按「传感器→控制→通信」的顺序加锁,再也没出过问题。

3.4 知识体系图

下面这张图,把任务划分和优先级设计的核心逻辑串起来了。你可以把它贴在工位旁边,做设计时对照着看。

任务划分与优先级设计知识体系 任务划分与优先级设计 任务分解原则 优先级分配策略 避免反转与死锁 单一职责 时间解耦 资源隔离 可测试性 硬实时 软实时 非实时 优先级继承 优先级天花板 固定顺序加锁 超时机制 核心目标:保证硬实时任务按时完成,避免优先级反转和死锁

3.5 小结

任务划分和优先级设计,是机器人系统开发的「地基」。地基没打好,后面优化再多也白搭。我个人建议,每次新项目启动时,花半天时间把任务分解和优先级分配画清楚,比后期花几天去调bug划算得多。

嗯,这一章就到这里。记住:单一职责、时间解耦、固定顺序加锁、避免嵌套——这四句话记住了,能省你很多调试时间。