一、开启任务调度器
函数原型:
void vTaskStartScheduler( void )
作用:用于启动任务调度器,任务调度器启动后, FreeRTOS 便会开始进行任务调度
内部实现机制(以动态创建为例):
(1)首先,判断动态创建任务 or 静态创建任务
(2)创建空闲任务
(3)如果使能软件定时器,则创建软件定时器任务
(4)关闭中断,防止调度器开启之前或过程中,受中断干扰,会在运行第一个任务时打开中断
(5)初始化全局变量,并将任务调度器的运行标志设置为已运行
(6)初始化任务运行时间统计功能的时基定时器
(7)调用函数 xPortStartScheduler()
xPortStartScheduler() :
(1)检测用户在 FreeRTOSConfig.h 文件中对中断的相关配置是否有误
(2)配置 PendSV 和 SysTick 的中断优先级为最低优先级
(3)调用函数 vPortSetupTimerInterrupt()配置 SysTick(主要配置定时器的中断周期)
(4)初始化临界区嵌套计数器为 0
(5)调用函数 prvEnableVFP()使能 FPU(M4与M7内核才有FPU)
(6)调用函数 prvStartFirstTask()启动第一个任务
二、启动第一个任务
要运行任务,必须把任务的寄存器的值加载到CPU的寄存器中,任务A的寄存器值,在一开始创建任务时就保存在任务堆栈里边
注:
1、中断产生时,硬件自动将xPSR,PC(R15),LR(R14),R12,R3-R0出/入栈(保存和恢复);
而R4~R11需要手动出/入栈(保存和恢复)
2、进入中断后硬件会强制使用MSP指针 ,此时LR(R14)的值将会被自动被更新为特殊的EXC_RETURN
2.1 开启第一个任务函数:prvStartFirstTask ()
__asm void prvStartFirstTask( void )
{/* 八字节对齐 */PRESERVE8/* 将向量表偏移量寄存器地址存储到R0 */ldr r0, =0xE000ED08/* 向量表偏移量寄存器存储着向量表的起始地址 *//* 将向量表起始地址存储到R0 */ldr r0, [ r0 ]/* 将MSP的初始值存储到R0 */ldr r0, [ r0 ]/* 使MSP回到最初值 */msr msp, r0/* 使能中断 */cpsie icpsie fdsbisb/* 触发SVC中断,开启第一个任务 */svc 0nopnop
/* *INDENT-ON* */
}
功能:用于初始化启动第一个任务前的环境,主要是获取MSP 指针的初始值,并使能全局中断,触发SVC,开启一个任务
Q:向量表偏移量寄存器为什么会出现在这?
答:
经过前面一系列的操作,MSP的值已经改变,为了获取MSP的初始值,需要通过VTOR寄存器,找到向量表的起始地址,再通过此地址才可以找到MSP的初始值
程序在运行过程中需要一定的栈空间来保存局部变量等一些信息。当有信息保存到栈中时,
MCU 会自动更新 SP 指针,ARM Cortex-M 内核提供了两个栈空间:
(1)MSP(主堆栈指针):它由 OS 内核、异常服务例程以及所有需要特权访问的应用程序代码来使用
(2)PSP(进程堆栈指针):用于常规的应用程序代码(不处于异常服务例程中时)
在FreeRTOS中,中断使用MSP(主堆栈),中断以外使用PSP(进程堆栈)
2.2 SVC中断函数:void vPortSVCHandler( )
函数功能:恢复现场、开启中断,并跳转到PC所指向的函数中(第一个任务函数中)
//进行8字节对齐 PRESERVE8//将当前任务TCB结构体的指针的地址存储在R3ldr r3, = pxCurrentTCB /* Restore the context. *///通过地址找到当前任务结构的地址ldr r1, [ r3 ] /* Use pxCurrentTCBConst to get the pxCurrentTCB address. *///通过地址找到第一个成员变量,栈顶指针(保存着更新后的地址,已经开辟了寄存器组的空间)ldr r0, [ r1 ] /* The first item in pxCurrentTCB is the task top of stack. *///出栈ldmia r0 !, { r4 - r11 } /* 将出栈后的栈顶指针的值赋值给PSP,用于后面的现场保存 */msr psp, r0 /* Restore the task stack pointer. */isb//R0清0mov r0, # 0//使能所有中断msr basepri, r0//R14与0xd进行或运算,表示退出中断后,进入线程模式,并使用PSPorr r14, # 0xd//跳转到PC所指向地址的函数中bx r14
R14:链接寄存器,在中断中记录了异常返回值 EXC_RETURN
当从 SVC 中断服务退出前,通过向 r14 寄存器最后 4 位按位或上 0x0D,使得硬件在退出时使用进程堆栈指针 PSP 完成出栈操作并返回后进入任务模式、返 回 Thumb 状态。在 SVC 中断服务里面,使用的是 MSP 堆栈指针,是处在 ARM 状态。
当 r14 为 0xFFFFFFFX,执行是中断返回指令,cortext-m3 的做法,X 的 bit0 为 1 表示 返回 thumb 状态,bit1 和 bit2 分别表示返回后 sp 用 msp 还是 psp、以及返回到特权模式还 是用户模式
异常返回,这个时候出栈使用的是 PSP 指针,自动将栈中的剩下 内容加载到 CPU 寄存器: xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0 (任务的形参)同时 PSP 的值也将更新,即指向任务栈的栈顶
注意:SVC中断只在启动第一次任务时会调用一次,以后均不调用
三、任务切换
本质:CPU寄存器值的切换
假设当由任务A切换到任务B时,主要分为两步:
第一步:需暂停任务A的执行,并将此时任务A的寄存器保存到任务堆栈,这个过程叫做保存现场;
第二步:将任务B的各个寄存器值(被存于任务堆栈中)恢复到CPU寄存器中,这个过程叫做恢复现场;
对任务A保存现场,对任务B恢复现场,这个整体的过程称之为:上下文切换
注意:任务切换的过程在PendSV中断服务函数里边完成