之前一直看的都是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
;----------------------------------------------------------------------------------
; 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,也是不支持中断嵌套的。有兴趣的同学,可以自己想办法去实现。