UCOSIII 专栏 01:任务调度与任务状态

从任务调度入口开始梳理 UCOSIII 的任务状态、调度方式和优先级运行逻辑,作为整套专栏的开篇。

周二 2月 03 2026
2379 字 · 10 分钟

UCOS-III 任务调度与任务状态

一、任务调度简介

1. 什么是任务调度

调度器的作用,就是决定 当前应该让哪个任务占用 CPU 运行

在 UCOS-III 中,任务调度的核心目标是:

  • 保证高优先级任务优先运行
  • 在同优先级任务之间实现公平调度
  • 当任务状态变化时,能够及时切换任务

二、UCOS-III 的两种任务调度方式

UCOS-III 支持两种任务调度方式:

  • 抢占式调度
  • 时间片调度

1. 抢占式调度

基本概念

抢占式调度主要针对 不同优先级任务。 高优先级任务一旦进入就绪态,就可以立即抢占低优先级任务的 CPU。

特点

  • 高优先级任务优先执行
  • 只要有更高优先级任务就绪,当前任务就会被抢占
  • 低优先级任务不能阻止高优先级任务运行
  • 被抢占任务会回到就绪态,等待下次调度

例子

假设有 3 个任务:

  • Task1:最低优先级
  • Task2:中等优先级
  • Task3:最高优先级

运行过程:

  1. Task1 先运行
  2. Task2 进入就绪态后,抢占 Task1
  3. Task3 进入就绪态后,再抢占 Task2
  4. Task3 挂起或延时,系统重新选择当前最高优先级就绪任务,可能切回 Task2
  5. Task2 也不能运行,再切回 Task1

结论

抢占式调度的本质是:

始终让当前所有就绪任务中优先级最高的任务运行。


2. 时间片调度

基本概念

时间片调度主要针对 相同优先级任务。 当多个同优先级任务都处于就绪态时,调度器按时间片轮流让它们运行。

特点

  • 仅适用于同优先级任务
  • 同优先级任务轮流执行
  • 每个任务运行一个时间片后切换
  • 体现的是同优先级任务之间的公平性

例子

假设有 3 个任务:

  • Task1
  • Task2
  • Task3

并且:

  • 三者优先级相同
  • 都在同一个就绪列表中
  • 每个任务默认时间片为 100

运行过程:

  1. Task1 运行 100 个 tick 后切换到 Task2
  2. Task2 运行 100 个 tick 后切换到 Task3
  3. Task3 运行中挂起或等待事件,则不再继续消耗时间片,直接切换到下一个可运行任务
  4. 之后系统再回到 Task1,循环执行

关键细节

  • 时间片大小由 系统时钟节拍(tick) 决定
  • 每个任务可以设置自己的时间片长度
  • 若任务未用完时间片就挂起、延时或等待,剩余时间片不会保留到下次
  • 时间片调度不是替代优先级调度,而是补充同优先级任务间的轮转机制

结论

时间片调度的本质是:

在相同优先级的多个就绪任务之间轮流分配 CPU 时间。


三、时间片与系统时钟节拍的关系

时间片是以 系统时钟节拍(tick) 为单位的。

  • 系统会周期性产生 tick
  • 每来一次 tick,当前任务的时间片计数减 1
  • 当时间片减到 0 时,如果还有同优先级任务就绪,就发生轮转切换

例如:

  • 默认时间片为 100
  • 表示任务最多连续运行 100 个 tick
  • 到第 100 个 tick 后,切换到同优先级的下一个任务

因此可以理解为:

时间片 = 任务在一次轮转中允许连续占用 CPU 的 tick 数。


四、任务状态

1. 运行态

任务正在 CPU 上执行时,称为 运行态

特点:

  • 同一时刻通常只有一个任务处于运行态
  • 运行态任务是当前被调度器选中的任务
  • 在 STM32 这类单核系统中,同一时刻只能有一个任务真正执行

2. 就绪态

任务已经具备运行条件,但尚未获得 CPU,称为 就绪态

特点:

  • 随时可以运行
  • 一旦成为当前最高优先级就绪任务,就会进入运行态
  • 就绪态任务通常保存在就绪列表中

3. 挂起态

任务因延时或等待某个事件、信号量、消息等而暂时不能运行时,进入 挂起态

特点:

  • 暂时不具备运行条件
  • 不参与 CPU 竞争
  • 等待条件满足后,才会重新进入就绪态

4. 休眠态

任务已经从系统调度管理中移除时,可理解为 休眠态,通常对应任务被删除。

特点:

  • 不再参与调度
  • 不再进入就绪列表
  • 若想再次运行,一般需要重新创建

5. 中断态

当正在运行的任务被中断打断,CPU 转去执行中断服务函数时,系统处于 中断态

特点:

  • 中断态不是普通任务运行
  • 中断结束后,系统重新决定恢复哪个任务
  • 若中断期间有更高优先级任务进入就绪态,则中断退出后可能不会回到原任务,而是直接切换到更高优先级任务

五、任务状态之间的转换规律

基本规律

  1. 新创建任务,初始状态都是就绪态
  2. 被删除任务,会转为休眠态
  3. 只有就绪态和中断恢复后,任务才可能进入运行态
  4. 挂起态、休眠态不能直接进入运行态,必须先回到就绪态

