2. 裸机编程基础
好,咱们进入第二章。这一章是RTOS的“地基”。
很多初学者一上来就追着API用,结果出了问题根本不知道从哪查。我个人习惯是,先把这几个核心概念嚼碎了,再动手写代码。你想想看,连“任务”和“函数”的区别都搞不清,怎么去设计一个多任务系统?
2.1 任务(Task)—— 不再是简单的函数
裸机编程里,我们写的是 main() 里的一个大循环。RTOS里,我们把功能拆成一个个任务。
任务是什么?它不是普通的C函数。函数是被调用、执行、返回。任务呢?它是个无限循环,有自己的栈空间,有自己的优先级,还有自己的状态。
任务 = 函数体 + 栈 + 优先级 + 状态
函数体是代码逻辑,栈用来保存局部变量和调用现场,优先级决定了谁先跑,状态告诉调度器“我现在能不能跑”。
我在项目中遇到过一个问题:一个新手同事把任务写成了只跑一次的普通函数,结果任务跑完就“消失”了,系统直接挂掉。记住,RTOS的任务函数里必须有一个while(1),或者等待事件挂起自己,绝对不能让它返回。
任务状态机
任务在生命周期里会经历几个状态。我习惯用一张图来记:
这张图我建议你存下来。调试的时候,看任务卡在哪个状态,问题就定位了一半。
2.2 调度器(Scheduler)—— 谁来决定谁先跑?
调度器是RTOS的“大脑”。它的工作很简单:从就绪列表里,挑一个优先级最高的任务去执行。
但这里有个坑。调度策略不同,行为完全不同。
| 调度策略 | 核心规则 | 典型场景 |
|---|---|---|
| 抢占式调度 | 高优先级任务就绪时,立即打断当前任务 | 大多数RTOS默认策略(FreeRTOS、uC/OS) |
| 时间片轮转 | 同优先级任务轮流执行固定时间片 | 多个同等重要的任务共享CPU |
| 协作式调度 | 任务主动让出CPU,否则一直跑 | 极简系统、裸机模拟RTOS |
我曾经踩过一个坑:在一个产品里,我把所有任务都设成了同一个优先级,以为时间片轮转会公平分配。结果一个任务里有个while(1)空转,直接占满了CPU,其他任务饿死。后来我强制给每个关键任务分配不同优先级,才解决问题。
记住:优先级是资源,不要滥用。优先级反转、优先级继承这些高级话题,我们后面章节会专门讲。
2.3 内核(Kernel)—— RTOS的心脏
内核是什么?说白了,就是RTOS最核心的那部分代码。它负责:
- 任务管理:创建、删除、挂起、恢复任务
- 调度器:决定哪个任务运行
- 中断管理:处理中断与任务的交互
- 时间管理:提供延时、超时服务
- 内存管理:分配和回收任务栈、队列等资源
内核分两种:微内核和宏内核。RTOS里大多是宏内核,所有核心服务都编译成一个镜像。好处是效率高,坏处是——一个地方崩了,全系统挂。嗯,嵌入式系统里,我们通常接受这个风险,因为实时性优先。
2.4 临界区(Critical Section)—— 保护共享资源
这是RTOS里最容易出bug的地方,没有之一。
临界区,就是一段不能被中断打断的代码。为什么?因为如果两个任务同时访问同一个全局变量,或者同一个外设寄存器,数据就乱了。
临界区的本质:关中断
进入临界区前,关掉所有可屏蔽中断;退出临界区时,恢复中断状态。这样,这段代码就变成了“原子操作”。
我见过最典型的错误:一个新手在临界区里调用了printf(),结果打印函数本身用了很长时间,导致系统中断响应延迟飙升。记住:临界区要短、要快,最好只做变量赋值、标志位翻转这种微秒级操作。
// 典型临界区用法(FreeRTOS风格)
taskENTER_CRITICAL();
{
// 保护共享变量
shared_counter++;
if (shared_counter > 100) {
shared_counter = 0;
}
}
taskEXIT_CRITICAL();
我的建议:能用信号量、互斥量解决的问题,尽量别用临界区。临界区是“核武器”,杀伤力大,副作用也大。后面讲同步机制时,我会详细对比它们的适用场景。
2.5 上下文切换(Context Switch)—— 换人干活
上下文切换,就是CPU从执行一个任务,切换到执行另一个任务的过程。这个过程是RTOS的“灵魂操作”。
具体来说,切换时要做三件事:
- 保存当前任务的上下文:把CPU寄存器、PC指针、栈指针等压入当前任务的栈
- 加载新任务的上下文:从新任务的栈里弹出之前保存的寄存器值
- 跳转到新任务继续执行:PC指针指向新任务上次被中断的位置
你想想看,这个操作有多频繁?在10ms的时钟节拍下,每秒要发生100次上下文切换。如果一次切换耗时10微秒,那CPU就有0.1%的时间花在切换上。这个开销,在硬实时系统里必须算清楚。
我曾经在一个项目中,把任务数量设到了50多个,结果上下文切换开销占了CPU的5%。后来我合并了部分任务,把数量降到15个,系统响应才恢复正常。任务不是越多越好,够用就行。
2.6 实时性指标—— 怎么衡量RTOS快不快?
很多同学问:“这个RTOS实时性怎么样?” 我一般会反问:“你关心的是哪个指标?”
实时性不是玄学,它有几个硬指标:
| 指标 | 定义 | 典型值(商业RTOS) |
|---|---|---|
| 中断延迟 | 从硬件中断触发到ISR开始执行的时间 | 几十纳秒 ~ 几微秒 |
| 任务切换时间 | 从一个任务切换到另一个任务的时间 | 几微秒 ~ 几十微秒 |
| 抢占延迟 | 高优先级任务就绪到开始执行的时间 | 取决于临界区长度 |
| 抖动(Jitter) | 任务实际执行时间与理论时间的偏差 | 越小越好,硬实时要求无抖动 |
这里我要强调一点:中断延迟和任务切换时间,是RTOS的“硬实力”。这两个指标主要取决于芯片架构和RTOS实现。而抢占延迟,很大程度上取决于你写的代码——临界区越长,抢占延迟越大。
一句话总结:
RTOS的实时性,不是看它跑得多快,而是看它能不能在确定的时间内响应外部事件。确定性,比速度更重要。
好了,这一章的核心概念就这些。任务、调度器、内核、临界区、上下文切换、实时性指标——这六个词,是后面所有章节的基石。你把这章吃透了,后面学信号量、队列、中断管理,就会觉得顺理成章。