4. 任务(Task)基础:任务状态、任务优先级、任务调度策略

好,咱们今天聊聊任务。这是 RTOS 里最核心的概念,没有之一。

你想想看,一个嵌入式系统要同时处理按键、刷屏、读传感器、发数据……如果还像裸机那样写一个大循环,迟早会乱成一锅粥。任务,就是把这些并发的事情拆成一个个独立的“小线程”,让操作系统帮你调度。

4.1 任务的状态机:它到底在干嘛?

一个任务从创建到销毁,会经历几种状态。我刚开始学 RTOS 时,总觉得状态图很抽象,直到有一次调试一个死锁问题,才真正理解了“阻塞”和“挂起”的区别。

典型的 RTOS 任务状态包括:

  • 就绪(Ready):任务已经准备好运行,就等 CPU 空闲。
  • 运行(Running):CPU 正在执行这个任务的代码。同一时刻,单核 CPU 上只有一个任务在运行。
  • 阻塞(Blocked):任务在等待某个事件,比如等待信号量、等待队列消息、等待延时结束。说白了,它“睡着了”,不参与 CPU 竞争。
  • 挂起(Suspended):任务被显式暂停,通常由其他任务或中断调用挂起函数。它比阻塞更“被动”。

嗯,这里要注意:有些 RTOS(比如 FreeRTOS)把“挂起”也归为阻塞的一种,但概念上还是有区别的。我个人习惯把“阻塞”理解为“主动等资源”,“挂起”理解为“被动被叫停”。

核心逻辑:任务只能在“就绪”状态下被调度器选中,进入“运行”状态。运行中如果遇到延时或等待资源,就进入“阻塞”;如果被更高优先级任务抢占,就回到“就绪”。

我曾经在一个项目中,因为没处理好任务阻塞超时,导致一个低优先级任务永远拿不到信号量,整个系统卡死。后来加了个超时机制,问题才解决。避坑指南:所有阻塞调用,尽量设置超时参数,别用无限等待。

4.2 任务优先级:谁更重要?

优先级决定了任务在就绪队列里的排队顺序。数值越小优先级越高?还是越大越高?这取决于 RTOS 的实现。FreeRTOS 里数值越大优先级越高,uC/OS 则相反。我建议你养成看手册的习惯,别想当然。

优先级设计有几个原则:

  • 实时性要求高的任务,给高优先级。比如电机控制、中断响应后的数据处理。
  • 避免同优先级任务过多。同优先级任务通常采用时间片轮转,如果太多,切换开销会变大。
  • 注意优先级反转。这是一个经典问题:低优先级任务拿着高优先级任务需要的资源,导致高优先级任务被“反转”阻塞。解决办法是使用优先级继承协议。

我的经验:优先级数量一般 8~32 级就够用了。别搞 256 级,调度器开销大,而且你根本用不上那么多。我一般把任务分成三类:紧急(0-2级)、普通(3-5级)、后台(6-7级)。

4.3 任务调度策略:谁先跑?

调度策略就是操作系统决定“下一个该谁跑”的规则。常见的策略有三种:

策略 说明 适用场景
抢占式调度 高优先级任务就绪时,立即打断当前运行的低优先级任务 大多数实时系统,响应快
时间片轮转 同优先级任务轮流运行固定时间片 多个同等重要的任务共享 CPU
协作式调度 任务主动让出 CPU,否则一直运行 简单系统,无强实时要求

绝大多数 RTOS 都支持抢占式调度。这也是实时性的根本保障。你想想看,如果高优先级任务不能立即抢到 CPU,那还叫什么实时?

不过,抢占式调度也有代价:临界区保护必须做好。否则一个任务正在修改共享数据,突然被抢走,另一个任务也来修改,数据就乱套了。我见过一个产品,就是因为没加临界区保护,导致两个任务同时操作一个全局变量,偶尔出现数据错乱,排查了整整两天。

注意:抢占式调度下,中断服务程序(ISR)的优先级高于所有任务。ISR 里不要做复杂操作,只做“标记事件”,具体处理交给任务。这是 RTOS 设计的基本功。

4.4 知识体系图

下面这张图,帮你把任务状态、优先级、调度策略串起来:

任务核心知识体系 任务状态机 就绪 → 运行 → 阻塞 挂起(被动暂停) 任务优先级 数值大小决定优先级 注意优先级反转 调度策略 抢占式 时间片轮转 协作式 三者关系 • 任务状态决定“任务当前能不能跑” • 任务优先级决定“就绪队列里谁先跑” • 调度策略决定“什么时候切换任务” • 三者配合,才能实现实时、可靠的多任务系统

4.5 实战建议

说了这么多理论,最后给点实在的:

  1. 任务栈大小要合理。太小会溢出,太大浪费内存。我一般先给个保守值(比如 512 字节),然后用工具检测实际使用量,再调整。
  2. 不要在一个任务里做太多事。如果一个任务既要读传感器,又要算数据,还要发消息,那就拆成三个任务。职责单一,调试也方便。
  3. 优先级不要轻易动态修改。除非你很清楚后果。动态改优先级容易引发不可预见的调度行为。
  4. 用好空闲任务。空闲任务里可以放低功耗模式,或者做系统健康检查。

一个小技巧:调试阶段,可以在每个任务里加一个计数器,统计它被调度的次数。如果某个任务调度次数异常多或异常少,说明优先级或延时设置有问题。这招帮我抓过不少 bug。

好了,任务基础就聊到这儿。记住:任务状态、优先级、调度策略,这三者是 RTOS 多任务设计的基石。理解透了,后面学信号量、消息队列、互斥锁,就会轻松很多。


专注资料整理