UCOS
本笔记较为详细地记录了UCOSII操作系统实现的大体流程,因为为笔者的学习笔记,故不推荐直接零基础看。推荐对操作系统有大体了解后,并希望更加深入了解时,再翻阅本笔记。
使用操作系统可以更好地实现任务间的协调,虽然运行之会消耗一部分CPU资源,但可以带来更高效的任务协调和同步,从另一层面减小了资源浪费。也可以提供更清晰的程序框架,方便协作交流。
版本:UCOSII V2.91
-
II和III的部分区别
- II中各种操作的参数与III有很多不同,III中要复杂很多。
- II中任务优先级即为任务ID,同优先级只能存在一个任务;III中同优先级可设置多个任务,初始化每个任务时可为其分配时间片,支持时间片轮转调度同优先级任务。
- II中分为消息邮箱和消息队列,而III中只有消息队列。
- III中每个任务都有内嵌信号量和内嵌消息队列。
- II中所有通讯都基于事件控制块ECB,创建和操作都是基于ECB;而III中取消了这一设计,信号量是信号量,消息队列是消息队列,创建和操作时都直接使用各自的数据结构,并没有进行统一管理。
-
UCOS中的任务堆栈
- 任务堆栈是UCOS实现任务调度的关键,其本体是ram中栈区外静态分配的一块数组,实现与内存栈相似的功能。即像内存中的栈区一样存储运行时的局部变量,并在任务调度发生时将CPU寄存器值入栈,保存当前任务的现场,在重获执行权之后恢复现场。
- 其大小应根据任务需要使用的局部变量,和函数的嵌套调用导致的所需栈空间大小进行分配。
-
UCOS任务调度
- 任务调度通过任务级调度OS_Sched()和中断级调度OSIntExit()中调用OS_TASK_SW()实现的。
- 任务调度过程的本质大概可以理解为:1.查询任务就绪表中最高优先级的任务。2.保存当前执行任务的现场,恢复目标任务的现场至CPU。 3.若为UCOS-III则再多一个时间片轮转的逻辑。
- 每次退出硬件中断时,ucos都会进行一次任务切换,其系统心跳节拍在stm32上是通过滴答计时器来实现的,周期由滴答定时器选择的时钟和滴答计时器的重装载值决定。在SysTick_Handler的硬件中断处理时会不断进行任务切换。
UCOS源码阅读笔记
下文代码源自ucosii-V2.91,根据个人理解进行了部分删减,适合用于理解其基本功能的运行逻辑。
头文件
/****************************************************************************************
* ucos_ii.h头文件部分,仅记录了一些重要部分,并不完全
****************************************************************************************/
宏定义部分
/***** 宏定义部分 *****/
// MISCELLANEOUS 杂毛宏定义
#define OS_TASK_STAT_PRIO (OS_LOWEST_PRIO - 1u) /* 统计任务优先级,倒数第二 */
#define OS_TASK_IDLE_PRIO (OS_LOWEST_PRIO) /* 空闲任务优先级,优先级最低 */
#define OS_EVENT_TBL_SIZE ((OS_LOWEST_PRIO) / 8u + 1u) /* 事件表大小 */
#define OS_RDY_TBL_SIZE ((OS_LOWEST_PRIO) / 8u + 1u) /* 就绪任务表大小 */
#define OS_TCB_RESERVED ((OS_TCB *)1) /* 用于表示某优先级任务已被留存 */
// TASK STATUS 任务状态宏定义
#define OS_STAT_RDY 0x00u /* 就绪 */
#define OS_STAT_SEM 0x01u /* 等待信号量 */
#define OS_STAT_MBOX 0x02u /* 等待消息邮箱 */
#define OS_STAT_Q 0x04u /* 等待消息队列 */
#define OS_STAT_SUSPEND 0x08u /* 任务挂起 */
#define OS_STAT_MUTEX 0x10u /* 等待互斥信号量 */
#define OS_STAT_FLAG 0x20u /* 等待事件标志组 */
#define OS_STAT_MULTI 0x80u /* Pending on multiple events */
#define OS_STAT_PEND_ANY (OS_STAT_SEM | OS_STAT_MBOX | OS_STAT_Q | OS_STAT_MUTEX | OS_STAT_FLAG)
// TASK PEND STATUS 任务等待状态标志
#define OS_STAT_PEND_OK 0u /* 正常等待完成 */
#define OS_STAT_PEND_TO 1u /* 等待超时标志 */
#define OS_STAT_PEND_ABORT 2u /* 等待放弃标志 */
// OS_EVENT types 事件种类
#define OS_EVENT_TYPE_UNUSED 0u /* 未分配 */
#define OS_EVENT_TYPE_MBOX 1u /* 消息邮箱 */
#define OS_EVENT_TYPE_Q 2u /* 消息队列 */
#define OS_EVENT_TYPE_SEM 3u /* 信号量 */
#define OS_EVENT_TYPE_MUTEX 4u /* 互斥信号量 */
#define OS_EVENT_TYPE_FLAG 5u /* 事件标志组 */
// EVENT FLAGS 事件标志组宏定义
#define OS_FLAG_WAIT_CLR_ALL 0u /* 触发逻辑为标志组全为0 */
#define OS_FLAG_WAIT_CLR_AND 0u
#define OS_FLAG_WAIT_CLR_ANY 1u /* 触发逻辑为标志组任意一个为0 */
#define OS_FLAG_WAIT_CLR_OR 1u
#define OS_FLAG_WAIT_SET_ALL 2u /* 触发逻辑为标志组全为1 */
#define OS_FLAG_WAIT_SET_AND 2u
#define OS_FLAG_WAIT_SET_ANY 3u /* 触发逻辑为标志组任意一个为1 */
#define OS_FLAG_WAIT_SET_OR 3u
#define OS_FLAG_CONSUME 0x80u /* 满足条件后是否清除flag */
#define OS_FLAG_CLR 0u
#define OS_FLAG_SET 1u
// 事件删除函数可选项
#define OS_DEL_NO_PEND 0u
#define OS_DEL_ALWAYS 1u
// OSXXXPend() OPTIONS 等待函数可选项
#define OS_PEND_OPT_NONE 0u /* 使在等待该事件的最高优先级的任务放弃等待*/
#define OS_PEND_OPT_BROADCAST 1u /* Abort时使用,取消所有等待该事件的任务 */
// OSXXXPostOpt() OPTIONS 发送函数可选项
#define OS_POST_OPT_NONE 0x00u /* 默认 */
#define OS_POST_OPT_BROADCAST 0x01u /* 仅在邮箱和消息队列使用,同时向所有等待任务发送 */
#define OS_POST_OPT_FRONT 0x02u /* 仅在消息队列使用,发到消息队列最前 */
#define OS_POST_OPT_NO_SCHED 0x04u /* 仅在邮箱和消息队列使用,不触发任务调度 */
// TIMER OPTIONS (软件定时器相关可选项)
#define OS_TMR_OPT_NONE 0u /* 默认 */
#define OS_TMR_OPT_ONE_SHOT 1u /* 单次计时 */
#define OS_TMR_OPT_PERIODIC 2u /* 周期性重置 */
// TIMER STATES 软件定时器状态
#define OS_TMR_STATE_UNUSED 0u /* 未被分配 */
#define OS_TMR_STATE_STOPPED 1u /* 创建了但未被加入轮辐 */
#define OS_TMR_STATE_COMPLETED 2u /* 单次计时完成 */
#define OS_TMR_STATE_RUNNING 3u /* 计时中 */
// ERROR CODES 错误码
......
数据结构部分
/***** 数据结构部分 *****/
// EVENT CONTROL BLOCK 事件控制块
typedef struct os_event {
INT8U OSEventType; /* 事件种类 */
/* 可以在各种地方使用,FreeList指向下个ECB、传消息时指向消息、在Mutex中指向占有信号量的TCB */
void *OSEventPtr;
INT16U OSEventCnt; /* 信号量计数(仅信号量有用) */
/* 等待该事件的任务优先级组 OS_PRIO根据优先级总数选择为u8或u16*/
OS_PRIO OSEventGrp;
OS_PRIO OSEventTbl[OS_EVENT_TBL_SIZE];/* 等待该事件的任务优先级表 */
#if OS_EVENT_NAME_EN > 0u
INT8U *OSEventName; /* 事件名 */
#endif
} OS_EVENT;
// EVENT FLAGS CONTROL BLOCK 事件标志组
typedef struct os_flag_grp {
INT8U OSFlagType; /* 等价OSEventType,只能为OS_EVENT_TYPE_FLAG */
void *OSFlagWaitList;/* 等待该事件标志组的事件标志组任务节点链表头 */
OS_FLAGS OSFlagFlags; /* 根据OS_FLAGS_NBITS设置为8/16/32 */
#if OS_FLAG_NAME_EN > 0u
INT8U *OSFlagName; /* 事件标志组名 */
#endif
} OS_FLAG_GRP;
// 事件标志组等待任务节点
typedef struct os_flag_node {
void *OSFlagNodeNext; /* 指向下个节点 */
void *OSFlagNodePrev; /* 指向前一个节点 */
void *OSFlagNodeTCB; /* 指向当前节点对应的任务控制块 */
void *OSFlagNodeFlagGrp; /* 指回OS_FLAG_GRP */
OS_FLAGS OSFlagNodeFlags; /* 该任务实际要等待哪些标志位 */
INT8U OSFlagNodeWaitType; /* 触发逻辑全0/任意0/全1/任意1 */
} OS_FLAG_NODE;
#endif
// MESSAGE MAILBOX DATA 消息邮箱
typedef struct os_mbox_data {
void *OSMsg; /* 指向对应消息 */
OS_PRIO OSEventTbl[OS_EVENT_TBL_SIZE]; /* 等待该事件的任务优先级表 */
OS_PRIO OSEventGrp; /* 等待该事件的任务优先级组 */
} OS_MBOX_DATA;
// MEMORY PARTITION DATA STRUCTURES 内存分配数据结构
typedef struct os_mem {
/* 内存控制块 */
void *OSMemAddr; /* 对应内存区头地址 */
void *OSMemFreeList; /* 空闲内存块链表头 */
INT32U OSMemBlkSize; /* 单个块内存大小(byte) */
INT32U OSMemNBlks; /* 该区中的内存块数目 */
INT32U OSMemNFree; /* 该区中空闲内存块数目 */
#if OS_MEM_NAME_EN > 0u
INT8U *OSMemName; /* 内存区名 */
#endif
} OS_MEM;
// 另有一个结构基本一致的 OS_MEM_DATA, 其作用为作为OSMemQuery函数的输入,之所以多此一举是尽量防止用户直接操作系统内核数据结构OS_MEM
// UCOS提供的动态内存管理机制并不好用,创建内存区时所有内存块大小相同,取用时一次只能取用一个内存块。
// MUTUAL EXCLUSION SEMAPHORE DATA 互斥信号量
typedef struct os_mutex_data {
OS_PRIO OSEventTbl[OS_EVENT_TBL_SIZE]; /* 等待该事件的任务优先级表 */
OS_PRIO OSEventGrp; /* 等待该事件的任务优先级组 */
BOOLEAN OSValue; /* 互斥信号量值 */
INT8U OSOwnerPrio; /* 使用者优先级,无人使用则置为0xff */
INT8U OSMutexPIP; /* 优先级继承优先级,防止优先级反转 */
} OS_MUTEX_DATA;
// MESSAGE QUEUE DATA 消息队列
typedef struct os_q {
/* 消息队列控制块 */
struct os_q *OSQPtr; /* 指向下一个消息队列控制块 */
void **OSQStart; /* 循环队列起始地址 */
void **OSQEnd; /* 循环队列结束地址 */
void **OSQIn; /* 下个消息进入的地址 */
void **OSQOut; /* 下个消息取出的地址 */
INT16U OSQSize; /* 队列长度 */
INT16U OSQEntries; /* 队列内消息总数 */
} OS_Q;
// 另有结构相近的 OS_Q_DATA, 其作用同OS_MEM_DATA
// SEMAPHORE DATA 信号量DATA,作用同上
typedef struct os_sem_data {
INT16U OSCnt; /* 信号量计数 */
OS_PRIO OSEventTbl[OS_EVENT_TBL_SIZE]; /* 等待该事件的任务优先级表 */
OS_PRIO OSEventGrp; /* 等待该事件的任务优先级组 */
} OS_SEM_DATA;
// TASK STACK DATA 任务堆栈
typedef struct os_stk_data {
INT32U OSFree; /* 任务栈内空闲字节数 */
INT32U OSUsed; /* 任务栈内已使用字节数 */
} OS_STK_DATA;
// TASK CONTROL BLOCK 任务控制块 只记录了部分基础成员
typedef struct os_tcb {
OS_STK *OSTCBStkPtr; /* 任务栈顶指针 */
struct os_tcb *OSTCBNext; /* 双向链表下一个 */
struct os_tcb *OSTCBPrev; /* 双向链表前一个 */
OS_EVENT *OSTCBEventPtr; /* 指向任务在等待的事件控制块 */
void *OSTCBMsg; /* 保存从邮箱或队列得到的消息 */
INT32U OSTCBDly; /* Delay的系统时钟次数,或等待事件的timeout */
INT8U OSTCBStat; /* 任务状态 */
INT8U OSTCBStatPend; /* 等待事件状态 */
INT8U OSTCBPrio; /* 优先级 */
INT8U OSTCBX; /* 就绪表中X轴实际数 */
INT8U OSTCBY; /* 就绪表中Y轴实际数 */
OS_PRIO OSTCBBitX; /* 就绪表中X轴字节表示 */
OS_PRIO OSTCBBitY; /* 就绪表中Y轴字节表示 */
/* 若prio=15=1111b, 则OSTCBY = prio>>3 = 1, OSTCBBitY = 1<<OSTCBY = 10b;
OSTCBX = prio&0x07 = 7, OSTCBBitX = 1 << OSTCBX = 10000000b */
} OS_TCB;
// TIMER DATA TYPES 软件定时器
typedef void (*OS_TMR_CALLBACK)(void *ptmr, void *parg);
typedef struct os_tmr {
INT8U OSTmrType; /* 仅可为OS_TMR_TYPE */
OS_TMR_CALLBACK OSTmrCallback; /* 回调函数,宏定义如上 */
void *OSTmrCallbackArg; /* 回调函数传参 */
void *OSTmrNext; /* 双向链表 */
void *OSTmrPrev;
INT32U OSTmrMatch; /* OSTmrTime等于该值时认为计时结束 */
INT32U OSTmrDly; /* 周期计时前的等待计数 */
INT32U OSTmrPeriod; /* 计时周期 */
INT8U OSTmrOpt; /* 定时器可选项,见OS_TMR_OPT_XXX */
INT8U OSTmrState; /* 定时器状态:UNUSED/RUNNING/STOPPED */
} OS_TMR;
typedef struct os_tmr_wheel {
/* 计时器轮,在大量计时器时可通过散列操作有效提高效率 */
OS_TMR *OSTmrFirst; /* 该轮辐指向的第一个定时器OS_TMR */
INT16U OSTmrEntries; /* 该轮辐上的计时器总数 */
} OS_TMR_WHEEL;
全局变量部分
/***** 全局变量部分,仅记录了部分重要变量 *****/
OS_EXT INT8U OSCPUUsage; /* 统计任务计算的CPU占用率 */
OS_EXT INT32U OSIdleCtrMax; /* 1s内空闲任务执行的最大次数 */
OS_EXT INT32U OSIdleCtrRun; /* 当前1s内空闲任务执行的次数 */
OS_EXT INT8U OSIntNesting; /* 中断嵌套层数 */
OS_EXT INT8U OSPrioCur; /* 当前任务优先级 */
OS_EXT INT8U OSPrioHighRdy; /* 最高已就绪任务优先级 */
extern INT8U const OSUnMapTbl[256]; /* 用于优先级判断时的查表 */
OS_EXT OS_PRIO OSRdyGrp; /* 任务就绪组,用于快速查找当前最高优先级任务 */
OS_EXT OS_PRIO OSRdyTbl[OS_RDY_TBL_SIZE]; /* 任务就绪表,用于快速查找当前最高优先级任务 */
OS_EXT BOOLEAN OSRunning; /* 系统内核是否正常工作 */
OS_EXT INT8U OSTaskCtr; /* 创建过的任务计数 */
OS_EXT OS_TCB *OSTCBCur; /* 当前进行任务的TCB */
OS_EXT OS_TCB *OSTCBHighRdy; /* 当前就绪态最高优先级任务的TCB */
OS_EXT OS_TCB *OSTCBFreeList;/* 仍可使用的空闲TCB单向链表的链表头 */
OS_EXT OS_TCB *OSTCBList; /* 已被使用的TCB双向链表的链表头,新任务向表头添加 */
OS_EXT OS_TCB *OSTCBPrioTbl[OS_LOWEST_PRIO + 1u]; /* 根据优先级存储的TCB指针数组,方便查找 */
OS_EXT OS_TCB OSTCBTbl[OS_MAX_TASKS + OS_N_SYS_TASKS]; /* 所有TCB本体实际存储的数据结构 */
// 其他诸如event,flag,Q,Tmr,Mem等都有类似*XXXFreeList和XXXTbl[MAX_SIZE]的全局变量
OS_EXT INT32U OSTmrTime; /* 当前计时器计数,会不断溢出回0 */
OS_EXT OS_EVENT *OSTmrSemSignal; /* 定时器信号量,每次发送该信号量时会使OSTmrTime加一 */
OS_EXT OS_STK OSTmrTaskStk[OS_TASK_TMR_STK_SIZE]; /* 定时器管理任务所用栈 */
OS_EXT OS_TMR_WHEEL OSTmrWheelTbl[OS_TMR_CFG_WHEEL_SIZE]; /* 定时器轮本体数组 */
函数实现部分
/*************************************************************************************** * 函数部分,仅细读一些重要函数的实现,以便于方便理解流程
* 本部分整理的函数仅保留核心部分,对于一些条件编译和异常情况判断的代码会予以剔除
****************************************************************************************/
os_mem.c
/* ******************* os_mem.c (ucos为用户提供的内存管理,并不常用) *****************
* 内存管理流程大致梳理:ucos全局变量OS_MEM OSMemTbl[]用于存储预定大小的OS_MEM内存区结构体,另有
* OS_MEM *OSMemFreeList作为空闲内存区结构体的链表头指针。创建内存区时即从Tbl中拿取一个内存区结构
* 体,并将结构体中的void *OSMemFreeList成员变量(与空闲内存区链表头指针重名-_-)按照函数输入的起始地
* 址,内存块大小,内存块数目此三个参数,从指定起始地址开始初始化一个内存块链表。之后的取用和释放都以目
* 标内存区结构体下的单个内存块为单位操作(所以不好用)
*********************************************************************************** */
/* **** 创建内存区函数 ****
* 输入: addr 起始地址
* nblks 内存区中内存块数目
* blksize 单个内存块大小
* perr 错误码
* 返回: OS_MEM * 内存控制块结构体指针
*/
OS_MEM *OSMemCreate (void *addr,
INT32U nblks,
INT32U blksize,
INT8U *perr)
{
OS_MEM *pmem;
INT8U *pblk;
void **plink;
INT32U loops;
INT32U i;
OS_ENTER_CRITICAL();
pmem = OSMemFreeList; /* 用内存区空闲链表OSMemFreeList从全局内存区OSMemTbl[]中拿取一块 */
if (OSMemFreeList != (OS_MEM *)0) {
/* 更新全局内存区空闲链表 */
OSMemFreeList = (OS_MEM *)OSMemFreeList->OSMemFreeList;
}
OS_EXIT_CRITICAL();
if (pmem == (OS_MEM *)0) {
/* 判断拿到的内存区是否为空 */
*perr = OS_ERR_MEM_INVALID_PART;
return ((OS_MEM *)0);
}
/* 将内存区初始化成nklks个内存块的链表结构 */
plink = (void **)addr; /* 起始地址转换为二重指针 */
pblk = (INT8U *)addr; /* 起始地址转换为字节指针,方便后面分配空间 */
loops = nblks - 1u;
for (i = 0u; i < loops; i++) {
pblk += blksize; /* 字节指针向后一个块 */
*plink = (void *)pblk; /* 二重指针解引用,即将addr下的第一个字存为上一行结果处的地址 */
plink = (void **)pblk; /* 二重指针向后移一个块 */
}
*plink = (void *)0; /* 内存最后一个块,链表指针指向空 */
/* 初始化内存控制块结构体 */
pmem->OSMemAddr = addr;
pmem->OSMemFreeList = addr; /* 此处可能会有疑问,该成员在描述中是指向下一个内存块的,但此处仅为一个不定型指针。解释:其被使用时形如*(void **)OSMemFreeList,强制转换加解引用表示直接取addr地址下第一个字代表的指针 */
pmem->OSMemNFree = nblks;
pmem->OSMemNBlks = nblks;
pmem->OSMemBlkSize = blksize;
*perr = OS_ERR_NONE;
return (pmem);
}
/* **** 从内存区中拿取单个内存块函数 ****
* 输入: pmem 内存控制块结构体指针
* perr 错误码
* 返回: void
*/
void *OSMemGet (OS_MEM *pmem,
INT8U *perr)
{
void *pblk;
OS_ENTER_CRITICAL();
if (pmem->OSMemNFree > 0u) {
pblk = pmem->OSMemFreeList; /* pblk置为当前空闲内存块的地址 */
pmem->OSMemFreeList = *(void **)pblk; /* 将内存控制块的空闲内存块链表指向下个块,具体实现原理见前面函数 */
pmem->OSMemNFree--; /* 可用内存块数目减一 */
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
return (pblk); /* 返回单个空闲内存块的地址 */
}
OS_EXIT_CRITICAL();
*perr = OS_ERR_MEM_NO_FREE_BLKS; /* 无空闲内存块错误 */
return ((void *)0);
}
os_time.c
/* ********************** os_time.c (时间管理,并不是软件计时器) ***********************
* 系统延时:ucos中实现延时的方式是在任务就绪表中为当前运行任务清0,并在OSTCBDly中装载延时周期数,
* 然后执行一次任务调度让出CPU,该任务便停在delay处直到再次获得CPU。
*
* 系统心跳时钟管理(本应在os_core.c中,跟系统时间管理关系比较大,故拿到此处):基本流程为:先对一些
* 表示全局系统时间的变量加一,然后遍历已使用任务控制块链表,对其中一般延时和等待事件的任务分别减Dly周
* 期,若减到零且该任务未被挂起则在就绪表中恢复。
*********************************************************************************** */
/* **** 从内存区中拿取单个内存块函数 ****
* 输入: ticks 延时周期数
*/
void OSTimeDly (INT32U ticks)
{
INT8U y;
if (OSIntNesting > 0u) {
/* 中断中不可调用 */
return;
}
if (OSLockNesting > 0u) {
/* 调度器上锁时不可调用 */
return;
}
if (ticks > 0u) {
OS_ENTER_CRITICAL();
y = OSTCBCur->OSTCBY;
OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX; /* 更新任务就绪表和任务就绪组 */
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;
}
OSTCBCur->OSTCBDly = ticks; /* 装载延时计数 */
OS_EXIT_CRITICAL();
OS_Sched(); /* 执行一次调度 */
}
}
/* **** 系统心跳时间管理 ****
* 无输入无输出
*/
void OSTimeTick (void)
{
OS_TCB *ptcb;
#if OS_TIME_GET_SET_EN > 0u /* 可使用拿取系统时间功能 */
OS_ENTER_CRITICAL();
OSTime++;
OS_EXIT_CRITICAL();
#endif
if (OSRunning == OS_TRUE) {
ptcb = OSTCBList; /* 取已使用的任务控制块链表头 */
while (ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO) {
/* 遍历直到链表尾的空闲任务 */
OS_ENTER_CRITICAL();
if (ptcb->OSTCBDly != 0u) {
/* 操作每一个处于延时状态的任务 */
ptcb->OSTCBDly--;
if (ptcb->OSTCBDly == 0u) {
/* 若减到零 */
/* 若在等待某个事件,并非一般延时 */
if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {
/* 清除等待事件状态 */
ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_PEND_ANY;
/* 等待状态置为超时 */
ptcb->OSTCBStatPend = OS_STAT_PEND_TO;
} else {
/* 不在等待事件,则为一般延时,等待状态清0 */
ptcb->OSTCBStatPend = OS_STAT_PEND_OK;
}
/* 未在挂起状态 */
if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) {
/* 就绪表和就绪组更新 */
OSRdyGrp