1. 开启任务调度器
vTaskStartScheduler()
,用于启动任务调度器,任务调度器启动后,FreeRTOS便会开始进行任务调度。
该函数内部实现:
-
创建空闲任务
-
如果使能软件定时器,则创建定时器任务
-
关闭中断,防止调度器开启之前或过程中,受中断干扰,会在运行第一个任务时打开中断
-
初始化全局变量,并将任务调度器的运行标志设置为已运行
-
初始化任务运行时间统计功能的时基定时器
-
调用函数
xPortStartScheduler()
作用:该函数用于完成启动任务调度器中与硬件架构相关的配置部分, 以及启动第一个任务该函数内部实现,如下: 1. 检测用户在FreeRTOSConfig.h文件中对中断的相关配置是否有误 2. 配置PendSV和SysTick的中断优先级为最低优先级 3. 调用函数vPortSetupTimerlnterrupt()配置SysTick 4. 初始化临界区嵌套计数器为O 5. 调用函数prvEnableVFP()使能FPU 6. 调用函数prvStartFirstTask()启动第一个任务
2. 启动第一个任务
prvStartFirstTask() /*开启第一个任务*/
vPortSVCHandler() /*SVC中断服务函数*/
2.1 怎么启动第一个任务
- 找到优先级最高的任务
- 将该任务的寄存器值恢复到CPU寄存器中
2.2 prvStartFirstTask()
- 该函数用于初始化启动第一个任务前的环境,主要是重新设置MSP指针,并使能全局中断。
- MSP指针:主堆栈指针,由OS内核、异常服务例程以及所有需要特权访问的应用程序代码来使用。
- PSP指针:进程堆栈指针,用于常规的应用程序代码(不处于异常服用例程中时)。
特别注意:在FreeRTOS中,中断使用MSP(主堆栈),中断以外使用PSP(进程堆栈)。
- 为什么是OxEOOOEDO8?
因为需从OxEO0OED08获取向量表的偏移。
为啥要获得向量表呢?
因为向量表的第一个是MSP指针!取MSP的初始值的思路是先根据向量表的位置寄存器VTOR(OxEO00ED08)来获取向量表存储的地址;
在根据向量表存储的地址,来访问第一个元素,也就是初始的MSP。
CM3允许向量表重定位——从其它地址处开始定位各异常向量这个就是向量表偏移量寄存器,向量表的起始地址保存的就是主栈指针MSP的初始值
2.3 vPortSVCHandler()
SVC中断只在启动第一次任务时会调用一次,以后均不调用。
- 通过pxCurrentTCB 获取优先级最高的就绪态任务的任务栈地址,优先级最高的就绪态任务是系统将要运行的任务。
- 通过任务的栈顶指针,将任务栈中的内容出栈到CPU寄存器中,任务栈中的内容在调用任务创建函数的时候,已初始化,然后设置PSP指针。
- 通过往BASEPRI寄存器中写0,允许中断。
- 执行 bx R14,告诉处理器ISR完成,需要返回,此刻处理器便会使用PSP做为堆栈指针,进行出栈操作,将xPSR、PC、LR、R12、R3~RO出栈,初始化的时候,PC被我们赋值成为了执行任务的函数的入口,所以呢,就正常跳入到了优先级最高的就绪状态的第一个任务的入口函数了
3. 任务切换
任务切换的本质就是CPU寄存器的切换。 例如:
当由任务A切换到任务B时,分为两步,整个过程被称为上下文切换
- 暂停任务A的执行,并将此时任务A的寄存器保存到任务堆栈,称为保存现场;
- 将任务B的各个寄存器值(被存于任务堆栈中 )恢复到CPU寄存器中,称为恢复现场
3.1 vTaskSwitchContext()
通过该函数查找最优先级任务,
利用函数taskSELECT_HIGHEST_PRIORITY_TASK(),前导置零指令找到最高优先级;
利用函数listGET_OWNER_OF_NEXT_ENTRY(),获取最高优先级的任务句柄。
3.2 任务切换流程
触发PendSV中断(任务切换的过程在PendSV中断服务函数里边完成),执行任务切换,主要途径有:
- 滴答定时器中断触发
- 调用FreeRTOS的API函数触发,如
portYIELD()
- 当前的psp是正在运行的任务的栈指针,读取当前psp进程指针,存入rO
- 压栈(保存现场)
- 获取当前最高优先级任务的任务控制块
- 出栈(恢复现场)
- 更新切换后的任务的的栈指针给PSP
- bx r14执行新任务函数