2. 任务状态机详解:就绪态、运行态、阻塞态、挂起态、休眠态及其转换条件

RTOS 内核的核心职责之一是管理多个并发任务的生命周期。每个任务在任意时刻都处于一个确定的状态,状态之间的转换由内核调度器与任务自身的系统调用共同驱动。理解任务状态机是分析优先级反转、死锁、饥饿等缺陷的前提。

2.1 五态模型概述

现代 RTOS(如 FreeRTOS、uC/OS-III、RT-Thread)普遍采用五态任务模型。下表列出了五种状态的核心语义与典型应用场景:

状态 英文 核心语义 典型场景
就绪态 Ready 任务已具备运行条件,等待调度器分配 CPU 任务创建后、延时到期后、等待的资源被释放后
运行态 Running 任务正在占用 CPU 执行指令 当前优先级最高的就绪任务被调度器选中
阻塞态 Blocked 任务因等待某事件(信号量、队列、延时)而暂停 调用 vTaskDelay()、等待信号量、等待消息队列
挂起态 Suspended 任务被显式暂停,不参与调度,不响应事件 调试阶段暂停任务、低功耗模式下冻结非关键任务
休眠态 Dormant / Deleted 任务控制块(TCB)存在但任务函数未启动或已被删除 任务创建后未调用 vTaskStartScheduler()、任务自删除
关键认知: 运行态在单核 CPU 上同一时刻只能有一个任务进入;就绪态是调度器选择下一个运行任务的唯一候选池;阻塞态是 RTOS 实现 I/O 等待和定时延迟的基础机制。

2.2 状态转换条件与流程图

任务状态之间的转换由明确的系统调用或内核事件触发。下图展示了五态之间的完整转换关系:

RTOS 五态任务状态机转换图 RTOS 五态任务状态机转换图 休眠态 (Dormant) 就绪态 (Ready) 运行态 (Running) 阻塞态 (Blocked) 挂起态 (Suspended) xTaskCreate() / 启动 调度器选择最高优先级 时间片耗尽 / 被抢占 等待事件/延时 事件到达 / 超时 vTaskSuspend() vTaskResume() vTaskSuspend() vTaskSuspend() ⚠ 转换规则要点: • 只有就绪态 → 运行态由调度器控制,其余转换均由任务主动调用 API 或内核事件触发。 • 挂起态是唯一不能自动退出的状态,必须由其他任务调用 vTaskResume() 才能恢复。

2.3 各状态详细解析

2.3.1 就绪态(Ready)

任务创建后或从其他状态恢复后进入就绪队列。调度器在每次时钟节拍(Tick)中断或事件发生后,从就绪态任务中选择优先级最高的任务投入运行。

  • 进入条件: 任务创建、阻塞超时返回、等待的资源可用、从挂起态恢复。
  • 离开条件: 被调度器选中(→运行态)、被显式挂起(→挂起态)。
  • 缺陷关联: 就绪队列中存在多个同优先级任务时,若未正确配置时间片轮转,可能导致任务饿死。

2.3.2 运行态(Running)

当前占用 CPU 的任务。在单核系统中,运行态任务的数量恒为 0 或 1。调度器通过上下文切换机制保存当前任务的寄存器现场,恢复下一个任务的现场。

  • 进入条件: 调度器从就绪态选中。
  • 离开条件: 主动调用阻塞 API(→阻塞态)、时间片耗尽或被更高优先级任务抢占(→就绪态)、主动挂起(→挂起态)、任务执行完毕自删除(→休眠态)。
  • 缺陷关联: 运行态任务若长时间不释放 CPU 且不被抢占,会阻塞低优先级任务,这是优先级反转的温床。

2.3.3 阻塞态(Blocked)

任务因等待某个条件而暂停执行,不参与调度。阻塞态是 RTOS 实现异步 I/O 和延迟执行的核心机制。

  • 进入条件: 调用 vTaskDelay()、xSemaphoreTake()、xQueueReceive() 等阻塞型 API。
  • 离开条件: 等待的事件到达、指定的超时时间到期。
  • 缺陷关联: 阻塞态任务若持有互斥锁,会导致其他等待该锁的高优先级任务也被阻塞,形成优先级反转。
