第4章:任务优先级设计

优先级设计这事儿,说简单也简单,说复杂能让你调一整天。我刚开始做飞控那会儿,觉得优先级不就是给任务排个序嘛,高的先跑,低的后跑。直到第一次遇到无人机空中突然「卡住」——嗯,就是那种电机还在转,但姿态完全失控的状态。后来查了三天,才发现是优先级反转惹的祸。

4.1 优先级反转问题

先说说什么是优先级反转。你想想看,有三个任务:高优先级任务A、中优先级任务B、低优先级任务C。正常情况下,A应该最先执行对吧?但现实往往很骨感。

假设C先拿到了某个共享资源(比如I2C总线),然后A来了,想用同一个资源。A发现资源被占着,只能等C释放。这时候B也来了,B不需要那个资源,所以B直接抢占了C的CPU时间。结果呢?A在等C,C在等B,B在跑自己的代码。高优先级的A,反而被中优先级的B给「踩」了。

核心问题:高优先级任务间接被低优先级任务阻塞,而中优先级任务趁机「插队」。这就是优先级反转。

我在项目中遇到过最典型的案例:飞控的传感器数据采集任务(高优先级)被一个日志记录任务(低优先级)阻塞,中间还夹着一个通信任务(中优先级)。结果传感器数据延迟了50ms,飞控直接触发保护性降落。你说冤不冤?

4.2 优先级继承协议

怎么解决?一个经典方案是优先级继承协议。说白了就是:当低优先级任务C持有高优先级任务A需要的资源时,C临时「继承」A的优先级。这样B就抢不过C了,C能尽快跑完释放资源。

我习惯在FreeRTOS里这么配:

// 创建互斥量时使用优先级继承
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();

// 任务A(高优先级)
void TaskA(void *pvParameters) {
    while(1) {
        // 尝试获取互斥量
        if(xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
            // 访问共享资源
            readSensorData();
            xSemaphoreGive(xMutex);
        }
    }
}

// 任务C(低优先级)
void TaskC(void *pvParameters) {
    while(1) {
        xSemaphoreTake(xMutex, portMAX_DELAY);
        // 写日志到SD卡(耗时操作)
        writeLogData();
        xSemaphoreGive(xMutex);
    }
}

避坑指南:我曾经在STM32F4上用过优先级继承,发现中断服务函数里千万别用带继承的互斥量。中断上下文没有任务优先级的概念,用了反而会死锁。记住:ISR里只用信号量,别用互斥量。

4.3 优先级天花板协议

另一个方案是优先级天花板协议。这个更「暴力」一点:每个共享资源都设定一个「天花板优先级」——也就是所有可能访问这个资源的任务中,最高的那个优先级。谁拿到资源,谁的优先级就立刻升到天花板。

举个例子:

共享资源 可能访问的任务 天花板优先级
I2C总线 传感器任务(50)、日志任务(20) 50
GPS数据缓冲区 导航任务(45)、显示任务(15) 45
PWM控制寄存器 控制任务(60)、校准任务(30) 60

这样做的好处是:实现简单,不需要动态调整优先级。坏处是:可能造成不必要的优先级提升。比如日志任务只是偶尔写一下I2C,但每次都得顶着50的优先级跑,有点「大炮打蚊子」的感觉。

注意:优先级天花板协议在静态配置的系统中特别好用。飞控里很多资源访问模式是固定的,用天花板协议反而比继承协议更可靠。我个人在PX4的uORB通信中就见过类似的设计思路。

4.4 实际项目中的优先级分配策略

好了,理论说完了,咱们聊聊实战。飞控系统里任务优先级怎么分?我一般按这个思路来:

  1. 安全关键任务放最高——比如看门狗喂狗、紧急降落、故障检测。这些任务优先级设在80-90(假设0最低,100最高)。
  2. 控制环路任务次之——姿态控制、位置控制、电机混控。优先级设在60-75。这些任务有硬实时要求,延迟超过1ms就可能炸机。
  3. 传感器采集任务再低一点——IMU读取、GPS解析、气压计采集。优先级设在40-55。虽然也重要,但可以容忍偶尔的抖动。
  4. 通信和日志任务放最低——数传电台、SD卡写入、状态显示。优先级设在10-30。这些任务延迟几百毫秒都没事。

你可能会问:为什么控制环路比传感器采集优先级还高?嗯,这里有个细节。传感器数据通常有DMA或FIFO缓冲,丢几帧数据还能插值补回来。但控制环路一旦被阻塞,输出PWM波形就会乱,电机直接抖起来。我吃过这个亏,所以现在都是控制任务优先。

一个实用的分配原则:任务的优先级 = 任务的「破坏力」× 任务的「时效性」。破坏力越大、时效性越强,优先级越高。飞控里控制任务的破坏力是100%,日志任务最多就是丢几条数据。

最后分享一个我踩过的坑。有一次做多旋翼飞控,我把电池电压监测任务的优先级设得比控制任务还高。结果电池电压一波动,监测任务疯狂抢占CPU,控制任务被饿死,飞机直接翻了个跟头。后来我把监测任务改成每100ms执行一次,优先级降到20,问题就解决了。

优先级设计没有银弹。每个系统都有自己的脾气,关键是多测试、多观察。用RTOS的跟踪工具看看任务的实际运行时间、阻塞时间、抢占次数,比纸上谈兵靠谱得多。

任务优先级设计核心逻辑 优先级反转 高优先级任务被低优先级 任务间接阻塞 中优先级任务趁机抢占 优先级继承协议 低优先级任务临时继承 高优先级任务的优先级 动态调整,实现简单 优先级天花板协议 资源设定天花板优先级 谁拿资源谁升到天花板 静态配置,适合飞控 实际项目优先级分配策略 安全关键任务 看门狗、紧急降落 优先级:80-90 控制环路任务 姿态、位置控制 优先级:60-75 传感器采集任务 IMU、GPS、气压计 优先级:40-55 通信日志任务 数传、SD卡写入 优先级:10-30 核心原则:破坏力 × 时效性 = 优先级
专注资料整理