目录
一、任务创建
二、静态任务和动态任务创建的区别
三、任务调度
1、vTaskStartScheduler()调度器:
2、内核相关硬件初始化函数分析:xPortStartScheduler()
3、启动第一个任务函数分析:prvStartFirstTask()
4、SVC中断服务函数
5、MSP、PSP??
6、线程模式(Thread Mode),中断异常模式(Handler Mode)??
7、内核态和用户态??
8、SVC和PendSV的区别??
9、r0-r12寄存器组??
一、任务创建
二、静态任务和动态任务创建的区别
三、任务调度
1、vTaskStartScheduler()调度器:
(1) 创建空闲任务;
(2) 创建定时器任务(如果开启);
(3) 关闭中断,在 SVC 中断服务函数 vPortSVCHandler)中会打开中断;
(4) 当宏configGENERATE_RUN-TIME_STATS为1的时候说明使能时间统计功能,此时需要用户实现宏portCONFIGURE_TIMER_FOR_RUN_TIME_STATS,此宏用来配置一个定时器/计数器;
(5) 调用函数xPortStartScheduler()来初始化跟调度器启动有关的硬件,比如滴答定时器、FPU单元和PendSV中断等等。
2、内核相关硬件初始化函数分析:xPortStartScheduler()
(1) 设置PendSV的中断优先级,为最低优先级;
(2) 设置滴答定时器的中断优先级,为最低优先级。
(3) 调用函数vPortSetupTimerInterrupt()来设置滴答定时器的定时周期,并且使能滴答定时器的中断;
(4) 初始化临界区嵌套计数器;
(5) 调用函数prvEnableVFP()使能FPU;
(6) 设置寄存器FPCCR的bit31和bit30都为1,这样S0-S15和FPSCR寄存器在异常入口和退出时的壮态自动保存和恢复。并且异常流程使用惰性压栈的特性以保证中断等待;
(7) 启动第一个任务。
3、启动第一个任务函数分析:prvStartFirstTask()
向量表的起始地址保存的就是主栈指针MSP 的初始值,这一行代码执行完以后寄存器 RO 就存储 MSP 的初始值。现在来看(1)、(2)、(3)这三步起始就是为了获取MSP的初始值。
(5)和(6)、使能中断,关于这两个指令的详细内容请参考《权威指南》的“第4章架构”的第4.2.3 小节。
(7)和(8)、数据同步和指令同步屏障,这两个指令的详细内容请参考《权威指南》的“第 5章 指令集”的 5.6.13 小节。
(9),调用SVC指令触发SVC中断, SVC也叫做请求管理调用, SVC和PendSV异常对于OS的设计来说非常重要。SVC 异常由 SVC 指令触发。关于 SVC 的详细内容请参考《权威指南》的“第 10 章 OS 支持特性”的 10.3 小节。在 FreeRTOS 中仅仅使用 SVC 异常来启动第一个任务,后面的程序中就再也用不到SVC了。
4、SVC中断服务函数
(1)、(2)和(3):目的就是获取要切换到的这个任务的任务栈顶指针,因为任务所对应的寄存器值,也就是现场都保存在任务的任务堆栈中,所以需要获取栈顶指针来恢复这些寄存器值。
(4):通过这一步我们就从任务堆栈中将 R4-R11,R14 这几个寄存器的值给恢复了,注意 R14 的值为OXFFFFFFFD,这个值就是我们在初始化任务堆栈的时候保存的EXC-RETURN的值。
(8):执行此行代码以后硬件自动恢复寄存器R0-R3、R12、LR、PC和xPSR的值,堆栈使用进程栈PSP,然后执行寄存器PC中保存的任务函数。至此, FreeRTOS的任务调度器正式开始运行。
5、MSP、PSP??
MSP和PSP 双堆栈机制是为了OS设计的,是为了让OS、中断与应用程序代码运行解耦,增加整体程序的健壮性。
那双堆栈指针的作用是什么?答案是为了隔离OS和应用程序,程序的运行少不了堆栈,因为我们CPU只有少量的通用寄存器,当我们使用的临时变量比较多得时候,就需要将这些临时变量存储到堆栈里,而堆栈的push和pop都是通过SP来实现的,所以通过MSP和PSP就能实现OS内核与应用程序的隔离,应用程序task用PSP,而OS用MSP,这样会非常安全。因为应用程序再怎么折腾也只是在自己的堆栈内折腾,不会影响内核OS。
在发生中断时,CPU会自动的保存现场,这部分工作是硬件自动完成的,而SP到底是指向PSP还是MSP,则是根据在发生中断前是使用MSP还是PSP,比如发生中断前,正在运行RTOS的task,即在使用PSP,那么CPU保存现场也是使用PSP,然后进入中断服务程序,此时会从PSP切换到MSP。
6、线程模式(Thread Mode),中断异常模式(Handler Mode)??
特权状态和非特权,即内核态和用户态的设计也是为了OS设计的,让整个程序分层,整体程序更加健壮。
7、内核态和用户态??
用户态--->内核态:唯一途径是通过中断、异常、陷入机制(访管指令)
内核态->用户态:设置程序状态字PSW
8、SVC和PendSV的区别??
SVC不能挂起,它将立即被执行;而PendSV可以暂时挂起异常,对于操作系统来说这很有用,它可以等待一个重要的任务执行完毕后再处理该异常。
总得来说就是SVC由用户调用SVC #<immed>时立即触发,而PendSV则是置相应的异常标志位,等待比PendSV优先级高的异常执行完后,再执行PendSV。
在FreeRTOS中,使用SVC异常来开启第一个任务的调度。然后将PendSV异常优先级设置为最低,保证中断的实时性,后续的上下文切换全由PendSV来实现。