IRQ异常处理代码分析
在分析IRQ处理代码过程首先要说明下MACRO/MEND伪指令的用法,宏是一段独立的程序代码,它是通过伪指令定义的,在程序中使用宏指令即可调用宏。当程序被汇编时,汇编程序将对每个调用进行展开,用宏定义取代源程序中的宏指令。
MACRO、MEND
语法格式:
MACRO
[$ label] macroname{ $ parameter1, $ parameter,…… }
指令序列
MEND
IRQ异常处理代码分析
CODE32
AREA IRQ,CODE,READONLY
MACRO
$IRQ_Label HANDLER $IRQ_Exception_Function//此处通过伪指令定义了一个宏HANDLER,其中$IRQ_Lable为一个标号表示IRQ中断处理的起始地址,$IRQ_Exception_Function为宏的参数即ISR的地址
EXPORT $IRQ_Label ;//输出的标号
IMPORT $IRQ_Exception_Function //引用的外部标号
$IRQ_Label
SUB LR, LR, #4 //计算返回地址,此时ARM7处于IRQ模式,此处LR为LR_irq
STMFD SP!, {R0-R3, R12, LR} //保存任务环境,下面的过程使用了R0-R3 R12,LR等寄存器组,该寄存器组内容会发生变化,为了实现无缝返回故需要将该寄存器组进行压栈保存
MRS R3, SPSR //保存状态到R3
STMFD SP, {R3, SP, LR}^ //保存用户状态的R3,SP,LR,注意不能回写。此处不能回写的原因首先要了解^的作用(:如果寄存器组没有PC的话,寄存器组的内容为用户模式下的,有PC的话为自动更新CPSR。)由于寄存器组中没有PC故括号内的寄存器为用户模式下的,此时回写SP的话,回写的是用户模式下的SP,覆盖掉了IRQ模式的SP。第一个SP为SP_irq第二个SP为SP_usr。此处再一次压栈的作用是保存用户态下的寄存器,用来实现中断的嵌套
LDR R2, =OSIntNesting //OSIntNesting++
LDRB R1, [R2]
ADD R1, R1, #1
STRB R1, [R2]
SUB SP, SP, #4*3//由于压入IRQ堆栈了3个寄存器而没有回写SP,故需要认为的调整SP指向最后压入的数据。
MSR CPSR_c, #(NoInt | SYS32Mode) //切换到系统模式,为了实现中断的嵌套进入系统模式,此时中断是关闭的
CMP R1, #1
LDREQ SP, =StackUsr
BL $IRQ_Exception_Function //调用c语言的中断处理程序,在中断处理函数过程中调用了OS_EXIT_CRITICAL()开启中断,此处就可以实现中断的嵌套
MSR CPSR_c, #(NoInt | SYS32Mode) //切换到系统模式,此处应该是为了防止中断嵌套后模式的改变,强制返回系统模式,但是我觉得即使中断嵌套发生但最终还是会返回系统模式的,这一句有点多余(我是菜鸟个人感觉的)
LDR R2, =OsEnterSum //OsEnterSum置一,使OSIntExit退出时中断关闭为什么置一可以使其关中断将在后面的OSINTexit中分析说明
MOV R1, #1
STR R1, [R2]
BL OSIntExit
LDR R2, =OsEnterSum //因为中断服务程序要退出,所以OsEnterSum=0
MOV R1, #0
STR R1, [R2]
MSR CPSR_c, #(NoInt | IRQ32Mode) //切换回irq模式
LDMFD SP, {R3, SP, LR}^ //恢复用户状态的R3,SP,LR,注意不能回写
//如果回写的是用户的SP
LDR R0, =OSTCBHighRdy
LDR R0, [R0]
LDR R1, =OSTCBCur
LDR R1, [R1]
CMP R0, R1
ADD SP, SP, #4*3 //由于不能回写SP故需要认为调整SP的指针
MSR SPSR_cxsf, R3
LDMEQFD SP!, {R0-R3, R12, PC}^ //不进行任务切换
LDR PC, =OSIntCtxSw //进行任务切换
MEND
END
Ø OsEnterSum全局变量表示关中断的次数
Ø 中断级的切换调用OSIntCtxSw而不是OS_TASK_SW()是因为ISR已经将CPU的寄存器压入过栈了,不需要做第二次压栈。
Ø 存在一个疑问在调用OSIntExit的过程中已经进行了任务切换的判断和执行为什么在下边汇编语言中又判断执行了一次。我在网上查了下是说OSIntExit只是简单的查了优先级表,而没有做具体的任务切换,而具体工作交由后面的汇编程序执行,但是我没看明白,谁来给解释下
Ø 中断嵌套的实现是依靠了一:压入用户模式的CPSR/LR/SP。二:在ISR中开启中断实现的。
Ø 由于在压入sp_usr时,不能回写sp_irq(回写则保存的是sp_usr覆盖掉sp_irq)故需要人为的调整SP的指向
OSIntExit ()函数分析
void OSIntExit (void)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
if (OSRunning == TRUE) {//只有多任务调度开始才可以进行
OS_ENTER_CRITICAL();
if (OSIntNesting > 0){ //参数验证
OSIntNesting--;//中断退出中断嵌套层数减一
}
if ((OSIntNesting == 0) && (OSLockNesting == 0)) { //只有在中断结束和调度器开锁的情况下才可以进行任务调度(ISR中不允许任务调度)
OSIntExitY = OSUnMapTbl[OSRdyGrp];//此处使用了全局变量于OS_IntCtxSw的不同
OSPrioHighRdy = (INT8U)((OSIntExitY << 3) + OSUnMapTbl[OSRdyTbl[OSIntExitY]]);//获得最高优先级
if (OSPrioHighRdy != OSPrioCur) {//如果最高优先级和当前优先级不一样的话,那么进行任务切换
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];//获得最高优先级任务TCB
OSCtxSwCtr++; //任务切换次数加一
OSIntCtxSw();调用任务切换 }
}
OS_EXIT_CRITICAL();退出中断,此处于OSEntersum关中断计数变量有关了开中断函数是靠软中断实现的即
case 0x03: /* 开中断函数OS_EXIT_CRITICAL(),参考os_cpu.h文件 */
if (--OsEnterSum == 0)
{
__asm
{
MRS R0, SPSR
BIC R0, R0, #NoInt
MSR SPSR_c, R0
}
}
break;
}
如果在IRQ中断中没有将OSENTERSUM变量置一的话,在OS_ENTER_CRITICAL中OsEnterSum加一到退出时--OsEnterSum == 0则中断开启。所以为了防止此处中断开启,故需要将OsEnterSum置一,再调用OS_ENTER_CRITICAL()中OsEnterSum++,此时OsEnterSum==2,而在调用OS_EXIT_CRITICAL()中--OsEnterSum == 1,中断不开启,只是将中断嵌套层数减一。
}
Ø 任务调度不能发生在中断的过程中
Ø OS_ENTER_CRITICAL(),OS_EXIT_CRITICAL()是由软中断实现的
IRQ_Exception_Function 分析
改写μC/OSII 内核中 HANDLER 宏可以实现ARM的中断嵌套,这样做虽然提高了系统的实时性,但损害了系统运行的稳定性和可移植性。通过对中断过程的分析,下面给出一种编写中断服务程序的模板,充分利用ISR执行在特权模式——系统模式这一特点来实现中断嵌套的条件。中断服务程序模板如下:
void ISR(void)
{
OS_ENTER_CRITICAL();//在中断服务程序中关中断
/*清中断标志*/ //防止没有清中断标志使得中断多次进入
S_EXIT_CRITICAL(); //在中断服务程序中开中断
VICVectAddr=0; //将中断服务程序的入口地址置0
/*用户的C语言代码*/ //进行用户在中断中要做的工
}
Ø 由于Handler宏中已将LR、SPSR、返回地址和发生中断前的堆栈指针等寄存器入栈保存,所以接下来要做的就只剩下开关中断的工作。由于 在进入C中断处理程序之前进入的是关中断系统模式,所以必须在C语言中重新打开中断,而C语言是不能进行寄存器操作的,因此必须调用软中断 OS_EXIT_CRITICAL()重新打开中断。在开中断之前,要判断将全局变量OsEnterSum减1后是否为0,所以必须在调用开中断之前调用 软中断OS_ENTER_CRITICAL()将OsEnterSum变成1。在临界区中可以进行一些处理,如清中断标志、关低优先级中断等。进行C语言 中断服务程序之后要将VICVectAddr置位为0,这是ARM7处理器核的要求必须进行这样的编写,否则会导致一些错误(如不能第2次进入中断等)。