ucos II任务管理之四:改变任务的优先级
在ucos II 里,任务的优先级是可以改变的。可以修改任务优先级的一个好处就是可以解决优先级翻转的问题。
什么是优先级翻转问题?偷偷地百度一下
所谓优先级翻转问题(priority inversion)即当一个高优先级任务通过信号量机制访问共享资源时,该信号量已被一低优先级任务占有,而这个低优先级任务在访问共享资源时可能又被其它一些中等优先级任务抢先,因此造成高优先级任务被许多具有较低优先级任务阻塞,实时性难以得到保证。
在访问共享资源时,恰当地修改任务的优先级就可以解决优先级翻转问题了。
但改变任务的优先级也是很花时间的,如果不发生优先级翻转而提升了任务的优先级,释放资源后又改回原优先级,则无形中浪费了许多CPU时。在mcu21项目中并没有改变任务的优先级。
改变任务的优先级的操作也是很简单的。主要是调用UCOS II 系统函数OSTaskChangePrio (INT8U oldprio, INT8U newprio),它需要两个参数,一个任务原优先级,一个是任务改变后的优先级。
下面,又是再一次,深入地解剖下OSTaskChangePrio (INT8U oldprio, INT8U newprio)的实现过程。这个过程很枯燥.
程序清单 L 4.15 OSTaskChangePrio(). |
INT8U OSTaskChangePrio (INT8U oldprio, INT8U newprio) |
{ |
OS_TCB *ptcb; |
OS_EVENT *pevent; |
INT8U x; |
INT8U y; |
INT8U bitx; |
INT8U bity; |
|
|
if ((oldprio >= OS_LOWEST_PRIO && oldprio != OS_PRIO_SELF) || (1) |
newprio >= OS_LOWEST_PRIO) { |
return (OS_PRIO_INVALID); |
} |
OS_ENTER_CRITICAL(); |
if (OSTCBPrioTbl[newprio] != (OS_TCB *)0) { (2) |
OS_EXIT_CRITICAL(); |
return (OS_PRIO_EXIST); |
} else { |
OSTCBPrioTbl[newprio] = (OS_TCB *)1; (3) |
OS_EXIT_CRITICAL(); |
y = newprio >> 3; (4) |
bity = OSMapTbl[y]; |
x = newprio & 0x07; |
bitx = OSMapTbl[x]; |
OS_ENTER_CRITICAL(); |
if (oldprio == OS_PRIO_SELF) { (5) |
oldprio = OSTCBCur->OSTCBPrio; |
} |
if ((ptcb = OSTCBPrioTbl[oldprio]) != (OS_TCB *)0) { (6) |
OSTCBPrioTbl[oldprio] = (OS_TCB *)0; (7) |
if (OSRdyTbl[ptcb->OSTCBY] & ptcb->OSTCBBitX) { (8) |
if ((OSRdyTbl[ptcb->OSTCBY] &= ~ptcb->OSTCBBitX) == 0) {(9) |
OSRdyGrp &= ~ptcb->OSTCBBitY; |
} |
OSRdyGrp |= bity; (10) |
OSRdyTbl[y] |= bitx; |
} else { |
if ((pevent = ptcb->OSTCBEventPtr) != (OS_EVENT *)0) { (11) |
if ((pevent->OSEventTbl[ptcb->OSTCBY] &=
|
pevent->OSEventGrp &= ~ptcb->OSTCBBitY; |
} |
pevent->OSEventGrp |= bity; (12) |
pevent->OSEventTbl[y] |= bitx; |
} |
} |
OSTCBPrioTbl[newprio] = ptcb; (13) |
ptcb->OSTCBPrio = newprio; (14) |
ptcb->OSTCBY = y; (15) |
ptcb->OSTCBX = x; |
ptcb->OSTCBBitY = bity; |
ptcb->OSTCBBitX = bitx; |
OS_EXIT_CRITICAL(); |
OSSched(); (16) |
return (OS_NO_ERR); |
} else { |
OSTCBPrioTbl[newprio] = (OS_TCB *)0; (17) |
OS_EXIT_CRITICAL(); |
return (OS_PRIO_ERR); |
} |
} |
} |
修改任务的优先级,需要经过以下几大步骤:
1.首先,用户不能改变空闲任务的优先级[L4.15(1)],但用户可以改变调用本函数的任务或者其它任务的优先级。为了改变调用本函数的任务的优先级,用户可以指定该任务当前的优先级或OS_PRIO_SELF,OSTaskChangePrio()会决定该任务的优先级。用户还必须指定任务的新(即想要的)优先级。因为μC/OS-Ⅱ不允许多个任务具有相同的优先级,所以OSTaskChangePrio()需要检验新优先级是否是合法的(即不存在具有新优先级的任务)[L4.15(2)]。如果新优先级是合法的,μC/OS-Ⅱ通过将某些东西储存到OSTCBPrioTbl[newprio]中保留这个优先级[L4.15(3)]。如此就使得OSTaskChangePrio()可以重新允许中断,因为此时其它任务已经不可能建立拥有该优先级的任务,也不能通过指定相同的新优先级来调用OSTaskChangePrio()。接下来OSTaskChangePrio()可以预先计算新优先级任务的OS_TCB中的某些值[L4.15(4)]。而这些值用来将任务放入就绪表或从该表中移除(参看3.04,就绪表)。
2.其次,OSTaskChangePrio()检验目前的任务是否想改变它的优先级[L4.15(5)]。然后,OSTaskChangePrio()检查想要改变优先级的任务是否存在[L4.15(6)]。很明显,如果要改变优先级的任务就是当前任务,这个测试就会成功。但是,如果OSTaskChangePrio()想要改变优先级的任务不存在,它必须将保留的新优先级放回到优先级表OSTCBPrioTbl[]中[L4.15(17)],并返回给调用者一个错误码。
3.再次,OSTaskChangePrio()可以通过插入NULL指针将指向当前任务OS_TCB的指针从优先级表中移除了[L4.15(7)]。这就使得当前任务的旧的优先级可以重新使用了。接着,我们检验一下OSTaskChangePrio()想要改变优先级的任务是否就绪[L4.15(8)]。如果该任务处于就绪状态,它必须在当前的优先级下从就绪表中移除[L4.15(9)],然后在新的优先级下插入到就绪表中[L4.15(10)]。这儿需要注意的是,OSTaskChangePrio()所用的是重新计算的值[L4.15(4)]将任务插入就绪表中的。
如果任务已经就绪,它可能会正在等待一个信号量、一封邮件或是一个消息队列。如果OSTCBEventPtr非空(不等于NULL)[L4.15(8)],OSTaskChangePrio()就会知道任务正在等待以上的某件事。如果任务在等待某一事件的发生,OSTaskChangePrio()必须将任务从事件控制块(参看6.00,事件控制块)的等待队列(在旧的优先级下)中移除。并在新的优先级下将事件插入到等待队列中[L4.15(12)]。任务也有可能正在等待延时的期满(参看第五章-任务管理)或是被挂起(参看4.07,挂起任务,OSTaskSuspend())。在这些情况下,从L4.15(8)到L4.15(12)这几行可以略过。
4.最后,这也是最关键的一步,任务的优先级就是这里被修改的,其实简单来说就是修改任务控制块里面的参数。在任务块里,OSTCBPrio ,OSTCBY ,OSTCBX ,OSTCBBitY , OSTCBBitX 这几个变量的值标注了任务的优先级。修改任务的优先级就是修改这几个变量的值。OSTaskChangePrio()将指向任务OS_TCB的指针存到OSTCBPrioTbl[]中[L4.15(13)]。新的优先级被保存在OS_TCB中[L4.15(14)],重新计算的值也被保存在OS_TCB中[L4.15(15)]。OSTaskChangePrio()完成了关键性的步骤后,在新的优先级高于旧的优先级或新的优先级高于调用本函数的任务的优先级情况下,任务调度程序就会被调用[L4.15(16)]。