ucos中的异常处理

        之前一直看的都是linux,linux是非实时的操作系统,高优先级的任务不会立刻被调度,在linux中为了满足进程交互性的需求,发明了时间片,给所有任务都分配时间片,(后面再继续分析两种的不同。)

        ucos是一种实时操作系统。 当一个高优先级的任务准备就绪后,它会立刻被调度,并且一直执行直到任务结束,或是任务被其他更高优先级的任务抢占,亦或是任务需要等待信号量而变成休眠状态。在ucos中没有时间片的概念。 当然,与linux类似,ucos也通过时钟的tick来驱动,每次时钟tick时,发生中断,中断函数结束时,就会进行任务切换,因为可能会有高优先级的任务变成了就绪状态,如果有就立刻运行更高优先级的任务。

下面具体分析。

1. 栈的初始化

首先,对于每一种处理模式,都有各自的sp指针(user模式和system模式是共用的)。在板子初始化时,会调用InitStacks

;function initializing stacks
InitStacks
	;Don''t use DRAM,such as stmfd,ldmfd......
	;SVCstack is initialized before
	;Under toolkit ver 2.5, 'msr cpsr,r1' can be used instead of 'msr cpsr_cxsf,r1'
	mrs	r0,cpsr
	bic	r0,r0,#MODEMASK
	orr	r1,r0,#UNDEFMODE|NOINT
	msr	cpsr_cxsf,r1		;UndefMode
	ldr	sp,=UndefStack		; UndefStack=0x33FF_5C00

	orr	r1,r0,#ABORTMODE|NOINT
	msr	cpsr_cxsf,r1		;AbortMode
	ldr	sp,=AbortStack		; AbortStack=0x33FF_6000

	orr	r1,r0,#IRQMODE|NOINT
	msr	cpsr_cxsf,r1		;IRQMode
	ldr	sp,=IRQStack		; IRQStack=0x33FF_7000

	orr	r1,r0,#FIQMODE|NOINT
	msr	cpsr_cxsf,r1		;FIQMode
	ldr	sp,=FIQStack		; FIQStack=0x33FF_8000

	bic	r0,r0,#MODEMASK|NOINT
	orr	r1,r0,#SVCMODE
	msr	cpsr_cxsf,r1		;SVCMode
	ldr	sp,=SVCStack		; SVCStack=0x33FF_5800

	;USER mode has not be initialized.

	mov	pc,lr
	;The LR register won''t be valid if the current mode is not SVC mode.
arm刚启动时,默认是svc模式,上面进行的工作就是通过msr 改变cpsr寄存器,从而进入不同的模式,并设置该相应模式下的栈寄存器sp。这样,我们可以看出,不同的模式,有各自不同的栈。记得在x86上,发生中断时,中断函数用到的就是原来正常任务的栈,并没有自己独立的栈区。

2. 中断处理

前面分析过2440init.s里面中断的处理,为了适应ucos系统, 对中断的处理进行了修改。

 	;Initialize stacks
	bl	InitStacks

  	; Setup IRQ handler
	ldr	r0,=HandleIRQ       ;This routine is needed
	;ldr	r1,=IsrIRQ	  ;if there isn''t 'subs pc,lr,#4' at 0x18, 0x1c  被注释了
	ldr	r1, =OS_CPU_IRQ_ISR ;modify by txf, for ucos       调用的的是这里。
	str	r1,[r0]
OS_CPU_IRQ_ISR 	

	STMFD   SP!, {R1-R3}			; We will use R1-R3 as temporary registers
;----------------------------------------------------------------------------
;   R1--SP
;   R2--PC 
;   R3--SPSR
;------------------------------------------------------------------------
	MOV     R1, SP
	ADD     SP, SP, #12             ;Adjust IRQ stack pointer
	SUB     R2, LR, #4              ;Adjust PC for return address to task

	MRS     R3, SPSR				; Copy SPSR (Task CPSR)
	
	MSR     CPSR_cxsf, #SVCMODE|NOINT   ;Change to SVC mode

					; SAVE TASK''S CONTEXT ONTO OLD TASK''S STACK
									
	STMFD   SP!, {R2}				; Push task''s PC 
	STMFD   SP!, {R4-R12, LR}		; Push task''s LR,R12-R4
	
	LDMFD   R1!, {R4-R6}			; Load Task''s R1-R3 from IRQ stack 
	STMFD   SP!, {R4-R6}			; Push Task''s R1-R3 to SVC stack
	STMFD   SP!, {R0}			; Push Task''s R0 to SVC stack
	
	STMFD   SP!, {R3}			; Push task''s CPSR
	
	LDR     R0,=OSIntNesting        ;OSIntNesting++
	LDRB    R1,[R0]
	ADD     R1,R1,#1
	STRB    R1,[R0] 
	
	CMP     R1,#1                   ;if(OSIntNesting==1){
	BNE     %F1
	 
	LDR     R4,=OSTCBCur            ;OSTCBHighRdy->OSTCBStkPtr=SP;
	LDR     R5,[R4]
	STR     SP,[R5]                 ;}
	
