4. 任务调度算法:优先级抢占调度、时间片轮转调度、协作式调度、调度器挂起与恢复
调度算法,说白了就是RTOS的「大脑」。它决定哪个任务先跑,哪个任务后跑。我做了这么多年无人机飞控,见过太多因为调度策略选错导致的炸机事故。嗯,今天咱们就把这四种调度方式掰开揉碎了讲清楚。
4.1 优先级抢占调度
这是RTOS里最常用的调度方式。每个任务都有一个优先级,数字越小优先级越高(或者反过来,各家RTOS定义不同)。高优先级的任务一旦就绪,立马抢走CPU。
我在项目中遇到过一个问题:有个新手把传感器采集任务优先级设得比控制任务还高。结果传感器数据采集频率上去了,但控制周期反而被拖垮了。无人机在天上晃得跟喝醉了似的。
举个例子,FreeRTOS里创建任务时指定优先级:
// 创建高优先级任务 - 控制任务
xTaskCreate(control_task, "Control", 256, NULL, 5, NULL);
// 创建低优先级任务 - 日志记录
xTaskCreate(log_task, "Log", 256, NULL, 1, NULL);
// 创建中等优先级任务 - 通信
xTaskCreate(comm_task, "Comm", 256, NULL, 3, NULL);
这里控制任务优先级5最高,通信任务3次之,日志任务1最低。只要控制任务就绪,它就会立即抢占其他任务。为什么会这样?因为飞控的实时性要求决定了控制周期必须稳定。
4.2 时间片轮转调度
同优先级的任务怎么分CPU?时间片轮转。每个任务分到一个时间片,用完就换下一个。有点像大家排队打饭,每人固定吃10秒。
我个人习惯在无人机的地面站通信任务里用这个。多个通信任务优先级相同,轮流发送数据包,谁也不饿着谁。
| 调度方式 | 适用场景 | 缺点 |
|---|---|---|
| 优先级抢占 | 实时性要求高的任务 | 可能导致低优先级任务饿死 |
| 时间片轮转 | 同优先级任务公平共享CPU | 上下文切换开销大 |
在RT-Thread里配置时间片长度:
// 创建两个同优先级任务,时间片各10个tick
rt_thread_t task1 = rt_thread_create("task1", task1_entry, RT_NULL, 512, 10, 10);
rt_thread_t task2 = rt_thread_create("task2", task2_entry, RT_NULL, 512, 10, 10);
4.3 协作式调度
这个就有点「佛系」了。任务自己决定什么时候让出CPU。你不主动让,别人就等着。说白了就是靠自觉。
我曾经在一个资源极度受限的MCU上用过协作式调度。那芯片只有4KB RAM,跑抢占式调度光任务栈就撑爆了。协作式调度省去了很多上下文切换的开销。
典型的协作式调度写法:
void task_a(void) {
while(1) {
// 做点事情
do_something();
// 主动让出CPU
task_yield();
}
}
void task_b(void) {
while(1) {
// 做点别的事情
do_other_thing();
// 主动让出CPU
task_yield();
}
}
4.4 调度器挂起与恢复
有时候我们需要暂时「冻结」调度器。比如在临界区操作共享数据时,不希望被其他任务打断。这时候就要挂起调度器。
你想想看,如果正在更新一个全局变量,突然被高优先级任务抢占了,那个变量可能处于不一致状态。这就是典型的竞态条件。
FreeRTOS里的做法:
// 挂起调度器
vTaskSuspendAll();
// 临界区操作
gps_data.latitude = new_lat;
gps_data.longitude = new_lon;
gps_data.altitude = new_alt;
// 恢复调度器
xTaskResumeAll();
但要注意,挂起调度器不等于关中断。中断仍然可以响应。如果你需要连中断都屏蔽,那就得用关中断指令了。
- 调度器挂起:任务切换被禁止,但中断仍可响应
- 关中断:连中断都被屏蔽,系统完全不受打扰
我建议在无人机飞控里,调度器挂起的时间越短越好。为什么?因为挂起期间,高优先级任务无法及时响应。比如紧急降落指令可能被延迟处理。我曾经见过一个案例,调度器挂起了50ms,结果错过了IMU数据采集窗口,导致姿态解算出现偏差。
4.5 四种调度策略的实战选择
说了这么多,到底怎么选?我总结一下:
- 优先级抢占调度:无人机飞控的主力。控制任务、传感器采集任务用高优先级,日志、调试用低优先级。
- 时间片轮转调度:同优先级的数据处理任务。比如多个传感器数据融合任务,大家轮流处理。
- 协作式调度:资源极度受限的场景。或者任务之间配合非常默契,不需要抢占。
- 调度器挂起:保护临界区数据。但记住,挂起时间要控制在微秒级。
最后说一句,调度算法没有银弹。你得根据具体硬件资源、实时性要求、任务特点来灵活搭配。做无人机这么多年,我最大的体会就是:调度策略选对了,飞控就稳了一半。