二、任务管理与调度:任务状态机、任务创建与删除、优先级调度策略、任务切换原理
好,咱们进入第二章。任务管理与调度,说白了就是操作系统的“心脏”。你想想看,一个工控机里跑着几十上百个任务,谁先执行、谁后执行、谁被暂停、谁被干掉——这些都得有个规矩。这个规矩,就是任务管理与调度。
我个人习惯把任务管理比作一个“交通指挥系统”。任务就是路上的车,调度器就是红绿灯和交警。没有它,系统就乱套了。我在项目中遇到过好几次,任务优先级设错了,结果关键的控制任务被低优先级的日志打印任务给堵死了,现场差点出事故。嗯,咱们今天就把这块彻底讲透。
2.1 任务状态机:任务的一生
每个任务从创建到销毁,会经历几个状态。实时操作系统里,任务状态一般分为四种:就绪态、运行态、阻塞态和挂起态。有些RTOS还会细分,但核心就是这四个。
| 状态 | 说明 | 典型场景 |
|---|---|---|
| 就绪态 | 任务已准备好,只等CPU | 任务创建后、延时到期后 |
| 运行态 | 正在占用CPU执行 | 调度器选中该任务 |
| 阻塞态 | 等待某个事件或资源 | 等待信号量、消息队列、延时 |
| 挂起态 | 被主动暂停,不参与调度 | 调试、低功耗模式 |
状态之间的转换,我画个简单的图给你看:
创建 → 就绪态 → 运行态 → 阻塞态
↑ ↓
← 就绪态 ←
挂起态 ←→ 就绪态/阻塞态(通过API控制)
这里有个关键点:只有就绪态的任务才能被调度器选中。阻塞态的任务,哪怕优先级再高,也不会占用CPU。我曾经调试一个系统,发现一个高优先级任务一直不执行,查了半天才发现它卡在等待一个永远不会来的信号量上——这就是典型的“优先级反转”的前兆。
2.2 任务创建与删除:生与死的艺术
任务创建,在RTOS里一般用类似 xTaskCreate() 的API。参数包括:任务函数指针、任务名称、栈大小、参数、优先级、任务句柄。
我一般这样写:
// 创建一个控制任务
TaskHandle_t xControlTaskHandle = NULL;
void vControlTask(void *pvParameters)
{
// 任务初始化
while(1)
{
// 读取传感器
// 执行控制算法
// 输出控制信号
vTaskDelay(pdMS_TO_TICKS(10)); // 10ms周期
}
}
void main(void)
{
// 创建任务
xTaskCreate(
vControlTask, // 任务函数
"ControlTask", // 任务名称(调试用)
1024, // 栈大小(单位:字)
NULL, // 参数
5, // 优先级(数值越大优先级越高)
&xControlTaskHandle // 任务句柄
);
// 启动调度器
vTaskStartScheduler();
}
任务删除呢?一般用 vTaskDelete()。但要注意:删除任务前,一定要确保它释放了所有占用的资源。比如动态分配的内存、打开的硬件外设等。否则会造成资源泄漏。
2.3 优先级调度策略:谁先谁后?
实时操作系统最常用的调度策略是基于优先级的抢占式调度。说白了就是:高优先级的任务一旦就绪,立刻抢占低优先级任务的CPU。
优先级怎么设?我有个原则:
- 硬实时任务(比如电机控制、中断响应)→ 最高优先级
- 软实时任务(比如数据采集、通信处理)→ 中等优先级
- 非实时任务(比如日志打印、界面刷新)→ 最低优先级
但这里有个坑——优先级反转。举个例子:
- 任务A(高优先级)等待任务C(低优先级)释放的资源
- 任务B(中优先级)开始运行,抢占了任务C的CPU
- 任务A只能干等,直到任务B运行完,任务C才能释放资源
结果就是:高优先级任务被中优先级任务间接阻塞了。这在实际工控系统中非常危险。
解决办法?用优先级继承协议或优先级天花板协议。大部分RTOS(比如FreeRTOS)的互斥量已经内置了优先级继承机制。所以,能用互斥量就别用二值信号量,这是我在项目里摔过跟头后学到的教训。
2.4 任务切换原理:上下文切换的秘密
任务切换,也叫上下文切换。每次切换,CPU都要做三件事:
- 保存当前任务的上下文(寄存器、栈指针、状态字等)
- 找到下一个要运行的任务(调度器决定)
- 恢复新任务的上下文(加载寄存器、栈指针等)
这个过程通常由PendSV异常(在ARM Cortex-M上)或类似的软中断来完成。为什么不用普通函数调用?因为上下文切换必须在特权模式下、原子性地完成,不能被其他中断打断。
我画个简化的切换流程:
当前任务运行中
↓
触发PendSV异常
↓
保存R4-R11、LR、PC、xPSR到当前任务栈
↓
更新当前任务TCB中的栈指针
↓
调度器选择下一个任务
↓
从新任务TCB中恢复栈指针
↓
从新任务栈中恢复R4-R11、LR、PC、xPSR
↓
跳转到新任务继续执行
这里有个性能指标:上下文切换时间。一般在几微秒到几十微秒之间。我测过一个Cortex-M4跑168MHz的系统,一次切换大约1.2μs。如果系统里任务切换太频繁(比如每1ms切换一次),那切换开销就占了1.2%的CPU时间——还算可以接受。但如果任务数太多、切换太频繁,这个开销就会显著影响实时性。
- 当前任务主动阻塞(调用delay、等待信号量等)
- 高优先级任务就绪(比如中断中释放了信号量)
- 时间片轮转(同优先级任务轮流执行)
嗯,任务管理与调度这块,说白了就是“谁该干活、谁该等着、怎么换人”。掌握了这些,你就掌握了RTOS的核心。下一章咱们聊聊同步与通信——任务之间怎么“说话”,那可是另一门学问了。