第3章:优先级的基本概念

各位工程师朋友,咱们今天聊聊RTOS里最基础、也最容易踩坑的一个概念——优先级。

说实话,我见过太多人在优先级上翻车了。有一次,一个同事调试了三天,最后发现是优先级数值搞反了。嗯,这种事其实挺常见的。

3.1 静态优先级 vs 动态优先级

先说说两种优先级模式。静态优先级,就是任务创建时定死的,运行期间不能改。动态优先级呢,系统运行时可以调整。

我个人习惯,简单场景用静态优先级。为什么?因为好理解,好调试。你想想看,一个任务从生到死优先级不变,逻辑上多清晰。

但有些场景,静态优先级搞不定。比如一个低优先级任务偶尔需要紧急处理数据,这时候动态调整就派上用场了。

关键区别:

  • 静态优先级:任务创建时设定,运行期间固定不变
  • 动态优先级:系统运行时可以调整,常见于优先级继承、优先级反转处理等场景

我在项目中遇到过一个问题:一个数据采集任务,平时优先级很低,但一旦缓冲区快满了,必须立刻提升优先级去处理。这就是典型的动态优先级应用。

3.2 优先级数值的对应关系

这里有个大坑,我必须重点说。

不同RTOS对优先级数值的定义完全相反。有的系统数值越小优先级越高,有的则相反。你换一个RTOS,可能整个优先级设计都得重来。

RTOS 数值越小优先级 典型范围
FreeRTOS 越低 0 ~ configMAX_PRIORITIES-1
uC/OS-II 越高 0 ~ 63(0最高)
RT-Thread 越高 0 ~ RT_THREAD_PRIORITY_MAX-1
VxWorks 越高 0 ~ 255(0最高)

你看,FreeRTOS是数值越小优先级越低,而uC/OS-II正好相反。我刚开始用FreeRTOS时,习惯性地把最高优先级任务设成0,结果它老是不运行。查了半天才发现,原来0在FreeRTOS里是最低优先级。

避坑指南:

我曾经在一个项目里,团队一半人用FreeRTOS,另一半人用uC/OS-II。结果代码合并时,优先级数值全乱套了。后来我们统一加了个宏定义,比如#define HIGHEST_PRIORITY 0,再根据RTOS做映射。这招挺管用,推荐给你。

3.3 优先级数值的设计原则

说完了概念,聊聊实际怎么用。

我个人建议,优先级数值不要用满。比如系统支持32级优先级,你只用16级。为什么?留点余量给后续扩展。我见过一个产品,刚开始只用了4个优先级,后来加了新功能,发现优先级不够用了,整个重构,那叫一个痛苦。

我的经验:

  • 关键任务(如心跳、安全监测)用最高优先级
  • 实时性要求高的任务(如传感器采集)用中高优先级
  • 普通任务(如数据显示)用中低优先级
  • 后台任务(如日志记录)用最低优先级

还有一点,优先级数值最好用宏定义,别直接写数字。你想想看,代码里到处都是osTaskCreate(task, NULL, 5),过两个月你自己都看不懂5是什么意思。

// 推荐写法
#define PRIO_HEARTBEAT    0   // 最高优先级
#define PRIO_SENSOR       2
#define PRIO_DISPLAY      4
#define PRIO_LOGGER       6   // 最低优先级

// 不推荐写法
osTaskCreate(heartbeatTask, NULL, 0);
osTaskCreate(sensorTask, NULL, 2);
osTaskCreate(displayTask, NULL, 4);
osTaskCreate(loggerTask, NULL, 6);

嗯,这样写,别人看代码一目了然。而且以后调整优先级,改一个宏定义就行,不用满世界找数字。

3.4 优先级反转与动态优先级

说到动态优先级,不得不提优先级反转。这是个经典问题。

简单说,就是低优先级任务占着资源不放,高优先级任务等着,结果中优先级任务插进来把低优先级任务抢走了CPU。高优先级任务反而被中优先级任务阻塞了。

解决这个问题,常用方法就是优先级继承——低优先级任务临时提升到高优先级任务的级别,等资源释放了再降回去。这就是动态优先级的一个典型应用。

优先级反转的典型场景:

  1. 任务A(高优先级)等待任务C(低优先级)释放互斥锁
  2. 任务B(中优先级)抢占任务C,任务C无法运行
  3. 任务A被任务B间接阻塞,优先级反转发生

我在一个电机控制项目里就遇到过。一个高优先级的控制任务,因为一个低优先级的日志任务占着I2C总线,死活拿不到数据。后来加了优先级继承,问题就解决了。

好了,优先级的基本概念就聊到这儿。记住两点:第一,搞清楚你的RTOS里数值和优先级的关系;第二,留好余量,别把优先级用满。下节课咱们聊聊优先级分配的具体策略。