UCOS-III 任务调度与任务状态
一、任务调度简介
1. 什么是任务调度
调度器的作用,就是决定 当前应该让哪个任务占用 CPU 运行。
在 UCOS-III 中,任务调度的核心目标是:
- 保证高优先级任务优先运行
- 在同优先级任务之间实现公平调度
- 当任务状态变化时,能够及时切换任务
二、UCOS-III 的两种任务调度方式
UCOS-III 支持两种任务调度方式:
- 抢占式调度
- 时间片调度
1. 抢占式调度
基本概念
抢占式调度主要针对 不同优先级任务。 高优先级任务一旦进入就绪态,就可以立即抢占低优先级任务的 CPU。
特点
- 高优先级任务优先执行
- 只要有更高优先级任务就绪,当前任务就会被抢占
- 低优先级任务不能阻止高优先级任务运行
- 被抢占任务会回到就绪态,等待下次调度
例子
假设有 3 个任务:
Task1:最低优先级Task2:中等优先级Task3:最高优先级
运行过程:
Task1先运行Task2进入就绪态后,抢占Task1Task3进入就绪态后,再抢占Task2- 若
Task3挂起或延时,系统重新选择当前最高优先级就绪任务,可能切回Task2 - 若
Task2也不能运行,再切回Task1
结论
抢占式调度的本质是:
始终让当前所有就绪任务中优先级最高的任务运行。
2. 时间片调度
基本概念
时间片调度主要针对 相同优先级任务。 当多个同优先级任务都处于就绪态时,调度器按时间片轮流让它们运行。
特点
- 仅适用于同优先级任务
- 同优先级任务轮流执行
- 每个任务运行一个时间片后切换
- 体现的是同优先级任务之间的公平性
例子
假设有 3 个任务:
Task1Task2Task3
并且:
- 三者优先级相同
- 都在同一个就绪列表中
- 每个任务默认时间片为
100
运行过程:
Task1运行 100 个 tick 后切换到Task2Task2运行 100 个 tick 后切换到Task3- 若
Task3运行中挂起或等待事件,则不再继续消耗时间片,直接切换到下一个可运行任务 - 之后系统再回到
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 转去执行中断服务函数时,系统处于 中断态。
特点:
- 中断态不是普通任务运行
- 中断结束后,系统重新决定恢复哪个任务
- 若中断期间有更高优先级任务进入就绪态,则中断退出后可能不会回到原任务,而是直接切换到更高优先级任务
五、任务状态之间的转换规律
基本规律
- 新创建任务,初始状态都是就绪态
- 被删除任务,会转为休眠态
- 只有就绪态和中断恢复后,任务才可能进入运行态
- 挂起态、休眠态不能直接进入运行态,必须先回到就绪态
常见状态转换
- 创建任务:
就绪态 - 获得 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 从运行态进入挂起态,并加入 DataSem 的 PendList:
DataSem PendList
HeadPtr | v+-------+| TaskB |+-------+TaskA 释放信号量
OSSemPost(&DataSem, OS_OPT_POST_1, &err);系统会执行:
- 从
PendList移除TaskB - 将
TaskB放入OSRdyList - 若
TaskB优先级更高,则立即运行
2. 等待消息队列 Queue
场景
TaskA:接收串口数据TaskB:处理数据
TaskB 等待消息
msg = OSQPend(&MsgQueue, 0, OS_OPT_PEND_BLOCKING, &msg_size, 0, &err);如果队列为空,TaskB 会进入 MsgQueue 的 PendList:
MsgQueue PendList
HeadPtr | v+-------+| TaskB |+-------+TaskA 发送消息
OSQPost(&MsgQueue, data, sizeof(data), OS_OPT_POST_FIFO, &err);系统会执行:
- 从
PendList取出TaskB - 将
TaskB放入OSRdyList 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 会进入 LCDMutex 的 PendList:
LCDMutex PendList
HeadPtr | v+-------+| TaskB |+-------+TaskA 释放锁
OSMutexPost(&LCDMutex, OS_OPT_POST_NONE, &err);系统会执行:
- 将
TaskB从PendList移除 - 让
TaskB获得互斥锁 - 将
TaskB放入OSRdyList
七、总结
UCOS-III 的任务调度可以概括为两层机制:
- 不同优先级任务:采用 抢占式调度
- 相同优先级任务:采用 时间片轮转调度
任务运行过程中,系统主要围绕以下三类链表管理任务状态:
OSRdyList:管理就绪任务OSTickList:管理延时等待任务PendList:管理事件等待任务
因此可以把 UCOS-III 的调度逻辑概括为:
高优先级优先,同优先级轮转;等待条件满足后重新进入就绪态,再由调度器决定是否运行。
UCOSIII 专栏
第 1 篇 / 共 4 篇
下一篇
UCOSIII 专栏 02:文件结构与 STM32 移植