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中断函数的内容。本期教程的内容理解透了,对于后面学习源码大有裨益。
参考资料: |