μC/OS中,中断服务子程序要用汇编语言来写。然而,如果用户使用的C语言编译器支持在线汇编语言的话,用户可以直接将中断服务子程序代码放在C语言的程序文件中。中断服务子程序的示意码如程序清单L3.15所示。
程序清单 L3.15 μC/OS-II中的中断服务子程序.
用户中断服务子程序:
保存全部CPU寄存器; (1)
调用OSIntEnter()或OSIntNesting直接加1; (2)
执行用户代码做中断服务; (3)
调用OSIntExit(); (4)
恢复所有CPU寄存器; (5)
执行中断返回指令; (6)
用户代码应该将全部CPU寄存器推入当前任务栈[L3.15(1)]。注意,有些微处理器,例如Motorola68020(及68020以上的微处理器),做中断服务时使用另外的堆栈。
μC/OS-Ⅱ可以用在这类微处理器中,当任务切换时,寄存器是保存在被中断了的那个任务的栈中的。
μC/OS-Ⅱ需要知道用户在做中断服务,故用户应该调用OSIntEnter(),或者将全程变量OSIntNesting[L3.15(2)]直接加1,如果用户使用的微处理器有存储器直接加1的单条指令的话。如果用户使用的微处理器没有这样的指令,必须先将OSIntNesting读入寄存器,再将寄存器加1,然后再写回到变量OSIatNesting中去,就不如调用OSIatEnter()。OSIntNesting是共享资源。OSIntEnter()把上述三条指令用开中断、关中断保护起来,以保证处理OSIntNesting时的排它性。直接给OSIntNesting加1比调用OSIntEnter()快得多,可能时,直接加1更好。要当心的是,在有些情况下,从OSIntEnter()返回时,会把中断开了。遇到这种情况,在调用OSIntEnter()之前要先清中断源,否则,中断将连续反复打入,用户应用程序就会崩溃!
上述两步完成以后,用户可以开始服务于叫中断的设备了[L3.15(3)]。这一段完全取决于应用。μC/OS-Ⅱ允许中断嵌套,因为μC/OS-Ⅱ跟踪嵌套层数OSIntNesting。然而,为允许中断嵌套,在多数情况下,用户应在开中断之前先清中断源。
调用脱离中断函数OSIntExit()[L3.15(4)]标志着中断服务子程序的终结,OSIntExit()将中断嵌套层数计数器减1。当嵌套计数器减到零时,所有中断,包括嵌套的中断就都完成了,此时μC/OS-Ⅱ要判定有没有优先级较高的任务被中断服务子程序(或任一嵌套的中断)唤醒了。如果有优先级高的任务进入了就绪态,μC/OS-Ⅱ就返回到那个高优先级的任务,OSIntExit()返回到调用点[L3.15(5)]。保存的寄存器的值是在这时恢复的,然后是执行中断返回指令[L3.16(6)]。注意,如果调度被禁止了(OSIntNesting>0),μC/OS-Ⅱ将被返回到被中断了的任务。
以上描述的详细解释如图F3.5所示。中断来到了[F3.5(1)]但还不能被被CPU识别,也许是因为中断被μC/OS-Ⅱ或用户应用程序关了,或者是因为CPU还没执行完当前指令。一旦CPU响应了这个中断[F3.5(2)],CPU的中断向量(至少大多数微处理器是如此)跳转到中断服务子程序[F3.5(3)]。如上所述,中断服务子程序保存CPU寄存器(也叫做 CPU context)[F3.5(4)],一旦做完,用户中断服务子程序通知μC/OS-Ⅱ进入中断服务子程序了,办法是调用OSIntEnter()或者给OSIntNesting直接加1[F3.5(5)]。然后用户中断服务代码开始执行[F3.5(6)]。用户中断服务中做的事要尽可能地少,要把大部分工作留给任务去做。中断服务子程序通知某任务去做事的手段是调用以下函数之一:OSMboxPost(),OSQPost(),OSQPostFront(),OSSemPost()。中断发生并由上述函数发出消息时,接收消息的任务可能是,也可能不是挂起在邮箱、队列或信号量上的任务。用户中断服务完成以后,要调用OSIntExit()[F3.5(7)]。从时序图上可以看出,对被中断了的任务说来,如果没有高优先级的任务被中断服务子程序激活而进入就绪态,OSIntExit()只占用很短的运行时间。进而,在这种情况下,CPU寄存器只是简单地恢复[F3.5(8)]并执行中断返回指令[F3.5(9)]。如果中断服务子程序使一个高优先级的任务进入了就绪态,则OSIntExit()将占用较长的运行时间,因为这时要做任务切换[F3.5(10)]。新任务的寄存器内容要恢复并执行中断返回指令[F3.5(12)]。
图3.5 中断服务
进入中断函数OSIntEnter()的代码如程序清单L3.16所示,从中断服务中退出函数OSIntExit()的代码如程序清单L3.17所示。如前所述,OSIntEnter()所做的事是非常少的。
程序清单 L3.16 通知μC/OS-Ⅱ,中断服务子程序开始了.
void OSIntEnter (void)
{
OS_ENTER_CRITICAL();
OSIntNesting++;
OS_EXIT_CRITICAL();
}
程序清单 L3.17 通知μC/OS-Ⅱ,脱离了中断服务
void OSIntExit (void)
{
OS_ENTER_CRITICAL(); (1)
if ((--OSIntNesting | OSLockNesting) == 0) { (2)
OSIntExitY = OSUnMapTbl[OSRdyGrp]; (3)
OSPrioHighRdy = (INT8U)((OSIntExitY << 3) +
OSUnMapTbl[OSRdyTbl[OSIntExitY]]);
if (OSPrioHighRdy != OSPrioCur) {
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
OSCtxSwCtr++;
OSIntCtxSw(); (4)
}
}
OS_EXIT_CRITICAL();
}
OSIntExit()看起来非常像OSSched()。但有三点不同。第一点,OSIntExit()使中断嵌套层数减1[L3.17(2)]而调度函数OSSched()的调度条件是:中断嵌套层数计数器和锁定嵌套计数器(OSLockNesting)二者都必须是零。第二个不同点是,OSRdyTbl[]所需的检索值Y是保存在全程变量OSIntExitY中的[L3.17(3)]。这是为了避免在任务栈中安排局部变量。这个变量在哪儿和中断任务切换函数OSIntCtxSw()有关,(见9.04.03节,中断任务切换函数)。最后一点,如果需要做任务切换,OSIntExit()将调用OSIntCtxSw()[L3.17(4)]而不是调用OS_TASK_SW(),正像在OSSched()函数中那样。
调用中断切换函数OSIntCtxSw()而不调用任务切换函数OS_TASK_SW(),有两个原因,首先是,如程序清单中L3.5(1)和图F3.6(1)所示,一半的工作,即CPU寄存器入栈的工作已经做完了。第二个原因是,在中断服务子程序中调用OSIntExit()时,将返回地址推入了堆栈[L3.15(4)和F3.6(2)]。OSIntExit()中的进入临界段函数OS_ENTER_CRITICAL()或许将CPU的状态字也推入了堆栈L3.7(1)和F3.6(3)。这取决于中断是怎么被关掉的(见第8章移植μC/OS-Ⅱ)。最后,调用OSIntCtxSw()时的返回地址又被推入了堆栈[L3.17(4)和F3.1(4)],除了栈中不相关的部分,当任务挂起时,栈结构应该与μC/OS-Ⅱ所规定的完全一致。OSIntCtxSw()只需要对栈指针做简单的调整,如图F3.6(5)所示。换句话说,调整栈结构要保证所有挂起任务的栈结构看起来是一样的。
图3.6中断中的任务切换函数OSIntCtxSw()调整栈结构
有的微处理器,像Motorola 68HC11中断发生时CPU寄存器是自动入栈的,且要想允许中断嵌套的话,在中断服务子程序中要重新开中断,这可以视作一个优点。确实,如果用户中断服务子程序执行得非常快,用户不需要通知任务自身进入了中断服务,只要不在中断服务期间开中断,也不需要调用OSIntEnter()或OSIntNesting加1。程序清单L3。18中的示意代码表示这种情况。一个任务和这个中断服务子程序通讯的唯一方法是通过全程变量。
程序清单 L3.18 Motorola 68HC11中的中断服务子程序
M68HC11_ISR: /* 快中断服务程序,必须禁止中断*/
所有寄存器被CPU自动保存;
执行用户代码以响应中断;
执行中断返回指令;