第4章:任务划分与优先级设计

各位同学,今天我们来聊聊实时控制系统中一个绕不开的话题——任务划分与优先级设计。说实话,我在这个领域摸爬滚打了十几年,见过太多系统因为任务划分不合理而崩溃的例子。你想想看,一个PCS系统里跑着几十上百个任务,要是优先级搞乱了,那画面简直不敢看。

4.1 任务类型的三种基本形态

我个人习惯把任务分成三类:周期任务、事件任务和紧急任务。这三类任务在PCS系统里各有各的脾气。

4.1.1 周期任务

周期任务就像你的心跳,每隔固定时间就得执行一次。比如数据采集任务,每10ms采集一次现场数据。我在项目中遇到过,有人把周期任务的周期设得太短,结果CPU负载直接飙到90%以上。

关键参数:周期任务的执行时间必须小于周期时间,否则就会出现任务超时。

// 典型的周期任务结构
void Task_DataAcquisition(void *pvParameters)
{
    TickType_t xLastWakeTime = xTaskGetTickCount();
    const TickType_t xFrequency = 10; // 10ms周期
    
    for(;;)
    {
        vTaskDelayUntil(&xLastWakeTime, xFrequency);
        // 采集数据
        ReadSensorData();
        ProcessData();
    }
}

4.1.2 事件任务

事件任务呢,说白了就是"等信号"的任务。比如报警处理任务,平时就挂在那睡觉,一旦有报警信号来了,立马醒过来干活。嗯,这里要注意:事件任务的响应时间取决于中断响应速度和任务切换开销。

4.1.3 紧急任务

紧急任务,我习惯叫它"救火队"。比如系统过压保护、急停处理这些。这类任务必须抢占一切资源,哪怕正在执行周期任务也得停下来。

避坑指南:我曾经见过一个项目,把紧急任务的优先级设得太高,结果它频繁抢占CPU,导致其他任务饿死。记住:紧急任务要少而精,执行时间要短。

4.2 优先级反转——一个让人头疼的问题

优先级反转,说白了就是一个高优先级任务被一个低优先级任务"卡脖子"了。为什么会这样?我来给你讲个真实案例。

我记得有一次调试一个化工过程控制系统,发现一个高优先级的控制任务总是莫名其妙地延迟。查了半天,原来是一个低优先级的日志写入任务占着共享资源不放,中间还有个中等优先级的任务在中间"插队"。结果高优先级任务只能干等着。

4.2.1 优先级反转的三种解决策略

策略 原理 适用场景
优先级继承 低优先级任务临时继承高优先级任务的优先级 资源竞争不频繁的场景
优先级天花板 设定资源访问的最高优先级 资源竞争频繁的场景
禁止中断 访问共享资源时关中断 临界区极短的场景
// 优先级继承示例(FreeRTOS)
void Task_HighPriority(void *pvParameters)
{
    // 尝试获取互斥锁
    if(xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE)
    {
        // 访问共享资源
        WriteToSharedMemory();
        xSemaphoreGive(xMutex);
    }
}

void Task_LowPriority(void *pvParameters)
{
    // 持有互斥锁时,如果高优先级任务在等待
    // 系统会自动提升当前任务的优先级
    xSemaphoreTake(xMutex, portMAX_DELAY);
    // 处理数据...
    xSemaphoreGive(xMutex);
}

4.3 看门狗任务——系统的最后一道防线

看门狗任务,我把它比作"系统保安"。它不干别的,就盯着系统有没有"死机"。如果某个任务卡住了,看门狗就会把系统复位。

4.3.1 看门狗任务的实现要点

  • 独立运行:看门狗任务必须独立于其他任务,最好用硬件定时器实现
  • 喂狗策略:不能只在主循环里喂狗,要在关键任务执行完后喂狗
  • 超时时间:一般设为最大任务周期的2-3倍

我的经验:我曾经在一个项目中,把看门狗的超时时间设得太短,结果系统频繁复位。后来发现是某个任务偶尔会超时,但系统整体还是健康的。所以看门狗的超时时间要留够余量。

// 看门狗任务示例
void Task_Watchdog(void *pvParameters)
{
    const TickType_t xWatchdogTimeout = pdMS_TO_TICKS(1000);
    TickType_t xLastFeedTime = xTaskGetTickCount();
    
    for(;;)
    {
        vTaskDelay(pdMS_TO_TICKS(100)); // 每100ms检查一次
        
        if((xTaskGetTickCount() - xLastFeedTime) > xWatchdogTimeout)
        {
            // 超时了,系统可能卡死
            // 记录错误日志
            LogError("Watchdog timeout!");
            // 执行系统复位
            NVIC_SystemReset();
        }
    }
}

// 其他任务在关键位置喂狗
void Task_Control(void *pvParameters)
{
    for(;;)
    {
        // 执行控制算法
        CalculatePID();
        // 喂狗
        xLastFeedTime = xTaskGetTickCount();
        // 输出控制信号
        OutputControlSignal();
    }
}

4.4 任务划分与优先级设计的实战建议

说了这么多理论,我来总结几条实战经验:

  1. 任务粒度要适中:任务太多切换开销大,任务太少实时性差。我一般控制在10-20个任务之间。
  2. 优先级不要超过7级:优先级太多反而容易出问题。我习惯用3-5级。
  3. 紧急任务要少:最多1-2个紧急任务,多了系统不稳定。
  4. 看门狗要独立:别把看门狗和业务逻辑混在一起。

核心原则:任务划分要"高内聚低耦合",优先级设计要"紧急优先、周期稳定、事件响应"。

4.5 知识体系结构图

下面这张图展示了本章的核心知识体系,我花了不少心思设计,希望能帮你理清思路。

任务划分与优先级设计知识体系 任务类型划分 周期任务 事件任务 紧急任务 优先级设计 优先级反转 优先级继承 优先级天花板 看门狗任务 独立运行 喂狗策略 超时处理 核心原则:高内聚低耦合 + 紧急优先 + 周期稳定 + 事件响应 任务类型决定任务的行为模式 优先级设计解决任务间的资源竞争 看门狗任务保障系统的可靠性 实战建议:任务10-20个,优先级3-5级,紧急任务≤2个

好了,这一章的内容就到这里。任务划分和优先级设计是PCS系统的核心,搞明白了这个,你的系统就成功了一半。记住我上面说的那些坑,别踩进去就行。