本帖最后由 Eric2013 于 2014-12-22 16:46 编辑
9.4 os_cpu_a.asm文件讲解 这个文件中函数的主要作用是实现任务切换,如果大家第5章的教程学懂了,这里面的函数要简单很多,μCOS-III中没有使用到SVC,只是使用了PendSV。所以大家只需懂得PendSV的使用即可。 9.4.1 开启多任务
NVIC_INT_CTRL EQU 0xE000ED04 ; Interruptcontrol state register. NVIC_SYSPRI14 EQU 0xE000ED22 ; System priorityregister (priority 14). NVIC_PENDSV_PRI EQU 0xFF ; PendSV priority value (lowest). NVIC_PENDSVSET EQU 0x10000000 ; Value totrigger PendSV exception.
;******************************************************************************************************** ; START MULTITASKING ; void OSStartHighRdy(void) ; ; Note(s) : 1) This function triggers a PendSV exception (essentially,causes a context switch) to cause ; the first task tostart. ; ; 2) OSStartHighRdy()MUST: ; a) Setup PendSVexception priority to lowest; ; b) Set initial PSP to0, to tell context switcher this is first run; ; c) Set the main stackto OS_CPU_ExceptStkBase ; d) Trigger PendSVexception; ; e) Enable interrupts(tasks will run with interrupts enabled). ;********************************************************************************************************
OSStartHighRdy LDR R0, =NVIC_SYSPRI14 ; Set the PendSVexception priority LDR R1, =NVIC_PENDSV_PRI STRB R1, [R0]
MOVS R0, #0 ; Set the PSP to 0 forinitial context switch call MSR PSP, R0
LDR R0, =OS_CPU_ExceptStkBase ; Initialize the MSP to theOS_CPU_ExceptStkBase LDR R1, [R0] MSR MSP, R1
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSVexception (causes context switch) LDR R1, =NVIC_PENDSVSET STR R1, [R0]
CPSIE I ;Enable interrupts at processor level
OSStartHang B OSStartHang ; Should never get here 下面说一下上面的汇编代码都实现了什么操作。 l LDR R0, =NVIC_SYSPRI14 LDR R1, =NVIC_PENDSV_PRI STRB R1, [R0] 这三段代码的主要功能设置PendSV,这里将其设置位最低优先级,为什么要设置位最低优先级?在前面的教程中已经说得很清楚了,这里就不再赘述了。 l MOVS R0, #0 MSR PSP, R0 设置PSP = 0,是告诉具体的任务切换程序(OS_CPU_PendSVHandler()),这是第一次任务切换。做过切换后PSP就不会为0了,后面讲OS_CPU_PendSVHandler()时会看到。 l LDR R0, =OS_CPU_ExceptStkBase LDR R1, [R0] MSR MSP,R1 设置MSP = OS_CPU_ExceptStkBase,开启多任务后MSP主要用于中断服务程序。 l LDR R0, =NVIC_INT_CTRL LDR R1,=NVIC_PENDSVSET STR R1, [R0] 这里是使能PendSV中断。 l CPSIE I OSStartHang B OSStartHang 这里主要是使能全局中断,使能后如果没有其它高优先级的中断需要执行,就会立即响应PendSV中断。所以说如果程序运行到了B OSStartHang,说明多任务启动失败了。
9.4.2 任务切换使能 主要有两个任务切换的函数,一个是任务级的任务切换,另一个是中断级的任务切换。这两个函数都只是使能PendSV中断,任务切换的实际工作是在PendSV中完成的。 ;********************************************************************************************************
; PERFORM A CONTEXT SWITCH (From task level) - OSCtxSw()
;
; Note(s) : 1) OSCtxSw() is called when OS wants to perform a task context switch. This function
; triggers the PendSV exception which is where the real work is done.
;********************************************************************************************************
OSCtxSw
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
;********************************************************************************************************
; PERFORM A CONTEXT SWITCH (From interrupt level) - OSIntCtxSw()
;
; Note(s) : 1) OSIntCtxSw() is called by OSIntExit() when it determines a context switch is needed as
; the result of an interrupt. This function simply triggers a PendSV exception which will
; be handled when there are no more interrupts active and interrupts are enabled.
;********************************************************************************************************
OSIntCtxSw
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
9.4.3 PendSV中断 关于PendSV中断,我们在第4章和第5章已经非常详细的讲解了,这里主要是给大家分析一下任务切换的处理过程,这个中断函数的前面有英文的注释说的非常好,大家有必要看一下。 ;********************************************************************************************************
; HANDLE PendSV EXCEPTION
; void OS_CPU_PendSVHandler(void)
;
; Note(s) : 1) PendSV is used to cause a context switch. This is a recommended method for performing
; context switches with Cortex-M3. This is because the Cortex-M3 auto-saves half of the
; processor context on any exception, and restores same on return from exception. So only
; saving of R4-R11 is required and fixing up the stack pointers. Using the PendSV exception
; this way means that context saving and restoring is identical whether it is initiated from
; a thread or occurs due to an interrupt or exception.
;
; 2) Pseudo-code is:
; a) Get the process SP, if 0 then skip (goto d) the saving part (first context switch);
; b) Save remaining regs r4-r11 on process stack;
; c) Save the process SP in its TCB, OSTCBCurPtr->OSTCBStkPtr = SP;
; d) Call OSTaskSwHook();
; e) Get current high priority, OSPrioCur = OSPrioHighRdy;
; f) Get current ready thread TCB, OSTCBCurPtr = OSTCBHighRdyPtr;
; g) Get new process SP from TCB, SP = OSTCBHighRdyPtr->OSTCBStkPtr;
; h) Restore R4-R11 from new process stack;
; i) Perform exception return which will restore remaining context.
;
; 3) On entry into PendSV handler:
; a) The following have been saved on the process stack (by processor):
; xPSR, PC, LR, R12, R0-R3
; b) Processor mode is switched to Handler mode (from Thread mode)
; c) Stack is Main stack (switched from Process stack)
; d) OSTCBCurPtr points to the OS_TCB of the task to suspend
; OSTCBHighRdyPtr points to the OS_TCB of the task to resume
;
; 4) Since PendSV is set to lowest priority in the system (by OSStartHighRdy() above), we
; know that it will only be run when no other exception or interrupt is active, and
; therefore safe to assume that context being switched out was using the process stack (PSP).
;********************************************************************************************************
OS_CPU_PendSVHandler
CPSID I ; Prevent interruption during context switch
MRS R0, PSP ; PSP is process stack pointer
CBZ R0, OS_CPU_PendSVHandler_nosave ; Skip register save the first time
SUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stack
STM R0, {R4-R11}
LDR R1, =OSTCBCurPtr ; OSTCBCurPtr->OSTCBStkPtr = SP;
LDR R1, [R1]
STR R0, [R1] ; R0 is SP of process being switched out
; At this point, entire context of process has been saved
OS_CPU_PendSVHandler_nosave
PUSH {R14} ; Save LR exc_return value
LDR R0, =OSTaskSwHook ; OSTaskSwHook();
BLX R0
POP {R14}
LDR R0, =OSPrioCur ; OSPrioCur = OSPrioHighRdy;
LDR R1, =OSPrioHighRdy
LDRB R2, [R1]
STRB R2, [R0]
LDR R0, =OSTCBCurPtr ; OSTCBCurPtr = OSTCBHighRdyPtr;
LDR R1, =OSTCBHighRdyPtr
LDR R2, [R1]
STR R2, [R0]
LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdyPtr->StkPtr;
LDM R0, {R4-R11} ; Restore r4-11 from new process stack
ADDS R0, R0, #0x20
MSR PSP, R0 ; Load PSP with new process SP
ORR LR, LR, #0x04 ; Ensure exception return uses process stack
CPSIE I
BX LR ; Exception return will restore remaining context
END
这里PendSV中断的主要作用就是保存当前任务的现场,恢复下一个任务的寄存器,从而切换到下一个任务中执行程序,并保证退出PendSV后使用的是PSP (任务堆栈指针)。下面先把这些汇编代码解释一下: OS_CPU_PendSVHandler ;xPSR, PC, LR, R12, R0-R3已自动保存入栈
CPSID I ;任务切换期间需要关中断
MRS R0, PSP ;R0 = PSP
CBZ R0, OS_CPU_PendSVHandler_nosave ;如果PSP==0跳转到OS_CPU_PendSVHandler_nosave去执行 在多任务
;的初始化时PSP被初始化为0,表示任务是第一次运行,不需要压栈
/********************************将当前的任务寄存器入栈*********************************************/
SUBS R0, R0, #0x20 ;R0 -= 0x20 保存R4-R11到任务堆栈 共32个字节
STM R0, {R4-R11} ;压栈R4-R11
LDR R1, =OSTCBCur ;获取OSTCBCur->OSTCBStkPtr,
;OSTCBStkPtr是OSTCBCur 中的第一个变量
LDR R1, [R1] ;R1 = *R1 (R1 = OSTCBCur)
STR R0, [R1] ;*R1 = R0 (*OSTCBCur = SP)
;将当前任务的堆栈保存到自己的任务控制块
;OSTCBCur->OSTCBStkPtr = PSP
;程序运行此位置,已经保存了当前任务的寄存器。
OS_CPU_PendSVHandler_nosave
PUSH {R14} ;R14 入栈
LDR R0, =OSTaskSwHook ;OSTaskSwHook(); R0 = &OSTaskSwHook
BLX R0 ;调用OSTaskSwHook()
POP {R14} ;恢复R14
/********************************加载要执行任务的寄存器*********************************************/
LDR R0, =OSPrioCur ;R0 = &OSPrioCur
LDR R1, =OSPrioHighRdy ;R1 = &OSPrioHighRdy
LDRB R2, [R1] ;R2 = *R1
STRB R2, [R0] ;*R0 = R2
;上面这四句函数的功能就是OSPrioCur = OSPrioHighRdy
LDR R0, =OSTCBCur ;R0 = &OSTCBCur
LDR R1, =OSTCBHighRdy ;R1 = &OSTCBHighRdy
LDR R2, [R1] ;R2 = *R1
STR R2, [R0] ;*R0 = R2
;上面这四句函数的功能就是OSTCBCur = OSTCBHighRdy
LDR R0, [R2] ;R0 = *R2 也就是R0 = OSTCBHighRdy又因为SP = OSTCBHighRdy->OSTCBStkPtr
;这条指令的含义就是R0=PSP。
LDM R0, {R4-R11} ;从任务堆栈恢复R4-R11
ADDS R0, R0, #0x20 ;调整R0 += 0x20
MSR PSP, R0 ;PSP = R0
ORR LR, LR, #0x04 ; 确保LR位2为1,返回后使用进程堆栈PSP
CPSIE I ; 开中断
BX LR ; 中断返回,剩下的8个寄存器会自动的出栈。
END
为了帮助大家更好的理解这个过程,这里举一个简单的例子: l 第一步:开始多任务后,系统就会从咱们上面说的OSStartHighRdy进入到PendSV中断,进入中断前使用的是MSP,也就是说自动入栈的8个寄存器被压入到MSP所指向的堆栈空间。 l 第二步:进入中断后开始MSP(因为中断程序中只能使用MSP,而不能使用PSP)。由于是第一次进入执行PendSV,剩下的8个需要手动入栈的寄存器不需要执行入栈操作(为什么不需要入栈操作? 因为第一次执行PendSV前的函数只是μCOS-III的初始化,而不是需要执行的任务,所以现场不需要保存)。 l 第三步:获得当前需要执行的最高优先级任务A后,程序就会从任务A的堆栈空间恢复需要手动入栈的8个寄存器R4-R11,在退出PendSV后,剩下的8个寄存器内容会自动得到恢复。由于前面初始化任务堆栈的时候,已经指定了任务的地址,所以这时就能正确的切换到任务A中。 l 第四步:当任务A被挂起或者其它的原因,系统再次进入到PendSV中断,进入中断前自动入栈的8个寄存器已经被保存到任务A的堆栈空间,进入中断后再将剩余8个需要手动入栈的8个寄存器推入任务A的堆栈空间,并保存PSP到任务A的TCB控制块,剩下执行的操作就和第三步相同了,获取另一个需要执行的高优先级任务B。初学的同学一定要不这个过程分析透,并牢记。这个过程很重要,要不任务怎么切换的都不知道。 9.5 总结 希望初学的同学将**所讲的三个文件自行分析一下,特别是PendSV中断函数的内容。本期教程的内容理解透了,对于后面学习源码大有裨益。
参考资料:
|