FreeRtos学习笔记(10)任务切换原理刨析
STM32 单片机启动流程中介绍了SP和PC寄存器,
STM32单片机bootloader扫盲中说过如何通过控制SP和PC寄存器从而控制程序从bootLoader跳转到APP,RTOS任务切换和BootLoader与APP之间的跳转类似,也是通过控制SP和PC指针实现任务之间跳转。
MSP和PSP
在中断服务函数使用MSP作为堆栈指针,如果工程中没有特殊设置(即非RTOS工程)整个工程都会默认使用MSP。如果工程使用了RTOS,则除了中断服务函数外,其他任务使用PSP作为堆栈指针。
Cortex‐M3 拥有两个堆栈指针,然而它们是banked,因此任一时刻只能使用其中的一个。
主堆栈指针(MSP):复位后缺省使用的堆栈指针,用于操作系统内核以及异常处理例程(包括中断服务例程)
进程堆栈指针(PSP):由用户的应用程序代码使用。
堆栈指针的最低两位永远是 0,这意味着堆栈总是 4 字节对齐的。 在 ARM 编程领域中,凡是打断程序顺序执行的事件,都被称为异常(exception)。除了外部中断外,当有指令执行了“非法操作”,或者访问被禁的内存区间,因各种错误产生的 fault,以及不可屏蔽中断发生时,都会打断程序的执行,这些情况统称为异常。在不严格的上下文中,异常与中断也可以混用。另外,程序代码也可以主动请求进入异常状态的(常用于系统调用)。
为什么堆栈指针有两个?
- 可以将用户应用程序的堆栈与特权级/操作系统内核(kernel)的堆栈分开,阻止用户程序访问内核的堆栈,消除了内核数据被破坏的可能。(举个例子,windos系统下,一个软件卡死并不会使整个windos操作系统卡死)
- 可以使RTOS实现任务间“可抢占的系统调用”,大幅提高实时性能(中断前使用PSP,进入中断服务函数后会自动使用MSP,在中断中修改PSP值,退出中断服务函数后SP会自动切换到PSP,而PSP的值在中断中修改过,退出中断时会根据新的PSP, POP出PC寄存器及其他寄存器值,从而完成任务切换)
MSP和PSP之间如何切换?
M3权威指南中指出MSP和PSP之间的切换有两种方法:
- 在特权级线程模式下写CONTROL[1]
- 在中断服务函数结束时修改LR寄存器(R14),下图为LR寄存器低四位所代表的含义
FreeRtos中就是通过修改LR寄存器值实现从MSP切换到PSP的。
上电后默认使用MSP,然后进行外设初始化,创建任务,最后调用vTaskStartScheduler()启动RTOS,在vTaskStartScheduler()中会调用xPortStartScheduler()函数,xPortStartScheduler()函数中会调用port.c中的prvPortStartFirstTask();启动第一个任务。
prvPortStartFirstTask()为一个汇编函数,主要功能就是触发SVC中断
static void prvPortStartFirstTask( void )
{
__asm volatile (
" ldr r0, =0xE000ED08 \n"/* Use the NVIC offset register to locate the stack. */
" ldr r0, [r0] \n"
" ldr r0, [r0] \n"
" msr msp, r0 \n"/* Set the msp back to the start of the stack. */
" cpsie i \n"/* Globally enable interrupts. */
" cpsie f \n"
" dsb \n"
" isb \n"
" svc 0 \n"/* 触发SVC异常,在SVC中断服务函数中启动第一个任务. */
" nop \n"
" .ltorg \n"
);
}
SVC中断服务函数-- vPortSVCHandler()也是一个汇编函数,主要干了两件事,恢复任务现场(也就是将任务栈中保存的寄存器值POP到对应寄存器);将MSP切换为PSP;汇编语句具体含义可以对照M3权威指南中第四章指令集自行翻译。
void vPortSVCHandler( void )
{
__asm volatile (
" ldr r3, pxCurrentTCBConst2 \n"/* Restore the context.