2. RTOS核心概念:任务、调度器、优先级、上下文切换、实时性指标
好,咱们正式开始啃RTOS的硬骨头了。这一章我打算把几个最核心的概念掰开揉碎了讲。你想想看,搞洪水预警系统,说白了就是让一堆任务在正确的时间点干正确的事。那RTOS凭什么能做到?靠的就是这几个东西:任务、调度器、优先级、上下文切换,还有实时性指标。
我个人习惯,学任何RTOS之前,先把这几个概念刻在脑子里。它们就像房子的地基,地基不稳,后面盖多少层都白搭。
2.1 任务(Task)—— 系统里干活的最小单元
任务是什么?说白了,就是一段独立的程序,有自己的栈空间,有自己的执行流。在洪水预警系统里,采集水位数据是一个任务,处理数据是一个任务,发送报警信号又是一个任务。
每个任务在RTOS里通常是个无限循环。为什么?因为嵌入式系统一开机就不停了,任务得一直等着干活。我见过不少新手把任务写成跑一次就退出的函数,结果系统直接崩了。嗯,这里要注意:任务函数绝对不能返回。
任务的核心要素:
- 任务控制块(TCB):RTOS用来管理任务的数据结构,存了栈指针、优先级、状态等信息
- 任务栈:每个任务独享的栈空间,存局部变量和函数调用信息
- 任务入口函数:任务开始执行的代码地址
给你看个典型的任务代码结构:
void vWaterLevelTask(void *pvParameters)
{
// 初始化硬件(比如ADC)
adc_init();
while(1)
{
// 读取水位传感器
uint32_t level = adc_read();
// 处理数据
process_water_level(level);
// 延时,让出CPU
vTaskDelay(pdMS_TO_TICKS(100));
}
}
我在项目中遇到过一个问题:有个同事把任务栈设得太小,结果函数调用层级一深,栈就溢出了。查了两天才找到原因。所以我的建议是——任务栈大小至少留30%的余量。
2.2 调度器(Scheduler)—— 系统的大脑
调度器是RTOS的核心。它决定哪个任务该运行,哪个任务该等着。你想想看,CPU只有一个(单核情况下),但任务可能有十几个。谁先跑?谁后跑?这就是调度器的工作。
常见的调度策略有两种:
| 调度策略 | 特点 | 适用场景 |
|---|---|---|
| 抢占式调度 | 高优先级任务随时可以打断低优先级任务 | 洪水预警这种实时性要求高的系统 |
| 协作式调度 | 任务主动让出CPU,其他任务才能运行 | 简单系统,任务之间配合默契 |
我个人强烈推荐抢占式调度。为什么?因为洪水来了可不会等你把当前任务跑完。水位突然暴涨,报警任务必须立刻抢到CPU,哪怕数据采集任务正跑到一半。
我的经验:在洪水预警系统里,我一般把报警任务的优先级设为最高。水位采集任务次之,日志记录任务最低。这样能保证最紧急的事情最先处理。
2.3 优先级(Priority)—— 谁更重要谁先跑
优先级就是个数字。数字越小优先级越高,还是数字越大优先级越高?这得看具体RTOS的实现。FreeRTOS里是数字越大优先级越高,uC/OS-II里是数字越小优先级越高。我建议你查手册确认一下,别搞反了。
优先级设计是个技术活。我曾经在一个项目里把优先级设得太细,分了32级。结果呢?调度器频繁切换,系统开销大得吓人。后来我学乖了,一般只分3-5个优先级:
- 紧急优先级:报警、故障处理
- 高优先级:数据采集、实时控制
- 中优先级:数据处理、通信
- 低优先级:日志、界面刷新
- 空闲优先级:后台任务
避坑指南:我曾经见过一个系统,两个任务优先级相同,结果它们互相抢CPU,谁都没法好好干活。记住:同优先级任务之间是时间片轮转,如果时间片设得太短,频繁切换会浪费大量CPU时间。
2.4 上下文切换(Context Switch)—— 换人干活的开销
上下文切换,说白了就是CPU从一个任务换到另一个任务时,要把当前任务的“现场”保存下来,再恢复下一个任务的“现场”。这个“现场”包括:CPU寄存器、栈指针、程序计数器等。
每次上下文切换都有开销。我测过,在Cortex-M3上跑FreeRTOS,一次上下文切换大概需要1-2微秒。听起来很快对吧?但如果你的系统里每秒切换几千次,那开销就不可忽视了。
上下文切换的典型流程:
- 触发中断(比如SysTick定时器)
- 保存当前任务的寄存器到它的栈里
- 更新当前任务的状态(从运行态变为就绪态)
- 调度器选下一个要运行的任务
- 恢复新任务的寄存器
- 跳转到新任务继续执行
嗯,这里有个关键点:上下文切换的频率直接影响系统的实时性。切换太频繁,CPU全花在切换上了;切换太少,任务响应又不够及时。我一般把时间片设在5-10毫秒,具体得看你的系统对响应时间的要求。
2.5 实时性指标—— 快不是全部,准时才是
实时性不是跑得快,而是在规定时间内完成。洪水预警系统里,从水位超限到发出报警,这个时间必须小于某个阈值。比如我们要求200毫秒内必须报警,那这个200毫秒就是实时性指标。
常用的实时性指标有三个:
| 指标 | 含义 | 洪水预警中的例子 |
|---|---|---|
| 响应时间 | 从事件发生到任务开始处理的时间 | 水位超限到报警任务开始执行 |
| 截止时间 | 任务必须完成的最晚时间 | 报警信号必须在200ms内发出 |
| 抖动 | 任务完成时间的波动范围 | 每次采集数据的时间偏差 |
硬实时 vs 软实时:
- 硬实时:错过截止时间就是系统故障。洪水报警就是硬实时,晚一秒钟可能就有人遇难。
- 软实时:偶尔错过截止时间还能接受。比如日志记录,晚几秒写进去问题不大。
我在设计洪水预警系统时,把报警任务设为硬实时,其他任务设为软实时。这样调度器会优先保证报警任务的截止时间,其他任务能跑就跑,跑不了就等等。
2.6 把这些概念串起来
好,咱们把这些概念串起来,看看它们在洪水预警系统里是怎么配合的。
假设有三个任务:
- Task_A:水位采集(优先级2,每100ms跑一次)
- Task_B:数据处理(优先级1,Task_A跑完就触发)
- Task_C:报警输出(优先级3,水位超限时触发)
正常情况下的执行流程:
- Task_A运行,采集水位数据
- Task_A延时,让出CPU
- 调度器选Task_B运行,处理数据
- Task_B处理完,延时
- 调度器选Task_C运行(如果有报警需要输出)
如果水位突然暴涨:
- Task_A正在运行,采集到超限数据
- Task_A通过某种机制(比如信号量)唤醒Task_C
- 调度器发现Task_C优先级更高,立刻切换过去
- Task_C输出报警信号
- Task_C处理完,调度器再切回Task_A
你看,整个过程里,调度器、优先级、上下文切换都在协同工作。哪个环节出问题,整个系统的实时性就保不住。
我的建议:刚开始学RTOS,别急着写复杂代码。先拿一个开发板,创建两三个任务,调一调优先级,观察一下任务切换的行为。我当年就是这么过来的,踩了不少坑,但也学得最扎实。
下一章咱们聊聊任务状态和任务管理。任务到底有几种状态?怎么创建、删除、挂起、恢复一个任务?这些操作在洪水预警系统里怎么用?到时候我会结合具体场景给你讲透。