2. RTOS基础概念:任务、调度器、临界区、上下文切换
好,咱们正式开始啃RTOS的核心概念。说实话,我刚入行那会儿,觉得RTOS就是个“高级while循环”,后来被现实狠狠教育了一顿。今天我把这四个最基础也最重要的概念掰开揉碎了讲,你吃透了它们,后面移植工作就顺了。
2.1 任务(Task)—— RTOS里的“独立线程”
任务是什么?说白了,就是一段独立的程序执行流。每个任务都有自己的栈空间、优先级和状态。我习惯把任务想象成一个“小工人”,各自干各自的活,互不干扰。
在RTOS里,任务通常是个无限循环的函数:
void myTask(void *param) {
while (1) {
// 干活
vTaskDelay(100); // 让出CPU
}
}
嗯,这里要注意:任务函数不能返回。一旦返回,RTOS会认为任务结束了,会把它清理掉。我在项目中遇到过有人把任务写成普通函数,执行完就return了,结果系统直接崩了——因为RTOS试图切换到一个已经不存在的任务。
任务的状态机:每个任务在运行中会经历四种状态——就绪、运行、阻塞、挂起。你想想看,这就像你排队买奶茶:
- 就绪:排着队,等叫号
- 运行:正在点单
- 阻塞:等奶茶做好(等资源)
- 挂起:暂时不买了(被暂停)
2.2 调度器(Scheduler)—— 谁干活,谁歇着
调度器是RTOS的大脑。它的工作就是决定:此时此刻,哪个任务该跑在CPU上。
常见的调度策略有两种:
| 调度策略 | 特点 | 适用场景 |
|---|---|---|
| 抢占式调度 | 高优先级任务随时可以抢CPU | 火控系统这种实时性要求高的 |
| 时间片轮转 | 同优先级任务轮流跑固定时间 | 普通嵌入式,多个任务平等 |
我个人习惯在火控系统里用抢占式调度。为什么?因为导弹来了,你不能让“显示温度”的任务占着CPU,而“计算弹道”的任务干等着。优先级高的任务必须立刻抢到CPU。
避坑指南:我曾经在调度器里犯过一个低级错误——把两个关键任务的优先级设成一样。结果它们互相抢CPU,导致火控响应延迟了200微秒。200微秒在火控系统里,可能就是生与死的区别。记住:关键任务优先级一定要唯一。
2.3 临界区(Critical Section)—— 别让数据乱掉
临界区,说白了就是一段“不允许被打断”的代码。比如你在更新一个全局变量,突然被高优先级任务抢走了CPU,等回来时数据已经乱了。
RTOS里保护临界区最常用的方法是关中断:
taskENTER_CRITICAL();
// 这里操作共享数据,不会被中断打断
sharedVar++;
taskEXIT_CRITICAL();
嗯,这里有个坑:临界区里不要做耗时操作。我见过有人把整个传感器读取放在临界区里,结果中断被关了100毫秒,系统直接超时。临界区应该短小精悍,只保护真正需要保护的那几行代码。
警告:临界区嵌套要小心。有些RTOS支持嵌套,但你必须保证enter和exit成对出现。我曾经调试过一个bug,就是因为enter了两次但只exit了一次,导致中断永远关着——整个系统像死了一样。
2.4 上下文切换(Context Switch)—— 换人干活
上下文切换,就是RTOS把当前任务的状态保存起来,再恢复另一个任务的状态。这个过程包括:保存寄存器、栈指针、状态字等。
你想想看,这就像你正在写代码,突然被叫去开会。你得保存好当前编辑的文件(保存上下文),开完会再恢复回来(恢复上下文)。
上下文切换的典型流程:
- 触发切换(定时器中断或任务主动让出)
- 保存当前任务的寄存器到它的栈里
- 找到下一个要运行的任务
- 从它的栈里恢复寄存器
- 跳转到新任务的PC指针
我在移植RTOS时,最头疼的就是上下文切换的汇编代码。不同CPU架构的寄存器布局不一样,你得对着芯片手册一行一行写。我记得有一次在ARM Cortex-M上移植,因为没处理好浮点寄存器的保存,导致任务切换后浮点运算全错了——那个bug我调了整整两天。
上下文切换的代价:每次切换大概需要几十到几百微秒。如果你的系统有100个任务,每秒切换1000次,那光切换就占用了10%的CPU。所以任务数量不是越多越好,够用就行。
2.5 四个概念的关系
这四个概念其实是环环相扣的:
- 任务是干活的主体
- 调度器决定谁干活
- 临界区保护干活时的数据
- 上下文切换是换人干活的具体操作
说白了,RTOS就是一套“多任务管理+资源保护”的框架。你理解了这四个概念,后面看RTOS的源码就会轻松很多。下一章咱们开始讲RTOS的启动流程,到时候你会看到这些概念是怎么串起来的。
个人建议:初学者可以先在PC上模拟跑一个RTOS,比如FreeRTOS的Windows模拟版。把任务创建、调度、临界区都跑一遍,比光看书强十倍。我当年就是这么入门的。