常见状态转换

  • 创建任务:就绪态
  • 获得 CPU:就绪态 -> 运行态
  • 被高优先级任务抢占:运行态 -> 就绪态
  • 延时 / 等待事件:运行态 -> 挂起态
  • 等待条件满足:挂起态 -> 就绪态
  • 被删除:活动状态 -> 休眠态
  • 被中断打断:运行态 -> 中断态
  • 中断结束:中断态 -> 某个就绪任务的运行态

六、跟踪任务状态的三大核心列表

1. 就绪列表

对应名称:OSRdyList[x],其中 x 表示任务优先级。

struct os_rdy_list {
OS_TCB *HeadPtr;
OS_TCB *TailPtr;
#if (OS_CFG_DBG_EN > 0u)
OS_OBJ_QTY NbrEntries;
#endif
};

例如,调度器启动时会取最高优先级就绪任务:

OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;

作用

保存所有 处于就绪态的任务

含义

  • 不同优先级对应不同的就绪列表
  • 同一优先级的多个任务挂在同一个 OSRdyList[x]
  • 调度器总是从所有非空就绪列表中,选出优先级最高的那个列表中的任务运行

关键理解

  • 不同优先级之间:靠抢占式调度决定
  • 相同优先级之间:在同一个 OSRdyList[x] 中进行时间片轮转

2. Tick 列表

对应名称:OSTickList

struct os_tick_list {
OS_TCB *TCB_Ptr;
#if (OS_CFG_DBG_EN > 0u)
OS_OBJ_QTY NbrEntries;
OS_OBJ_QTY NbrUpdated;
#endif
};

作用

保存所有 因延时而等待超时的任务

典型场景

  • 任务调用延时函数后进入等待
  • 每次 tick 到来时,系统检查这些任务是否超时
  • 超时后,任务再转回就绪态

本质

OSTickList 用于管理 时间相关等待任务


3. 挂起列表

对应名称:PendList

作用

保存所有 等待同步对象或事件的任务

典型场景

  • 等待信号量
  • 等待消息队列
  • 等待事件标志组
  • 等待互斥锁或其他资源

本质

PendList 用于管理 事件相关等待任务


PendList 三个典型例子

1. 等待信号量 Semaphore

场景

  • TaskA:读取传感器
  • TaskB:处理数据
  • TaskB 必须等待 TaskA 采集完成

创建信号量

OS_SEM DataSem;
OSSemCreate(&DataSem, "DataSem", 0);

初始值为 0,表示当前没有数据。

TaskB 等待信号量

OSSemPend(&DataSem, 0, OS_OPT_PEND_BLOCKING, 0, &err);

若信号量为 0,则 TaskB 从运行态进入挂起态,并加入 DataSemPendList

DataSem PendList
HeadPtr
|
v
+-------+
| TaskB |
+-------+

TaskA 释放信号量

OSSemPost(&DataSem, OS_OPT_POST_1, &err);

系统会执行:

  1. PendList 移除 TaskB
  2. TaskB 放入 OSRdyList
  3. TaskB 优先级更高,则立即运行

2. 等待消息队列 Queue

场景

  • TaskA:接收串口数据
  • TaskB:处理数据

TaskB 等待消息

msg = OSQPend(&MsgQueue, 0, OS_OPT_PEND_BLOCKING, &msg_size, 0, &err);

如果队列为空,TaskB 会进入 MsgQueuePendList

MsgQueue PendList
HeadPtr
|
v
+-------+
| TaskB |
+-------+

TaskA 发送消息

OSQPost(&MsgQueue, data, sizeof(data), OS_OPT_POST_FIFO, &err);

系统会执行:

  1. PendList 取出 TaskB
  2. TaskB 放入 OSRdyList
  3. TaskB 重新参与调度并可能立即运行

3. 等待互斥锁 Mutex

场景

两个任务都要访问 LCD,需要互斥保护。

TaskA 获得锁

OSMutexPend(&LCDMutex, 0, OS_OPT_PEND_BLOCKING, 0, &err);

此时 TaskA 获得 LCDMutex

TaskB 申请锁

OSMutexPend(&LCDMutex, 0, OS_OPT_PEND_BLOCKING, 0, &err);

由于锁已被 TaskA 占用,TaskB 会进入 LCDMutexPendList

LCDMutex PendList
HeadPtr
|
v
+-------+
| TaskB |
+-------+

TaskA 释放锁

OSMutexPost(&LCDMutex, OS_OPT_POST_NONE, &err);

系统会执行:

  1. TaskBPendList 移除
  2. TaskB 获得互斥锁
  3. TaskB 放入 OSRdyList

七、总结

UCOS-III 的任务调度可以概括为两层机制:

  • 不同优先级任务:采用 抢占式调度
  • 相同优先级任务:采用 时间片轮转调度

任务运行过程中,系统主要围绕以下三类链表管理任务状态:

  • OSRdyList:管理就绪任务
  • OSTickList:管理延时等待任务
  • PendList:管理事件等待任务

因此可以把 UCOS-III 的调度逻辑概括为:

高优先级优先,同优先级轮转;等待条件满足后重新进入就绪态,再由调度器决定是否运行。


Thanks for reading!

UCOSIII 专栏 01:任务调度与任务状态

周二 2月 03 2026
2379 字 · 10 分钟

UCOSIII 专栏

第 1 篇 / 共 4 篇