1
	MSR    CPSR_c,#IRQMODE|NOINT    ;Change to IRQ mode to use IRQ stack to handle interrupt
	
	LDR     R0, =INTOFFSET
    LDR     R0, [R0]
       
    LDR     R1, IRQIsrVect
    MOV     LR, PC                          ; Save LR befor jump to the C function we need return back
    LDR     PC, [R1, R0, LSL #2]            ; Call OS_CPU_IRQ_ISR_handler();   
    
    MSR		CPSR_c,#SVCMODE|NOINT   ;Change to SVC mode
    BL 		OSIntExit           ;Call OSIntExit
    
    LDMFD   SP!,{R4}               ;POP the task''s CPSR 
    MSR		SPSR_cxsf,R4
    LDMFD   SP!,{R0-R12,LR,PC}^	   ;POP new Task''s context
我们目前有两套中断处理的函数:一个是官网自带的2440test,一个是ucos的处理。 2440test中,利用编译器自带的_irq函数,可以进行现场保护与恢复。而在ucos中,没有用_irq函数的特性,而是自己进行了寄存的保护和恢复。

首先,要进行现场保护。在中断发生时,系统正在运行task,并处于SVC模式下。中断之后,进入了IRQ模式,进入IRQ模式后,会运行中断处理。所以,在运行中断处理函数之前,需要保存context到任务的栈。 在 OS_CPU_IRQ_ISR中的前半段工作就是 先改变处理器工作模式到SVC模式,然后将所有的寄存器压栈,进行现场保护。之所以先将r1 r2 r3压栈,是因为利用这三个寄存器分别保存了在IRQ模式下的sp、lr、spsr寄存器,而这三个寄存器保存的是进入中断前的程序状态。这个自己想一下就明白了。

 然后,切换到irq模式,运行中断函数。在ucos的中断函数中,中断函数都是普通函数的形式,没有加_irq 说明符。

然后,在切换到SVC 模式,运行OSIntExit。在这个函数中:

void  OSIntExit (void)
{
#if OS_CRITICAL_METHOD == 3                                /* Allocate storage for CPU status register */
    OS_CPU_SR  cpu_sr = 0;
#endif
    if (OSRunning == OS_TRUE) {
        OS_ENTER_CRITICAL();
        if (OSIntNesting > 0) {                            /* Prevent OSIntNesting from wrapping       */
            OSIntNesting--;
        }
        if (OSIntNesting == 0) {                           /* Reschedule only if all ISRs complete ... */
            if (OSLockNesting == 0) {                      /* ... and not locked.                      */
                OS_SchedNew();
                if (OSPrioHighRdy != OSPrioCur) {          /* No Ctx Sw if current task is highest rdy */
                    OSTCBHighRdy  = OSTCBPrioTbl[OSPrioHighRdy];
#if OS_TASK_PROFILE_EN > 0
                    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();
    }
}
在中断退出的时候,会检测是否有更高优先级别的任务可以运行,如果有就立刻运行高优先级的任务。因为中断程序可能会使某些任务变成就绪态。
在OSIntCtxSW 进行 context的切换。前面已经将中断发生时的任务的 现场入栈了,因此,在OsIntCtxSw中,需要做的就是恢复新任务的执行context。

OSIntCtxSw
	;----------------------------------------------------------------------------------		
	; Call OSTaskSwHook();
	;----------------------------------------------------------------------------------	
	BL 		OSTaskSwHook
	
	;----------------------------------------------------------------------------------			
	; OSTCBCur = OSTCBHighRdy;
	;----------------------------------------------------------------------------------		
	LDR		R0, =OSTCBHighRdy
	LDR		R1, =OSTCBCur
	LDR		R0, [R0]
	STR		R0, [R1]
	
	;----------------------------------------------------------------------------------		
	; OSPrioCur = OSPrioHighRdy;
	;----------------------------------------------------------------------------------		
	LDR		R0, =OSPrioHighRdy
	LDR		R1, =OSPrioCur
	LDRB	R0, [R0]
	STRB	R0, [R1]
	
	;----------------------------------------------------------------------------------		
	; 		SP = OSTCBHighRdy->OSTCBStkPtr;
	;----------------------------------------------------------------------------------		
	LDR		R0, =OSTCBHighRdy
	LDR		R0, [R0]
	LDR		SP, [R0]
	
	;----------------------------------------------------------------------------------	
	; Restore New Task context
	;----------------------------------------------------------------------------------	
	LDMFD 	SP!, {R0}              ;POP CPSR
	MSR 	SPSR_cxsf, R0
	LDMFD 	SP!, {R0-R12, LR, PC}^	
OSintCtxSw 与 OSCtxSw 都是用来进行任务的context切换的,不同点是,osintctxsw用于中断处理中,它值负责恢复新任务的context,而OsctxSW还要先保存当前任务的context。


接下来还要执行OS_CPU_IRQ_ISR中的最后一部分:

  LDMFD   SP!,{R4}               ;POP the task''s CPSR 
  MSR	  SPSR_cxsf,R4
  LDMFD   SP!,{R0-R12,LR,PC}^	   ;POP new Task''s context


我觉得最后这部分有点奇怪。如果任务发生了切换,那么 最后这部分是多余的,因为新任务的context已经在寄存器中了。如果任务没有发生切换,那么这部分是需要的,需要将原任务的context从栈中恢复到寄存器中。
或许还是我对汇编指令的理解不够。。。。
从上面可以看出,在2440上实现的ucos,也是不支持中断嵌套的。有兴趣的同学,可以自己想办法去实现。


你可能感兴趣的:(mini2440裸机编程)