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);
我的经验: 时间片别设太短,否则频繁切换浪费CPU。也别太长,否则实时性变差。无人机上我一般设5-20个系统tick。

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();
    }
}
注意: 协作式调度最怕「流氓任务」。如果一个任务死循环不释放CPU,整个系统就卡死了。我在早期一个项目中吃过这个亏,一个传感器驱动里有个while等待标志位,结果标志位一直没置起来...嗯,从那以后我写协作式任务都会加看门狗。

4.4 调度器挂起与恢复

有时候我们需要暂时「冻结」调度器。比如在临界区操作共享数据时,不希望被其他任务打断。这时候就要挂起调度器。

你想想看,如果正在更新一个全局变量,突然被高优先级任务抢占了,那个变量可能处于不一致状态。这就是典型的竞态条件。

FreeRTOS里的做法:

// 挂起调度器
vTaskSuspendAll();

// 临界区操作
gps_data.latitude = new_lat;
gps_data.longitude = new_lon;
gps_data.altitude = new_alt;

// 恢复调度器
xTaskResumeAll();

但要注意,挂起调度器不等于关中断。中断仍然可以响应。如果你需要连中断都屏蔽,那就得用关中断指令了。

关键区别:
  • 调度器挂起:任务切换被禁止,但中断仍可响应
  • 关中断:连中断都被屏蔽,系统完全不受打扰

我建议在无人机飞控里,调度器挂起的时间越短越好。为什么?因为挂起期间,高优先级任务无法及时响应。比如紧急降落指令可能被延迟处理。我曾经见过一个案例,调度器挂起了50ms,结果错过了IMU数据采集窗口,导致姿态解算出现偏差。

4.5 四种调度策略的实战选择

说了这么多,到底怎么选?我总结一下:

  1. 优先级抢占调度:无人机飞控的主力。控制任务、传感器采集任务用高优先级,日志、调试用低优先级。
  2. 时间片轮转调度:同优先级的数据处理任务。比如多个传感器数据融合任务,大家轮流处理。
  3. 协作式调度:资源极度受限的场景。或者任务之间配合非常默契,不需要抢占。
  4. 调度器挂起:保护临界区数据。但记住,挂起时间要控制在微秒级。
避坑指南: 我曾经在无人机上同时用了抢占式和时间片轮转。结果发现一个低优先级的时间片任务占用了太多CPU,导致高优先级任务响应变慢。后来我把所有实时性要求高的任务都设成了不同优先级,彻底避免时间片干扰。

最后说一句,调度算法没有银弹。你得根据具体硬件资源、实时性要求、任务特点来灵活搭配。做无人机这么多年,我最大的体会就是:调度策略选对了,飞控就稳了一半。