这是μC/OS III
任务调度的第二篇文章:任务列表。分析μC/OS III
的三种任务列表:就绪列表,挂起列表,时基列表。并分析这三种任务列表的存在方式、作用、操作函数等。
一. 就绪列表Ready List
就绪列表里放的是准备(可以)运行的任务。就绪列表由两部分组成:优先级位图(BitMap) 和 指向所有就绪任务的列表。
1. 任务优先级和优先级位图
任务优先级的最大值在os_cfg.h
中有定义:
[os_cfg.h]
#define OS_CFG_PRIO_MAX 64u
任务优先级的数据类型是一个有符号字节类型:
[os_type.h]
typedef unsigned char CPU_INT08U;
typedef CPU_INT08U OS_PRIO;
优先级位图是一个数组OSPrioTbl[]
:
[os_prio.c]
CPU_DATA OSPrioTbl[OS_PRIO_TBL_SIZE];
其中OS_PRIO_TBL_SIZE
宏定义如下:
[lib_def.h]
#define CPU_WORD_SIZE_32 4u
#define CPU_CFG_DATA_SIZE CPU_WORD_SIZE_32
#define DEF_OCTET_NBR_BITS 8u
#define DEF_INT_CPU_NBR_BITS (CPU_CFG_DATA_SIZE * DEF_OCTET_NBR_BITS)
[os.h]
#define OS_PRIO_TBL_SIZE (((OS_CFG_PRIO_MAX - 1u) / (DEF_INT_CPU_NBR_BITS)) + 1u)
其含义指的是OSPrioTbl[]
的长度最少有OS_CFG_PRIO_MAX
个Bit
。如此便把所有的优先级都用一个Bit
进行了映射,映射到OSPrioTbl[]
数组相应的Bit
上。
获取就绪任务列表中任务的最高优先级函数为OS_PrioGetHighest
:
[os_prio.c]
OS_PRIO OS_PrioGetHighest (void)
{
CPU_DATA *p_tbl;
OS_PRIO prio;
prio = 0u;
p_tbl = &OSPrioTbl[0];
#if (OS_CFG_PRIO_MAX > DEF_INT_CPU_NBR_BITS)
while (*p_tbl == 0u) {
prio += (OS_PRIO)DEF_INT_CPU_NBR_BITS;
p_tbl++;
}
#endif
/* Find the position of the first bit set at the entry */
prio += (OS_PRIO)CPU_CntLeadZeros(*p_tbl);
return (prio);
}
此函数就是获取OSPrioTbl[]
数组中第一个不为0的Bit
的位序号。
和优先级位图相关的函数有4个:
序号 | 函数 | 作用 |
---|---|---|
1 | OS_PrioInit(void) | OSPrioTbl[] 所有成员都清0 |
2 | OS_PrioInsert(OS_PRIO prio) | 把 OSPrioTbl[] 中 prio 优先级所对应的位设1 |
3 | OS_PrioRemove(OS_PRIO prio) | 把 OSPrioTbl[] 中 prio 优先级所对应的位清0 |
4 | OS_PrioGetHighest(void) | 获取就绪列表中最高的任务优先级 |
2. 指向所有就绪任务的列表
指向所有就绪任务的指针列表是用数组OSRdyList
来表示:
[os.h]
struct os_rdy_list {
OS_TCB *HeadPtr;
OS_TCB *TailPtr;
#if (OS_CFG_DBG_EN == DEF_ENABLED)
OS_OBJ_QTY NbrEntries;
#endif
};
typedef struct os_rdy_list OS_RDY_LIST;
OS_RDY_LIST OSRdyList[OS_CFG_PRIO_MAX];
这个数组的长度就是最大优先级的大小,数组中每一个成员代表一个优先级,其值是指向包含此优先级中所有的任务的一个链表。
和优先级位图相关的函数有6个:
序号 | 函数 | 作用 |
---|---|---|
1 | OS_RdyListInit(void) | 初始化 OSRdyList , 把每一个 OS_RDY_LIST 的 HeadPtr 和 TailPtr 全设位0。 |
2 | OS_RdyListInsert(OS_TCB *p_tcb) | 把一个任务插入 OSRdyList , 先调用 OS_PrioInsert 插入设置优先级的 BitMap , 如果任务 p_tcb 的优先级和当前任务的优先级相同, 那么调用 OS_RdyListInsertTail 把这个任务插入到相应链表的尾部; 如果不同, 那么调用 OS_RdyListInsertHead , 把这个任务插入到相应链表的头部。 |
3 | OS_RdyListInsertHead(OS_TCB *p_tcb) | 把一个任务插入 OSRdyList 中某个与其优先级相对应的链表的头部。 |
4 | OS_RdyListInsertTail(OS_TCB *p_tcb) | 把一个任务插入 OSRdyList 中某个与其优先级相对应的链表的尾部。 |
5 | OS_RdyListMoveHeadToTail(OS_RDY_LIST *p_rdy_list) | 把一个链表的头部的 OS_TCB 移动到尾部。 |
6 | OS_RdyListRemove(OS_TCB *p_tcb) | 把一个任务 p_tcb 从包含这个任务的链表中移除, 即把这个任务移除就绪列表OSRdyList 。 |
二. 挂起列表Pend List
1. 挂起列表的定义
挂起列表的定义在os.h
里:
[os.h]
typedef struct os_pend_list OS_PEND_LIST;
struct os_pend_list {
OS_TCB *HeadPtr;
OS_TCB *TailPtr;
#if (OS_CFG_DBG_EN == DEF_ENABLED)
OS_OBJ_QTY NbrEntries;
#endif
};
其定义和就绪列表Ready List
几乎相同。
2. 挂起列表的操作函数
挂起列表Pend List
有4个操作函数:
序号 | 函数 | 作用 |
---|---|---|
1 | OS_PendListInit(OS_PEND_LIST *p_pend_list) | 初始化一个 Pend List ,把 HeadPtr 和 TailPtr 设置为0。 |
2 | OS_PendListInsertPrio(OS_PEND_LIST *p_pend_list, OS_TCB *p_tcb) | 把一个任务插入到一个 Pend List 中, 需要注意的是, Pend List 中任务的顺序是按照优先级大小排列的, 优先级最高的任务在前(即Prio 值最小的任务在前)。 |
3 | OS_PendListChangePrio(OS_TCB *p_tcb) | 如果一个任务的优先级发生了变化, 并且这个任务在 Pend List 中, 调用这个函数可以根据新的优先级来改变这个任务在这个 Pend List 中的位置。 |
4 | OS_PendListRemove(OS_TCB *p_tcb) | 把一个任务 p_tcb 从他所属的 Pend List 中移除。 |
3. 挂起列表与内核对象的关系
Pend List
存在于多个内核对象中:
序号 | 有Pend List 的内核对象 | 意义 |
---|---|---|
1 | OS_FLAG_GRP | 事件标志组 |
2 | OS_MUTEX | 互斥信号量 |
3 | OS_Q | 消息队列 |
4 | OS_SEM | 计数信号量 |
5 | OS_MON | 监视器 |
其中OS_FLAG_GRP
的定义如下:
[os.h]
struct os_flag_grp {
#if (OS_OBJ_TYPE_REQ == DEF_ENABLED)
OS_OBJ_TYPE Type;
#endif
#if (OS_CFG_DBG_EN == DEF_ENABLED)
CPU_CHAR *NamePtr;
#endif
OS_PEND_LIST PendList;
#if (OS_CFG_DBG_EN == DEF_ENABLED)
void *DbgPrevPtr;
void *DbgNextPtr;
CPU_CHAR *DbgNamePtr;
#endif
...
};
typedef struct os_flag_grp OS_FLAG_GRP;
OS_MUTEX
的定义如下:
[os.h]
struct os_mutex {
#if (OS_OBJ_TYPE_REQ == DEF_ENABLED)
OS_OBJ_TYPE Type;
#endif
#if (OS_CFG_DBG_EN == DEF_ENABLED)
CPU_CHAR *NamePtr;
#endif
OS_PEND_LIST PendList;
#if (OS_CFG_DBG_EN == DEF_ENABLED)
void *DbgPrevPtr;
void *DbgNextPtr;
CPU_CHAR *DbgNamePtr;
#endif
...
};
typedef struct os_mutex OS_MUTEX;
OS_Q
的定义如下:
[os.h]
struct os_q {
#if (OS_OBJ_TYPE_REQ == DEF_ENABLED)
OS_OBJ_TYPE Type;
#endif
#if (OS_CFG_DBG_EN == DEF_ENABLED)
CPU_CHAR *NamePtr;
#endif
OS_PEND_LIST PendList;
#if (OS_CFG_DBG_EN == DEF_ENABLED)
void *DbgPrevPtr;
void *DbgNextPtr;
CPU_CHAR *DbgNamePtr;
#endif
...
};
typedef struct os_q OS_Q;
OS_SEM
的定义如下:
[os.h]
struct os_sem {
#if (OS_OBJ_TYPE_REQ == DEF_ENABLED)
OS_OBJ_TYPE Type;
#endif
#if (OS_CFG_DBG_EN == DEF_ENABLED)
CPU_CHAR *NamePtr;
#endif
OS_PEND_LIST PendList;
#if (OS_CFG_DBG_EN == DEF_ENABLED)
void *DbgPrevPtr;
void *DbgNextPtr;
CPU_CHAR *DbgNamePtr;
#endif
...
};
typedef struct os_sem OS_SEM;
OS_MON
的定义如下:
[os.h]
struct os_mon {
#if (OS_OBJ_TYPE_REQ == DEF_ENABLED)
OS_OBJ_TYPE Type;
#endif
#if (OS_CFG_DBG_EN == DEF_ENABLED)
CPU_CHAR *NamePtr;
#endif
OS_PEND_LIST PendList;
#if (OS_CFG_DBG_EN == DEF_ENABLED)
void *DbgPrevPtr;
void *DbgNextPtr;
CPU_CHAR *DbgNamePtr;
#endif
...
};
typedef struct os_mon OS_MON;
可以看出他们都包含一个挂起列表OS_PEND_LIST
,并且他们的定义的前部分都是相同的。μC/OS III
这样子做是为了他们结构体有一个相同的部分,操作这些内核对象的时候可以类似操作c++ Class
的父类一样,用同样的接口去操作他们。他们的父类的定义为OS_PEND_OBJ
,其定义如下:
[os.h]
struct os_pend_obj {
#if (OS_OBJ_TYPE_REQ == DEF_ENABLED)
OS_OBJ_TYPE Type;
#endif
#if (OS_CFG_DBG_EN == DEF_ENABLED)
CPU_CHAR *NamePtr;
#endif
OS_PEND_LIST PendList;
#if (OS_CFG_DBG_EN == DEF_ENABLED)
void *DbgPrevPtr;
void *DbgNextPtr;
CPU_CHAR *DbgNamePtr;
#endif
};
typedef struct os_pend_obj OS_PEND_OBJ;
在代表一个任务实体的结构体OS_TCB
中也有一个OS_PEND_OBJ
来记录当前任务属于哪个OS_PEND_OBJ
:
[os.h]
struct os_tcb {
...
OS_TCB *PendNextPtr;
OS_TCB *PendPrevPtr;
OS_PEND_OBJ *PendObjPtr;
OS_STATE PendOn;
OS_STATUS PendStatus;
...
};
4. 挂起列表的作用
每当一个任务在Pend
某一个资源(内核对象)的时候,μC/OS III
会先把这个任务从就绪列表中移除,再把这个任务加入到这个资源(内核对象)的挂起列表中。等到其他任务或者中断对这个资源进行Post
操作时,μC/OS III
会判断正在等待的任务是否满足条件,如果满足条件就把这个任务从挂起列表中移除,再次放入到就绪列表中。
三. 时基列表
1. 时基列表的定义
struct os_tick_list {
OS_TCB *TCB_Ptr;
#if (OS_CFG_DBG_EN == DEF_ENABLED)
OS_OBJ_QTY NbrEntries;
OS_OBJ_QTY NbrUpdated;
#endif
};
typedef struct os_tick_list OS_TICK_LIST;
2. 时基列表的操作函数
时基列表Tick List
有3个操作函数:
序号 | 函数 | 作用 |
---|---|---|
1 | OS_TickListInsert(OS_TICK_LIST *p_list, OS_TCB *p_tcb, OS_TICK time) | 把指定的任务插入到指定的 Tick List 中, 并设置这个任务的超时时间, 这个时间是以一个 Tick 为单位的,插入到 Tick List 链表中的任务的排列顺序是以 Deltac Time 为依据的,最前面的任务的RemainTime 是距离当前时间的差值, 后面链表的每一个任务都是距离其前面的任务的RemainTime 的差值。 |
2 | OS_TickListInsertDly(OS_TCB *p_tcb, OS_TICK time, OS_OPT opt, OS_ERR *p_err) | 是把指定的任务插入到OSTickListDly 中, 最终调用 OS_TickListInsert |
3 | OS_TickListRemove(OS_TCB *p_tcb) | 把指定的任务从其所在的 Tick List 中移除。 |
3. 时基列表的存在方式及其作用
时基列表有两个,在os.h
中有定义:
[os.h]
OS_EXT OS_TICK_LIST OSTickListDly;
OS_EXT OS_TICK_LIST OSTickListTimeout;
OSTickListDly
是为μC/OS III
提供一种延时的机制,当任务调用OSTimeDly
或者OSTimeDlyHMSM
的时候,μC/OS III
就会把调用这两个函数的任务放入到OSTickListDly
时基列表中,每当来一次SysTick
的时候,在Tick Task
中会被更新剩余的延时时间。OSTickListTimeout
为一些Pend
操作提供Timeout
的机制,任务在Pend
操作等待一些资源的时候,会把任务挂到OSTickListTimeout
时基列表里,最终在Tick Task
中处理。