嵌入式实时内核基础:任务、调度器、上下文切换、临界区与互斥

好,咱们今天聊点硬核的。实时内核,说白了就是操作系统的“微缩心脏”。你写裸机程序时,主循环里一个个函数轮着调,这叫“超级循环”。但到了复杂系统——比如同时要处理传感器、通信、显示——超级循环就扛不住了。这时候,你需要一个内核来帮你管“谁先跑、谁后跑、跑多久”。

我个人习惯把实时内核比作一个“交通调度中心”。任务就是路上的车,调度器就是红绿灯和交警,上下文切换就是车辆变道。嗯,咱们一个一个拆开看。

任务:内核里的“最小执行单元”

任务(Task),在RTOS里就是一段独立的、无限循环的代码。每个任务都有自己的栈、优先级和状态。

我刚开始用RTOS时犯过一个低级错误:把所有功能塞进一个任务里,结果系统响应慢得像蜗牛。后来才明白,任务拆分要遵循“高内聚、低耦合”原则——一个任务只干一件事。

任务通常有四种状态:

状态 说明 典型场景
运行态 CPU正在执行该任务 任务获得CPU使用权
就绪态 任务可以运行,但CPU被别的任务占着 等待调度器分配CPU
阻塞态 任务在等待某个事件(信号量、队列、延时) 等待外设数据、等待定时器
挂起态 任务被显式暂停,不参与调度 调试、低功耗模式
避坑指南: 我曾经在STM32上写了一个任务,栈只给了128字节。结果任务里调了个printf,直接栈溢出,系统跑飞。后来我养成了习惯:每个任务至少给512字节栈,复杂任务给1KB以上。别省这点内存,省出来的都是bug。

调度器:谁先跑?跑多久?

调度器是内核的大脑。它决定哪个就绪任务获得CPU。常见的调度策略有两种:

  • 优先级抢占式调度:高优先级任务随时可以打断低优先级任务。实时性高,但容易出“优先级反转”。
  • 时间片轮转调度:同优先级任务轮流跑,每人分一个时间片。公平,但实时性一般。

我参与过一个工业控制项目,要求响应延迟不超过1ms。我们用了优先级抢占式调度,把电机控制任务设为最高优先级,通信任务设为中等,显示任务设为最低。效果不错,但调试时发现一个问题:通信任务偶尔会抢在电机任务之前跑——后来查出来是中断里释放了信号量,导致通信任务优先级被临时提升。这就是典型的“优先级反转”。

核心原则: 实时系统里,任务的优先级分配要遵循“速率单调调度”(RMS)理论——周期越短的任务,优先级越高。这不是拍脑袋定的,是有数学证明的。

上下文切换:换人如换刀

上下文切换,就是暂停当前任务,保存它的现场(寄存器、栈指针、PC),然后恢复另一个任务的现场。这个过程必须快,越快越好。

你想想看,如果一次上下文切换要花100微秒,而你的系统有10个任务,每个任务跑1ms,那光切换就占了10%的CPU时间。这太浪费了。

典型的上下文切换流程:

  1. 触发异常或中断(比如PendSV)
  2. 保存当前任务的寄存器到它的栈中
  3. 更新当前任务的控制块(TCB)
  4. 从就绪队列中选出下一个任务
  5. 恢复新任务的寄存器
  6. 返回异常,继续执行新任务
// 伪代码:上下文切换核心逻辑
void PendSV_Handler(void) {
    // 保存当前任务上下文
    save_context(current_task->stack_ptr);
    
    // 更新任务状态
    current_task->state = READY;
    
    // 选择下一个任务
    next_task = scheduler_pick_next();
    
    // 切换任务
    current_task = next_task;
    current_task->state = RUNNING;
    
    // 恢复新任务上下文
    restore_context(current_task->stack_ptr);
}
注意: 上下文切换期间,必须关中断!否则切换到一半,另一个中断进来,会把栈搞乱。我见过一个案例,工程师没关中断做切换,结果系统跑着跑着就死机,查了三天才发现是中断嵌套导致栈指针错乱。

临界区与互斥:别让共享资源打架

临界区,就是一段不能被打断的代码。通常是因为这段代码在操作共享资源——比如全局变量、外设寄存器、内存池。

互斥,就是保证同一时间只有一个任务能进入临界区。实现互斥的手段有:

  • 关中断:最简单粗暴,但关太久会影响实时性。
  • 信号量:轻量级同步机制,适合短临界区。
  • 互斥量:带优先级继承的信号量,能解决优先级反转。
  • 锁:自旋锁、读写锁等,适合多核场景。

我建议:能用关中断解决的,就别用信号量。关中断的开销最小,但时间要短——一般不超过几十微秒。如果临界区太长,比如操作一个大数据结构,那就用互斥量。

方法 开销 适用场景 风险
关中断 极低 短临界区(<10μs) 影响中断响应
信号量 低 任务间同步 优先级反转
互斥量 中 保护共享资源 死锁
关调度器 中 任务级临界区 影响任务调度
个人经验: 我曾经在一个项目中,用信号量保护一个环形缓冲区。结果高优先级任务等信号量时,低优先级任务占着缓冲区不放,导致高优先级任务被阻塞了2ms。后来换成互斥量(带优先级继承),问题解决。所以,如果任务优先级差异大,优先用互斥量。

知识结构图:实时内核核心要素

下面这张图,把任务、调度器、上下文切换、临界区的关系串起来了。你可以把它当作本章的“思维导图”。

实时内核核心要素关系图 实时内核核心要素关系图 实时内核 任务管理 调度器 上下文切换 临界区与互斥 就绪/运行/阻塞 优先级/栈/TCB 优先级抢占 时间片轮转 保存现场 恢复现场 关中断 信号量/互斥量 说明:四个模块相互依赖——任务由调度器管理,调度器触发上下文切换, 临界区与互斥保证任务间共享资源的安全访问。

这张图里,四个模块是相互咬合的。任务管理提供“执行单元”,调度器决定“谁跑”,上下文切换负责“换人”,临界区与互斥保证“不打架”。少了任何一个,实时系统都转不起来。

嗯,这一章的内容就到这里。记住:任务要拆得合理,调度策略要选对,上下文切换要快,临界区要短。这四个点抓好了,你的RTOS应用就稳了一半。