2、RTOS基础概念:任务、调度器、临界区、上下文切换、实时性指标

好,咱们正式开始啃RTOS的核心概念。说实话,很多初学者一上来就被这些术语吓住了。什么任务啊、调度啊、临界区啊……听着挺唬人。但你别急,我带你一个一个拆开看,其实没那么玄乎。

2.1 任务(Task)—— RTOS里的“独立线程”

任务,说白了就是一段独立执行的代码。在裸机编程里,我们通常写一个超级循环:

void main() {
    while(1) {
        read_sensor();
        update_display();
        check_communication();
    }
}

但有了RTOS,每个功能可以拆成独立的任务。每个任务有自己的栈空间、优先级和状态。我习惯把任务想象成“一个能独立干活的小人”。

任务通常有四种状态:

  • 运行态——CPU正在执行这个任务
  • 就绪态——任务准备好了,等CPU来执行
  • 阻塞态——任务在等某个事件(比如延时、信号量)
  • 挂起态——任务被暂停了,不参与调度
我的经验: 我在做车载HUD项目时,一开始把显示刷新和传感器读取放在同一个任务里。结果传感器一卡,显示就跟着抖。后来拆成两个独立任务,各跑各的,问题就解决了。记住:任务拆分要合理,别太细也别太粗。

2.2 调度器(Scheduler)—— 谁来决定下一个跑谁?

调度器是RTOS的大脑。它决定哪个任务该获得CPU的使用权。常见的调度策略有两种:

调度策略 特点 适用场景
优先级抢占式 高优先级任务随时可以打断低优先级任务 实时性要求高的系统
时间片轮转 同等优先级任务轮流执行固定时间片 多个同等重要任务

我个人更偏爱优先级抢占式调度。为什么?因为HUD系统里,显示刷新必须优先于日志记录。你想想看,如果日志任务占着CPU不放,显示画面卡了,那用户体验得多糟糕。

注意: 优先级不是越高越好。我曾经在一个项目里把所有任务都设成最高优先级,结果调度器忙得团团转,低优先级任务永远得不到执行。这叫“优先级反转”,是个坑。

2.3 临界区(Critical Section)—— 共享资源的“安全屋”

临界区,说白了就是一段不允许被打断的代码。当多个任务访问同一个全局变量或硬件寄存器时,就可能出问题。

举个例子:

// 任务A
counter++;

// 任务B
counter--;

如果任务A读到counter=5,正要加1,突然被任务B抢占了。任务B把counter减到4。然后任务A继续执行,把4加1变成5。你看,本来应该是5+1-1=5,结果变成了4。这就是典型的“竞态条件”。

解决办法就是加临界区保护:

taskENTER_CRITICAL();
counter++;
taskEXIT_CRITICAL();

嗯,这里要注意:临界区里不要做耗时操作。我见过有人把延时函数放在临界区里,结果整个系统卡死了。那叫一个惨。

2.4 上下文切换(Context Switch)—— 任务的“换人”过程

上下文切换,就是CPU从一个任务切换到另一个任务时,保存和恢复现场的过程。保存的内容包括:

  • CPU寄存器(R0-R15、PC、LR等)
  • 栈指针(SP)
  • 浮点寄存器(如果有FPU)

每次切换都有开销。我记得在Cortex-M3上,一次上下文切换大约需要几十到几百个时钟周期。听起来不多,但如果每秒切换几千次,那CPU时间就浪费了不少。

关键点: 上下文切换频率要合理。我建议:对于HUD这类系统,任务切换频率控制在100-1000Hz就够了。太频繁反而降低效率。

2.5 实时性指标—— 怎么衡量系统“快不快”?

实时性不是“快”,而是“可预测”。HUD系统里,显示刷新必须在16.67ms内完成(60Hz刷新率)。如果偶尔超时,画面就会撕裂。

常用的实时性指标有:

指标 含义 典型值
响应时间 从事件发生到任务开始处理的时间 <1ms
截止时间 任务必须完成的最晚时间 16.67ms(60fps)
抖动 响应时间的波动范围 <100μs
吞吐量 单位时间内处理的任务数 视场景而定

我曾经遇到一个坑:系统平时跑得好好的,但一旦开启Wi-Fi,显示任务就开始抖动。后来发现是Wi-Fi中断优先级太高,频繁打断显示任务。解决办法是把显示任务的中断优先级调高,同时给Wi-Fi中断加一个“可延迟处理”的机制。

避坑指南: 我曾经在调试一个HUD原型时,发现显示偶尔会闪一下。查了半天,原来是日志任务在打印时占用了临界区,导致显示任务被延迟。后来我把日志改成异步打印,问题就解决了。记住:临界区要短,中断要快,任务要稳。

好了,这一章的内容就这些。任务、调度器、临界区、上下文切换、实时性指标——这几个概念是RTOS的基石。你理解了它们,后面学信号量、消息队列、中断管理就会轻松很多。下一章我们聊聊任务间通信,那可是多任务协作的关键。