💡 工程建议: 阻塞态应设置合理的超时参数(如 pdMS_TO_TICKS(100)),避免任务无限期阻塞。无限阻塞(portMAX_DELAY)仅在确定事件必然发生时才使用。

2.3.4 挂起态(Suspended)

任务被显式暂停,调度器完全忽略该任务。挂起态任务不响应任何事件,即使其等待的资源可用也不会自动恢复。

  • 进入条件: 任务自身或其他任务调用 vTaskSuspend()。
  • 离开条件: 其他任务调用 vTaskResume() 或从中断调用 xTaskResumeFromISR()。
  • 缺陷关联: 忘记恢复挂起的任务会导致功能缺失;在中断中调用 vTaskResume() 可能引发临界区问题。

2.3.5 休眠态(Dormant)

任务控制块(TCB)已分配,但任务函数尚未启动执行,或任务已通过 vTaskDelete() 自删除。休眠态任务不占用任何调度资源。

  • 进入条件: 调用 xTaskCreate() 后但调度器未启动、任务函数返回后调用 vTaskDelete()。
  • 离开条件: 调度器启动后自动进入就绪态(首次)、重新创建任务。
  • 缺陷关联: 频繁创建和删除任务会导致 TCB 内存碎片,在资源受限的 MCU 上可能触发堆溢出。

2.4 状态转换的代码示例

以下代码展示了 FreeRTOS 中任务通过不同 API 触发状态转换的典型模式:

void vTaskA(void *pvParameters) {
    for (;;) {
        // 运行态:执行计算
        process_sensor_data();

        // 运行态 → 阻塞态:等待 100ms
        vTaskDelay(pdMS_TO_TICKS(100));

        // 阻塞态 → 就绪态:延时到期后自动进入就绪队列
    }
}

void vTaskB(void *pvParameters) {
    for (;;) {
        // 运行态 → 阻塞态:等待信号量
        if (xSemaphoreTake(xSemaphore, pdMS_TO_TICKS(50)) == pdTRUE) {
            // 阻塞态 → 就绪态:信号量获取成功
            handle_shared_resource();
        } else {
            // 超时返回就绪态
            log_timeout();
        }
    }
}

void vTaskC(void *pvParameters) {
    for (;;) {
        // 运行态 → 挂起态:调试时暂停自身
        vTaskSuspend(NULL);  // NULL 表示挂起当前任务

        // 挂起态 → 就绪态:由 vTaskResume() 恢复后继续
        recalibrate();
    }
}

2.5 状态转换与优先级反转的关联

任务状态机是理解优先级反转的底层工具。以下表格总结了不同状态组合下可能触发的反转场景:

场景 高优先级任务状态 中优先级任务状态 低优先级任务状态 反转风险
持有锁阻塞 阻塞态(等待锁) 运行态(抢占 CPU) 运行态(持有锁) 高 — 中优先级任务间接阻塞高优先级任务
锁释放后恢复 就绪态 → 运行态 就绪态(被抢占) 阻塞态/就绪态 低 — 反转解除
挂起态持有锁 阻塞态(等待锁) 就绪态 挂起态(持有锁) 极高 — 锁永远不会释放
休眠态任务残留锁 阻塞态(等待锁) 就绪态 休眠态(TCB 已删除但锁未释放) 致命 — 系统死锁
⚠ 缺陷预防: 在挂起或删除任务之前,必须确保该任务已释放所有持有的互斥锁、信号量和动态内存。否则将导致其他任务永久阻塞,形成系统级死锁。

2.6 状态机在 RTOS 内核中的实现要点

从内核实现角度看,任务状态机通常由 TCB 中的一个状态字段(如 eTaskState 枚举)和多个就绪列表/阻塞列表/挂起列表共同维护:

  • 就绪列表: 每个优先级对应一个链表,调度器通过位图或优先级组快速查找最高优先级的非空链表。
  • 阻塞列表: 按超时时间排序的链表,时钟节拍中断每次递减超时计数,超时任务自动移回就绪列表。
  • 挂起列表: 存储所有被 vTaskSuspend() 暂停的任务,不参与任何调度判断。
  • 状态转换原子性: 状态切换必须在临界区(关中断)内完成,防止在转换过程中被中断或调度器打断导致状态不一致。