任务管理基础:任务的概念、任务状态与优先级

大家好,我是你们的讲师。今天我们来聊聊实时操作系统里最核心的东西——任务管理。说白了,任务就是你要让CPU干的一件件具体的事。在机器人控制器里,任务无处不在:读取传感器数据、计算运动学逆解、发送PWM波给电机……每一件都是任务。

任务到底是什么?

我习惯把任务理解成「一个独立的执行流」。它有自己的代码、自己的栈空间、自己的优先级。在裸机编程里,我们通常写一个超级循环:

while(1) {
    read_sensor();
    compute_control();
    send_motor();
}

嗯,这种写法在简单场景下没问题。但一旦系统复杂起来,比如你要同时处理多个传感器、还要响应紧急停止信号,超级循环就捉襟见肘了。这时候,RTOS的任务机制就派上用场了。

每个任务在RTOS里其实就是一个无限循环的函数:

void task_sensor(void *param) {
    while(1) {
        read_imu();
        vTaskDelay(pdMS_TO_TICKS(10)); // 每10ms读一次
    }
}

void task_control(void *param) {
    while(1) {
        compute_pid();
        vTaskDelay(pdMS_TO_TICKS(5)); // 每5ms算一次
    }
}

你看,两个任务各自独立运行,互不干扰。这就是任务的基本概念——一个可以独立调度、独立运行的代码单元。

核心要点:任务 = 独立的执行流 + 独立的栈空间 + 独立的优先级

任务的五种状态

任务在运行过程中,状态会不断切换。我当年刚接触RTOS时,最困惑的就是这些状态。其实没那么复杂,你想想看,一个任务无非就这几种情况:

  • 运行态(Running):CPU正在执行这个任务的代码。同一时刻,单核CPU上只能有一个任务处于运行态。
  • 就绪态(Ready):任务已经准备好运行,但CPU正在忙别的任务。说白了就是「排队等CPU」。
  • 阻塞态(Blocked):任务在等某个事件发生。比如等延时结束、等信号量、等消息队列。这时候任务不消耗CPU。
  • 挂起态(Suspended):任务被强制暂停,需要别人把它唤醒。我记得在FreeRTOS里,挂起态的任务不会被调度器考虑。

还有一个状态叫「初始态」,就是任务刚创建但还没开始运行。不过很多教材把它归到就绪态里。

状态切换的典型场景是这样的:

// 任务A:等待传感器数据
void task_A(void *param) {
    while(1) {
        // 运行态:开始执行
        wait_for_sensor();  // 阻塞态:等数据
        // 数据来了,变成就绪态
        // 调度器选中它,变成运行态
        process_data();
    }
}

我的经验:调试任务状态时,我习惯在关键切换点加GPIO翻转。用示波器一看,哪个任务在跑、跑了多久,一目了然。比printf靠谱多了。

任务优先级——谁先跑?

优先级决定了任务在就绪队列里的排序。数值越小优先级越高?还是越大越高?这取决于RTOS的实现。FreeRTOS里数值越大优先级越高,而uC/OS-II里数值越小优先级越高。我个人习惯用FreeRTOS,所以下面都以「数值越大优先级越高」为例。

优先级的设计,说白了就是回答一个问题:「紧急的事情能不能插队?」

举个例子,在机器人控制器里:

任务 优先级 说明
紧急停止 最高(5) 一旦触发,必须立即响应
电机控制 高(4) 需要实时性,周期1ms
传感器采集 中(3) 周期10ms,可以容忍少量延迟
通信处理 低(2) 不紧急,有空再处理
日志记录 最低(1) 后台任务,优先级最低

我曾经在一个项目中,把电机控制任务的优先级设得和传感器采集一样高。结果呢?两个任务互相抢CPU,电机控制偶尔被延迟,导致机器人抖动。后来我把电机控制优先级提高一级,问题就解决了。

避坑指南:我曾经把紧急停止任务的优先级设得过高,结果它频繁抢占其他任务,导致系统整体吞吐量下降。后来我意识到:优先级不是越高越好,够用就行。紧急停止任务平时不触发,优先级高没问题;但如果一个高频任务优先级太高,反而会拖慢系统。

优先级反转——一个经典问题

说到优先级,就不得不提优先级反转。这是个经典问题,面试常考,实际项目中也经常遇到。

场景是这样的:

  • 任务A(高优先级)和任务C(低优先级)共享一个资源
  • 任务B(中优先级)不共享资源,但一直在跑
  • 任务C拿到了资源,然后被任务B抢占
  • 任务A想拿资源,但资源被任务C占着,只能等
  • 任务B一直跑,任务C一直没机会释放资源
  • 结果:高优先级的任务A,被中优先级的任务B「反转」了

嗯,听起来有点绕。我当年第一次遇到这个问题时,调试了整整两天。现象就是:紧急停止按钮按下去,机器人过了好几百毫秒才响应。后来一查,就是优先级反转。

解决办法?FreeRTOS里可以用互斥量(Mutex)代替二值信号量,因为互斥量内置了优先级继承机制。简单说就是:当高优先级任务等资源时,暂时把持有资源的低优先级任务的优先级提上来,让它快点跑完释放资源。

记住:共享资源用互斥量,不要用信号量。这是血的教训换来的。

任务状态转换图

最后,我画个简单的状态转换逻辑,你感受一下:

创建任务 → 就绪态
                ↓
就绪态 → 运行态(调度器选中)
运行态 → 就绪态(被更高优先级任务抢占)
运行态 → 阻塞态(等待事件/延时)
阻塞态 → 就绪态(事件到达/延时结束)
运行态 → 挂起态(主动调用vTaskSuspend)
挂起态 → 就绪态(其他任务调用vTaskResume)

实际项目中,80%的时间任务都在就绪态和阻塞态之间切换。运行态的时间其实很短,因为RTOS的调度器会尽量让任务「小步快跑」——每次只跑一小段,然后让出CPU。

好了,任务管理的基础就讲到这里。下一节我们会深入讨论任务创建和删除的细节,以及任务栈大小的估算方法。那个话题更有意思,到时候见。