先介绍一下优先级反转的相关知识:
优先级翻转是当一个高优先级任务通过信号量机制访问共享资源时,该信号量已被一低优先级任务占有,因此造成高优先级任务被许多具有较低优先级任务阻塞,实时性难以得到保证。
例如:有优先级为A、B和C三个任务,优先级A>B>C,任务A,B处于
挂起状态
,等待某一事件发生,任务C正在运行,此时任务C开始
使用
某一共享资源S。(A和C需要使用共享资源S,B不需要。)在使用中,任务A等待事件到来,任务A转为就绪态,因为它比任务C优先级高,所以立即执行。当任务A要使用共享资源S时,由于其正在被任务C使用,因此任务A被
挂起
,任务C开始运行。如果此时任务B等待事件到来,则任务B转为就绪态。由于任务B优先级比任务C高,因此任务B开始运行,直到其运行完毕,任务C才开始运行。直到任务C释放共享资源S后,任务A才得以执行。在这种情况下,优先级发生了翻转,任务B先于任务A运行。
这样的话,会导致如果有很多任务处于A和C优先级之间,在一定的条件下,都会优于A先执行。这样的话,A就被长时间阻塞。
解决优先级翻转问题有
优先级天花板(priority ceiling)和优先级继承(priority inheritance)
两种办法。
优先级天花板是当任务申请某资源时, 把该任务的优先级提升到可访问这个资源的所有任务中的最高优先级, 这个优先级称为该资源的优先级天花板。这种方法简单易行, 不必进行复杂的判断, 不管任务是否阻塞了高优先级任务的运行, 只要任务访问共享资源都会提升任务的优先级。
优先级继承是当任务A 申请共享资源S 时, 如果S正在被任务C 使用,通过比较任务C 与自身的优先级,如发现任务C 的优先级小于自身的优先级, 则将任务C的优先级提升到自身的优先级, 任务C 释放资源S 后,再恢复任务C 的原优先级。这种方法只在占有资源的低优先级任务阻塞了高优先级任务时才动态的改变任务的优先级,如果过程较复杂, 则需要进行判断。
notes
:挂起状态和就绪状态是两个不同的状态。最主要的区别是看任务是否在内存中。挂起后,任务将被暂时调出内存;就绪时,是调入内存。
本篇介绍的是进入中断和退出中断函数:(注释很详细)
/*$PAGE*/
/*
*********************************************************************************************************
* ENTER ISR
* 进入中断(执行中断)
* Description: This function is used to notify uC/OS-II that you are about to service an interrupt
* service routine (ISR). This allows uC/OS-II to keep track of interrupt nesting and thus
* only perform rescheduling at the last nested ISR.
该功能用来通知uc/os-II,正在进行一个中断服务。该功能可以使uc/os-II追踪中断嵌套信息并且只能在最后嵌套中断
*
* Arguments : none
*
* Returns : none
*
* Notes : 1) This function should be called ith interrupts already disabled在任务级不能调用该函数
* 2) Your ISR can directly increment OSIntNesting without calling this function because
* OSIntNesting has been declared 'global'.
如果系统使用的处理器能够执行自动的独立执行读取-修改-写入的操作,那么就可以直接递增
265 * 中断嵌套层数(OSIntNesting),这样可以避免调用函数所带来的额外开销。在中断服务子程序中
266 * 给OSIntNesting加1是不会有问题的,因为给OSIntNesting加1时,中断是关闭的
* 3) You MUST still call OSIntExit() even though you increment OSIntNesting directly.必须要有OSIntNesting()函数
* 4) You MUST invoke OSIntEnter() and OSIntExit() in pair. In other words, for every call
* to OSIntEnter() at the beginning of the ISR you MUST have a call to OSIntExit() at the
* end of the ISR. -- OSIntEnter()和OSIntExit()成对出现
* 5) You are allowed to nest interrupts up to 255 levels deep.中断深度可达255
* 6) I removed the OS_ENTER_CRITICAL() and OS_EXIT_CRITICAL() around the increment because
* OSIntEnter() is always called with interrupts disabled.
*********************************************************************************************************
*/
void OSIntEnter(void)
{
if (OSRunning == OS_TRUE) {
if (OSIntNesting < 255u) { /*如果中断层次<255*/
OSIntNesting++; /* Increment ISR nesting level 增加中断嵌套层次 */
}
}
}
/*$PAGE*/
/*
*********************************************************************************************************
* EXIT ISR
* 退出中断(中断完成)
* Description: This function is used to notify uC/OS-II that you have completed serviving an ISR. When
* the last nested ISR has completed, uC/OS-II will call the scheduler to determine whether
* a new, high-priority task, is ready to run.
* 该功能用来通知uc/os-II已完成中断。当最后一个中断嵌套完成时,
uc/os-II将调用调度程序确定是否有个新的高优先级的任务准备运行
* Arguments : none
*
* Returns : none
*
* Notes : 1) You MUST invoke OSIntEnter() and OSIntExit() in pair. In other words, for every call
* to OSIntEnter() at the beginning of the ISR you MUST have a call to OSIntExit() at the
* end of the ISR.
OSIntEnter()和OSIntExit()配套使用
* 2) Rescheduling is prevented when the scheduler is locked (see OS_SchedLock())
给调度器上锁用于禁止任务调度 (查看 OSSchedLock()函数)
*********************************************************************************************************
*/
void OSIntExit(void)
{
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register为CPU状态寄存器分配存储空间 */
OS_CPU_SR cpu_sr = 0u;
#endif
if (OSRunning == OS_TRUE) { /*如果操作系统还在运行,关闭中断*/
OS_ENTER_CRITICAL();
if (OSIntNesting > 0u) { /* Prevent OSIntNesting from wrapping 如果嵌套>0层,嵌套层减1 */
OSIntNesting--;
}
if (OSIntNesting == 0u) { /* Reschedule only if all ISRs complete ...所有的中断都完成了 */
if (OSLockNesting == 0u) { /* ... and not locked.并且嵌套锁也没有了,即已经完全解锁*/
OS_SchedNew(); /*进行新一轮的调用*/
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];/*在任务优先级表中找到最高优先级任务*/
if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy
检查具有最高优先级别的就绪任务的优先级是否是正在运行的任务的优先级。
如果是,不用进行上下文切换*/
#if OS_TASK_PROFILE_EN > 0u /*如果不是*/
OSTCBHighRdy->OSTCBCtxSwCtr++; /* Inc. # of context switches to this task 进行上下文切换 */
#endif
OSCtxSwCtr++; /* Keep track of the number of ctx switches上下文切换的次数(统计任务计数器) */
OSIntCtxSw(); /* Perform interrupt level ctx switch 做中断任务切换 */
}
}
}
OS_EXIT_CRITICAL(); /*打开中断*/
}
}
从上面的代码也可以看出来:进入中断函数很简单,就是将中断嵌套层数加1;退出中断函数相对复杂,涉及到了上下文切换及相关的判断。