第4章:任务调度机制——抢占式调度、时间片轮转、协作式调度、调度点分析
调度机制,说白了就是RTOS的「大脑」。它决定哪个任务该跑,哪个任务该等。我做了这么多年飞控,见过太多因为调度没搞明白导致炸机的案例。嗯,这一章咱们就把调度这件事彻底讲透。
4.1 三种调度方式,你选哪个?
RTOS里常见的调度方式有三种:抢占式、时间片轮转、协作式。很多初学者以为「抢占式就是最好的」,其实不然。飞控这种硬实时系统,选错调度方式,后果很严重。
4.1.1 抢占式调度
抢占式调度,说白了就是「高优先级任务来了,低优先级任务立刻让路」。不管低优先级任务执行到哪一步,只要高优先级任务就绪,CPU立刻切换过去。
我在做Pixhawk飞控移植时遇到过一个问题:一个低优先级的姿态解算任务正在算一半,高优先级的传感器读取任务来了。如果等它算完再读传感器,那传感器数据就过时了。抢占式调度完美解决了这个问题。
核心要点:抢占式调度保证高优先级任务的实时性,但要注意优先级反转问题。
代码实现上,抢占式调度通常依赖SysTick中断或PendSV中断。每次中断发生时,内核检查是否有更高优先级的任务就绪。如果有,就保存当前任务上下文,切换到新任务。
// 抢占式调度核心逻辑(简化版)
void SysTick_Handler(void) {
// 递减当前任务的时间片
if (--current_task->remaining_ticks == 0) {
// 检查是否有更高优先级任务就绪
Task_t *higher_task = find_highest_ready_task();
if (higher_task->priority > current_task->priority) {
// 触发PendSV进行上下文切换
SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
}
}
}
4.1.2 时间片轮转
时间片轮转,就是「大家轮流用CPU,每人分一段固定时间」。同优先级的任务,每人跑一个时间片,时间到了就换下一个。
我个人习惯在飞控的「非关键任务」上使用时间片轮转。比如日志记录、状态显示这些任务,不需要实时响应,大家轮流跑就行。但注意,时间片不能设得太短——我曾经设成1ms,结果光切换任务就占了30%的CPU,得不偿失。
| 时间片长度 | 适用场景 | 注意事项 |
|---|---|---|
| 1-5ms | 高频率任务(如传感器读取) | 切换开销大,慎用 |
| 10-50ms | 中等频率任务(如控制计算) | 平衡响应与开销 |
| 100ms以上 | 低频率任务(如日志、通信) | 响应较慢,但效率高 |
我的经验:飞控中,时间片轮转只用于同优先级任务。不同优先级之间,必须用抢占式。否则一个低优先级任务占着CPU不放,高优先级任务就等死了。
4.1.3 协作式调度
协作式调度,就是「任务自己决定什么时候让出CPU」。每个任务必须主动调用类似task_yield()的函数,才会切换到下一个任务。
说实话,我在飞控里几乎不用纯协作式调度。为什么?因为飞控任务太多,万一哪个任务忘了让出CPU,整个系统就卡死了。我曾经在一个小项目里试过协作式,结果一个while循环忘了加yield,无人机直接失控——嗯,从那以后我再也不敢在飞控里用纯协作式了。
但协作式有个优点:没有抢占式那么复杂的临界区保护问题。如果你写的是简单的、任务数量固定的系统,协作式反而更简单。
4.2 调度点分析——什么时候切换?
调度点,就是「系统决定要不要切换任务」的时刻。搞清楚调度点,你才能真正理解RTOS的行为。
常见的调度点有这几个:
- 时间中断:SysTick触发时,检查时间片是否用完
- 任务创建/删除:新任务加入或旧任务退出时,重新评估优先级
- 任务阻塞/唤醒:任务等待信号量、消息队列等资源时
- 中断退出:中断服务程序执行完毕后,检查是否有更高优先级任务就绪
我重点说说「中断退出」这个调度点。很多初学者以为中断退出后一定回到被中断的任务,其实不一定。如果中断里唤醒了一个更高优先级的任务,那退出中断后应该直接切换到那个高优先级任务。这就是「中断退出调度」的概念。
避坑指南:我曾经在STM32F4上移植FreeRTOS时,忘了在中断退出时触发PendSV。结果中断唤醒了高优先级任务,但系统还是回到低优先级任务继续跑。高优先级任务一直得不到执行,飞控直接炸了。检查了三天才发现是调度点没处理好。
4.3 三种调度方式的混合使用
实际飞控系统中,很少只用一种调度方式。我常用的策略是:
- 高优先级任务(如传感器读取、控制计算):抢占式调度,保证实时性
- 同优先级任务(如多个通信任务):时间片轮转,公平分配CPU
- 低优先级任务(如日志、调试):协作式调度,空闲时才跑
这种混合调度方式,既保证了关键任务的实时性,又让非关键任务能充分利用CPU空闲时间。嗯,说白了就是「好钢用在刀刃上」。
4.4 调度相关的常见问题
最后,我总结几个调度相关的常见坑:
- 优先级反转:低优先级任务持有高优先级任务需要的资源,导致高优先级任务被阻塞。解决方案:优先级继承或优先级天花板协议。
- 调度抖动:每次调度的时间不确定,导致任务执行时间不稳定。解决方案:减少临界区长度,优化中断响应。
- 任务饿死:低优先级任务永远得不到CPU。解决方案:使用时间片轮转或优先级老化机制。
我记得有一次,一个同事的飞控在高速飞行时突然失控。查了半天,发现是一个低优先级的GPS数据处理任务占用了互斥锁,导致高优先级的姿态控制任务被阻塞。这就是典型的优先级反转。后来我们加上了优先级继承,问题就解决了。
好了,调度机制就讲到这里。下一章咱们聊聊任务间通信——信号量、消息队列、事件标志组,这些才是RTOS的「血管」。