在UCOS-II嵌入式操作系统中,对任务的管理,主要掌握的点有任务结构体、单/双链表、优先级就绪表、任务相关操作(创建、删除等)等。
1. 任务结构体介绍
任务结构体的成员为人物相关的信息,方便管理任务。
typedef struct os_tcb 声明一个结构体类型,typedef的作用是给struct os_tcb起别名OS_TCB,定义结构体变量时,直接使用别名即可。可以省去struct关键字。比如,定义结构体变量a时,可以OS_TCB a; 而不需要 struct os_tcb a;
{
OS_STK *OSTCBStkPtr; /* 指向任务私有栈的栈顶,每个任务都有一个独自的私有栈,在任务切换时保存现场 */
#if OS_TASK_CREATE_EXT_EN > 0u 条件编译,根据需要可在配置文件中使能自己需要的功能,系统可裁剪体现在这。这里是人物扩展功能
void *OSTCBExtPtr; /* 任务扩展指针 */
OS_STK *OSTCBStkBottom; /* 指向任务栈底 */
INT32U OSTCBStkSize; /* 栈的大小 */
INT16U OSTCBOpt; /*可选项 */
INT16U OSTCBId; /* 任务I D */
#endif
struct os_tcb *OSTCBNext; /* 双向链表,指向链表的下一个 */
struct os_tcb *OSTCBPrev; /* 双向链表,指向链表的上一个 */
#if (OS_EVENT_EN)下面这部分主要用于任务间通讯,信号量、消息邮箱、阵列等
OS_EVENT *OSTCBEventPtr; /* 指向事件控制块指针 */
#endif
#if (OS_EVENT_EN) && (OS_EVENT_MULTI_EN > 0u)
OS_EVENT **OSTCBEventMultiPtr; /* 指向多事件控制块的指针 */
#endif
#if ((OS_Q_EN > 0u) && (OS_MAX_QS > 0u)) || (OS_MBOX_EN > 0u)下面这部分主要用于任务间通讯,信号量、消息邮箱、阵列等
void *OSTCBMsg; /* Message received from OSMboxPost() or OSQPost() */
#endif
#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
#if OS_TASK_DEL_EN > 0u
OS_FLAG_NODE *OSTCBFlagNode; /* Pointer to event flag node */
#endif
OS_FLAGS OSTCBFlagsRdy; /* Event flags that made task ready to run */
#endif
INT32U OSTCBDly; /* 延时参数,比如任务等待一个事件所要的时间 */
INT8U OSTCBStat; /* 指示任务当前的状态 */
INT8U OSTCBStatPend; /* 任务挂起的状态 */
INT8U OSTCBPrio; /*任务优先级,每个任务都有一个优先级,并且每个优先级仅有一个任务,0优先级最高 */
INT8U OSTCBX; /* 当十进制时,优先级0-63,前两位无效,只看后6为,该值表示低三位的值 */
INT8U OSTCBY; /* 优先级后六位中的高三位的值 */
OS_PRIO OSTCBBitX; /* 该值对应优先级就绪表中的第几列, */
OS_PRIO OSTCBBitY; /* 该值对应优先就绪表中的第几行,即优先级就绪组 */
#if OS_TASK_DEL_EN > 0u
INT8U OSTCBDelReq; /* 标识位,设置后告知任务删除 */
#endif
#if OS_TASK_PROFILE_EN > 0u
INT32U OSTCBCtxSwCtr; /* 计数任务被切换的次数 */
INT32U OSTCBCyclesTot; /*该任务运行时钟周期的总数量 */
INT32U OSTCBCyclesStart; /* Snapshot of cycle counter at start of task resumption */
OS_STK *OSTCBStkBase; /* Pointer to the beginning of the task stack */
INT32U OSTCBStkUsed; /* Number of bytes used from the stack */
#endif
#if OS_TASK_NAME_EN > 0u
INT8U *OSTCBTaskName;
#endif
#if OS_TASK_REG_TBL_SIZE > 0u
INT32U OSTCBRegTbl[OS_TASK_REG_TBL_SIZE];
#endif
} OS_TCB;
2 任务初始化
任务初始化的目的,把空任务控制块组成一个单向链表,当需创建一个任务时,就从链表中取下一个空任务块;当释放一个任务时,把任务控制块归还空任务控制块链表,如下所示:
static void OS_InitTCBList (void)
{
INT8U ix;
INT8U ix_next;
OS_TCB *ptcb1;
OS_TCB *ptcb2;
OS_MemClr((INT8U *)&OSTCBTbl[0], sizeof(OSTCBTbl)); /* Clear all the TCBs */
OS_MemClr((INT8U *)&OSTCBPrioTbl[0], sizeof(OSTCBPrioTbl)); /* Clear the priority table */
for (ix = 0u; ix < (OS_MAX_TASKS + OS_N_SYS_TASKS - 1u); ix++)
{ /* Init. list of free TCBs */
ix_next = ix + 1u;
ptcb1 = &OSTCBTbl[ix];
ptcb2 = &OSTCBTbl[ix_next];
ptcb1->OSTCBNext = ptcb2;
#if OS_TASK_NAME_EN > 0u
ptcb1->OSTCBTaskName = (INT8U *)(void *)"?"; /* Unknown name */
#endif
}
ptcb1 = &OSTCBTbl[ix];
ptcb1->OSTCBNext = (OS_TCB *)0; /* Last OS_TCB */
#if OS_TASK_NAME_EN > 0u
ptcb1->OSTCBTaskName = (INT8U *)(void *)"?"; /* Unknown name */
#endif
OSTCBList = (OS_TCB *)0; /* TCB lists initializations 将任务控制块列表进行初始化 */
OSTCBFreeList = &OSTCBTbl[0]; /*将TCB空闲列表的指针指向TCB表的首地址*/
}
3任务创建
任务创建的过程,实际就是向任务控制块中添加内容的过程,以空任务和统计任务为例 :
创建空任务:
1,在空任务块链表中取出一个,即空链表的表头下移一个,OSTCBFreeList指向下一个空任务块,永远指向空链表的表头。
2,然后把创建的任务的相关参数,放到取出的空任务控制块内;
3,把放置任务的控制块加入到任务控制块链表中,OSTCBList指向任务控制块双向链表的表头
创建统计任务:
1,在空任务链表中取出一个空任务块,OSTCBFreeList指向下一个空任务块。
2,把统计任务的相关信息,放到取出的空任务块内;
3,把该任务的控制块,加入到双向任务控制块链表内,OSTCBList指向任务控制块双向链表的表头
ptcb->OSTCBNext = OSTCBList; /* ptcb当前新创建的任务,这句话的意思是当前任务的下一个指向原先的表头,即新建任务放置在表头处 */
ptcb->OSTCBPrev = (OS_TCB *)0; /*当前新创建任务的上一个指向0,因为新创建任务放置在表头,已经是最前面了*/
if (OSTCBList != (OS_TCB *)0) //如果不是首次创建任务
{
OSTCBList->OSTCBPrev = ptcb; //新建任务插入到表头,所以原先表头任务后移一个,它的前一个指向新建任务,建立双向链表
}
4任务优先级就绪表
任务优先级就绪表的作用快速找到就绪任务对应的优先级,每个任务对应一个优先级,如何通过优先级判断任务的就绪状态呢,主要通过两个表格:优先级就绪表和优先级判定表。
- 优先级就绪表
我们知道ucos操作系统是一个优先级抢占式多任务系统,总共有64个优先级,0-63,数字越小优先级越高,高优先级任务可以抢占低优先级任务执行,即在任务调度过程中,总是执行优先级最高的任务。系统如何知道那个任务处于就绪状态,且优先级最高呢,就是我们通常说的优先级就绪表。优先级就绪表是一个8个8位的数组,总共有64位,每一位代表一个优先级的状态,如下表所示。
对上图解释:优先级最大63,二进制表示0011 1111,即最高位是用不到的,仅用到了低6位,高三位和低三位取值范围位0-7,用高三位表示行,低三位表示列,就构成了如上对应图。当需要将某个优先级置位就绪状态时 ,将相应的位置1即可,比如:优先级23 二进制表示:0001 0111 高三位010 即第二行,低三位111,第七列。
为了更清晰的表示就绪表中某行某列有就绪任务,引入映射表,如下:
该映射的意思:第几行有就绪任务,第几位置1,同理,第几列有任务,第几位置1。
既然知道了就绪表的原理,那代码中如何实现的呢,为了更清楚代码的执行过程,且先熟悉下程序中两个变量:
OSRdyGrp:优先级继续组,该变量是8位的,每一位都对应上面的一行,哪位为1就表示哪行有就绪的任务。
OSRdyTbl[]:优先级就绪表,知道了某行有就绪任务后,可以根据此数组判断具体是那列有就绪任务。该行有几个1就代表有几个就绪任务。
具体代码实现入下:先确定那行那列,然后写入就绪表中
#if OS_LOWEST_PRIO <= 63u /* Pre-compute X, Y 预处理任务优先级 */
ptcb->OSTCBY = (INT8U)(prio >> 3u);
ptcb->OSTCBX = (INT8U)(prio & 0x07u);
#else /* Pre-compute X, Y */
ptcb->OSTCBY = (INT8U)((INT8U)(prio >> 4u) & 0xFFu);
ptcb->OSTCBX = (INT8U) (prio & 0x0Fu);
#endif
/* Pre-compute BitX and BitY */
ptcb->OSTCBBitY = (OS_PRIO)(1uL << ptcb->OSTCBY);
ptcb->OSTCBBitX = (OS_PRIO)(1uL << ptcb->OSTCBX);该处的作用就是求映射关系,处于第几行第几列,就把字节中的第几位置1
OSRdyGrp |= ptcb->OSTCBBitY; /* 更新就绪表 使任务进入"就绪"状态 */
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
-
优先级判定表
上面了解了优先级就绪表的原理,那如何根据优先级就绪表快速的计算出具体优先级呢,这里引入优先级判定表。INT8U const OSUnMapTbl[256] = { 0u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x00 to 0x0F */ 4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x10 to 0x1F */ 5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x20 to 0x2F */ 4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x30 to 0x3F */ 6u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x40 to 0x4F */ 4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x50 to 0x5F */ 5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x60 to 0x6F */ 4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x70 to 0x7F */ 7u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x80 to 0x8F */ 4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x90 to 0x9F */ 5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xA0 to 0xAF */ 4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xB0 to 0xBF */ 6u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xC0 to 0xCF */ 4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xD0 to 0xDF */ 5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xE0 to 0xEF */ 4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u /* 0xF0 to 0xFF */ };
代码的实现:
y = OSUnMapTbl[OSRdyGrp]; OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);
OSRdyGrp:优先级就绪组,那位为1代表哪行有就绪任务,取值范围0000 0000-1111 1111(0-255),最大时表示所有行都有任务。因为上面映射的时候说过,哪行有任务,哪位就置1,则现在则反过来,OSUnMapTbl[OSRdyGrp ] 根据哪位是1,求出在哪行,这里求出的是最高优先级所在的行。
举例说明:
优先级23 二进制00 010 110
根据映射关系,高三位代表行,0X010=2,则在第三行(从0开始),则映射后为OSRdyGrp=0000 0100
根据判定表OSUnMapTbl[OSRdyGrp ] =OSUnMapTbl[4 ] =2,即010。
根据映射关系,低三位代表列,0X110=6,则在第六列(从0开始),映射后为OSRdyTbl[2]=0100 0000
根据判定表OSUnMapTbl[OSRdyTbl[2] ] =OSUnMapTbl[64 ] =6,即110。