uCOS II 是一个源代码公开的嵌入式实时操作系统,以其稳定可靠、高效、可移植性好,并且为占先式调度等特点,被广大工程技术人员使用。uCOS II 作为一种占先式的实时操作系统,在不少方面有着可以与商业内核相比的功能。但是uCOS II 不支持同优先级任务的调度,而实际的应用中,往往有些任务需要同优先级进行调度。如多点的温度或气压数据采集,若理解为不同的优先级任务去调度,不是一个好的逻辑设计,并且可能需要更多地考虑如何去实现不同任务的调度。另外, 如果允许同优先级任务调度,还可以解决优先级反转问题, 可以提升优先级低但占有资源的任务至申请该资源的高优先级任务的优先级, 直到低优先级的任务释放该资源, 恢复低优先级任务的优先级,高优先级的任务才占有该资源, 从而解决优先级反转问题。
1 uCOS II 的任务调度与OSTCBList结构
当两个或两个以上的任务有同样优先级,内核允许一个任务运行事先确定的一段时间,叫做时间额度,然后切换给另一个任务,也叫做时间片调度。内核在满足以下条件时, 把CPU 控制权交给下一个任务就绪态的任务:
当前任务已无事可做;
当前任务在时间片还没有结束时已经完成。
uCOS II 支持不同优先级间的任务调度,通过任务各自对应的优先级状态,快速地由OSTCBPrioTbl[OSPrioHighRdy]指针的指向切换到期望的TCB 来完成调度。这种调度与uCOS II 的OSTCBList 数据结构紧密相关, 其数据结构如图1 所示。
.jpg)
这是一个双向线性链表结构, 任务调度是从线性链表上的一个结点( 一个TCB 控制块) 切换到从链表上的另一个结点, 而不同的结点对应着不同的优先级, 这样来完成不同优先级的任务调度。所以要想实现uCOS II 的同优先级调度必须首先修改它的OSTCBList 数据结构,作如图2 所示的修改。
.jpg)
任务1 、任务2 和任务3 为不同优先级的任务,任务1、任务4 和任务5 为同优先级的任务。 当没有比任务1更高的优先级任务时, 每发生一次节拍中断, 任务1 、任务4 和任务5 就依次在运行态和就绪态切换。这里可以通过改变OSTCBPrioTbl[prio1]指向的任务TCB,也就是要修改OSSched()来实现。另外,很容易看出同优先级任务间的数据结构是一个循环链表结构, 所以TCB 也应相应的扩展两个指针域。新的数据结构将使就绪任务的TCB 在一个循环链表和线性链表上不断的切换。而对于等待任务的TCB , 也将在一个循环链表和线性链表上不断的切换。这里需要增加一个全局变量的数组OSTCBWaitTbl[OS_LOWEST_PRIO]指向相对应优先级的等待任务的TCB ,以便控制就绪任务进入等待任务或等待任务进入就绪任务时链表上任务TCB 的插入和删除。
2 对uCOS II的扩展及相关函数的修改
首先扩展TCB 的指针域, 定义新的TCB 为:
typedef struct os_tcb
{……
#if OS_TASK_PrioEqu_EN
struct OS_TCB *prioequNext;
struct OS_TCB*prioequPrev;
#endif
……}OS_TCB
增加的全局变量的数组定义为:
#if OS_TASK_PrioEqu_EN
OS_EXT OS_TCB OSTCBWaitTbl[OS_LOWEST_PRIO]
#endif
对OSTCBList 操作的每个相关函数都要修改, 主要修改以下方面的内容。
① 对于链表数据结构来说, 有以下几点。
链表的初始化及头结点。与OSTaskCreatExt()函数中的OSTCBInit()相关,需要完成同任务TCB 构成的循环链表及不同任务TCB 构成的线性链表。
链表结点的插入和删除,与任务的状态变化有关。当任务由就绪态进入等待状态或者由等待状态进入就绪态时, 任务TCB 需要添加到相应的链表中或从对应的链表中删除。这些函数与任务由就绪态进入等待状态或者由等待状态进入就绪态相关,有OSEventTaskWait(),OSEventTo(),OSTaskRdy(),OSTaskDel()以及OSTaskChangePrio()。
② 对同优先级任务调度时,由OSTCBPrioTbl[prio]指针的移动实现TCB 的切换,这里是对循环链表的操作,可以修改OSSched()实现。
③ 由时间延时满引起的超时,使等待的任务进入就绪态, 这一方面是对链表结点的插入和删除, 另外还是对任务延时的控制,需要修改OSTimeDly()和OSTimeTick()。
对这几个函数修改需要注意的是, 当要对某个优先级的循环链表插入结点前或删除结点后对应的TCB 为空时, 需要对就绪列表或等待列表相应的位置位或清零。
3 解决优先级反转的方法
任务对共享资源的占有和释放是通过对信号量的操作来实现的。
voidTask()
{……
OSSemPend();//等待信号量
共享资源;
OSSemPost();//释放信号量
……
}
那么,如何提升优先级低但占有资源的任务至申请该资源的高优先级任务的优先级,直到低优先级的任务释放该资源,恢复低优先级任务的优先级,高优先级的任务才占有该资源。首先这里需要改变任务优先级, 改变的优先级分别是提升优先级到高优先级和恢复任务到低优先级。显然,需要存储将要改变结果的优先级数,可以通过扩展OS_Event 来实现。
typestruct{
void *OSEventPtr;
……
int8u prio;}OS_Event
然后分别修改OSSemPend()和OSSemPost()。
void OSSemPend()
{……
if(pevent->OSEventCnt>0)
{pevebt->OSEventCnt--;
#if OS_TASK_PrioEqu_EN
if(pevent->OSEventCnt<1)
{pevent->prio=OSTCBCur->prio;}
//当资源恰好分配完时,记下当前任务优先级
#endif
OS_EXIT_CRITICAL();
*err=OS_NO_ERR;}
#if OS_TASK_PrioEqu_EN
elseif
{OSTaskChangePrio(pevent->prio,OSTCBCUR->prio);}
//当任务等待分配资源时提升占有资源任务的优先级
#endif
……
}
int8u OSSemPost()
{……
if(pevent->OSEventCnt<65535)
{pevent->OSEventCnt++;
#if OS_TASK_PrioEqu_EN
if(pevent->OSEventCnt<2)
OSTaskChangePrio(OSTCBCur->prio,pevent->prio);}
//当任务释放临界资源时恢复一个任务的优先级
#endif
OS_EXIT_CRITICAL();
return(OS_M_ERR);
……}
通过对这几个函数的修改, 实现了优先级的继承,解决了优先级的反转。问题的关键是对同优先级调度的支持和对资源分配的申请与释放过程的约定。
结语
uCOS II 是一个良好的实时操作系统。随着更多的人使用和不断的改进, 将会有更好的应用前景。