前言
本文对ucos中为什么使用PendSV,以及如何使用PendSV进行分析。
正文
ucos任务切换的核心工作是在PendSV异常服务函数中完成的。
如果通过SysTick中断进行上下文切换,理想情况如下:
现实情况是SysTick中断被触发时,系统有可能正在处理另一个中断请求。此时SysTick中断打断了正在处理的中断请求,情况如下:
IRQ正在执行时,如果SysTick完成了上下文切换,并且要回到线程模式,这是CPU会触发用法fault异常。
所以完成上下文切换并回到线程模式这件事,需要一个优先级很低的中断完成,低到不会打断其他任何中断,这就用到了PendSV中断。
首先需要把PendSV编程为最低优先级异常,如果某IRQ被SysTick抢占,SysTick只是会悬起一个PendSV中断,然后继续执行IRQ,等IRQ执行完毕再执行PendSV进行任务切换,如下:
这样就解决了会有在IRQ中返回线程模式的情况。
从以上分析可以看出,PendSV要保证中断优先级最低。
系统异常优先级寄存器如下:
可以看到设置PendSV优先级的寄存器为0xE000ED22
。
中断控制及状态寄存器如下:
可以看到对寄存器0xE000ED04
的第28位写入1可以悬起PendSV。
对应ucos代码在os_cpu_a.s
文件中,起始位置有一段宏定义如下:
对NVIC_SYSPRI14
写入NVIC_PENDSV_PRI
可以设置PendSV中断优先级为0xff
(最低)。
对NVIC_INT_CTRL
写入0x10000000
可以悬起PendSV。
之后有如下一段代码:
OSStartHighRdy
LDR R0, =NVIC_SYSPRI14 ; Set the PendSV exception priority
LDR R1, =NVIC_PENDSV_PRI
STRB R1, [R0]
MOVS R0, #0 ; Set the PSP to 0 for initial context switch call
MSR PSP, R0
LDR R0, =OS_CPU_ExceptStkBase ; Initialize the MSP to the OS_CPU_ExceptStkBase
LDR R1, [R0]
MSR MSP, R1
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
CPSIE I ; Enable interrupts at processor level
OSStartHang
B OSStartHang ; Should never get here
其中的
用于设置0xE000ED22
寄存器内容为0xFF。
用于设置PSP的值为0。
用于设置MSP的值为OS_CPU_ExceptStkBase
。
- MSP为主堆栈指针:复位后缺省使用的堆栈指针,用于操作系统内核以及异常处理例程(包
括中断服务例程)。 - PSP为进程堆栈指针:由用户的应用程序代码使用。
用于将0xE000ED04
寄存器赋值为0x10000000
,即悬起PendSV中断。
OSStartHighRdy
由OSStart()
函数调用。
接下来是PendSV中断服务函数的内容,如下:
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
其中的
用于判断PSP的值是否为0,如果是0说明是系统刚启动,不用保存就得任务信息,直接跳转到OS_CPU_PendSVHandler_nosave
执行,nosave意思是不进行压栈保存数据的处理。
用于将R4-R11
寄存器的内容压栈保护起来,R4-R11
被称为“被调用者保护寄存器”,R0-R3
、R12
、LR
和xPSR
被称为“调用者保护寄存器”,调用者保护寄存器在触发中断时,CPU就会自动压栈,所以ucos只负责将被调用者保护寄存器内容压栈,这部分内容在【Cortex-M3】中断处理时栈空间操作过程分析一文中有详细讲解。
用于将OSPrioCur
赋值为OSPrioHighRdy
。
用于将OSTCBCurPtr
赋值为OSTCBHighRdyPtr
。
OSTCBCurPtr
指向的一个os_tcb
的结构体的第一项为该任务的栈指针:
接下来的
用于将新的任务的R4-R11
出栈。
用于打开中断,跳转到新的任务开始执行代码。