FreeRTOS启动流程
前置知识:
LR寄存器
也就是我们常说的链接寄存器,专门用来用于函数、中断返回使用。
函数调用:
LR是直接作为返回值来使用的。
- 在函数调用开始时,LR寄存器的值就是PC的值,或者是下一次要执行的PC的值。
- 函数调用开始后,进入函数程序后,我们需要要将LR压栈,函数执行完成后,LR出栈到PC,此时PC就可以直接拿LR的值去执行,实现函数返回。
中断发生:
- CPU仍会自动更新LR的值,只不过这里不是将当前的PC值更新到LR了,而是根据当前的模式、MSP/PSP使用情况来查表生成一个EXC_RETURN值,将EXC_RETURN值更新到LR里。
- 中断执行完毕,对LR进行出栈操作,出栈到PC,CPU发现PC的值是EXC_RETURN的范围,就知道是中断返回了,此时PC值不会被执行,CPU会自动将进入中断时硬件压栈的栈帧进行出现,里面就包括了返回地址,将该地址再次赋值给PC,实现了恢复现场的操作。
为什么要使用SVC、PendSV来启动OS?
OS的运行需要使用PSP+用户态,那么就需要设置CONTROL寄存器。
配置CONTROL需要在内核态,那么就需要通过中断机制+修改EXC_RETURN来实现间接修改CONTROL。
常用的中断一般是SVC或者PendSV。
启动流程
- 在Cortex-M3处理器,上电后默认使用MSP,为内核态,此时MSP已经更新。
- 一旦开启OS,程序就永远不会返回这里,所以将MSP重置,指向上电初始位置,回收堆栈空间。
- 执行完中断处理函数,中断退出,此时CPU会从当前任务的堆栈PSP开始,使用PSP自动出栈,恢复另一半现场,其中PC中已经存放了第一个任务的运行地址,CPU就开始执行第一个任务,完成了第一个任务运行,即启动。
uboot启动流程
以stm32mp157为例:
linux系统主要分为五个流程:
1 ROM代码,处理上电以后首先执行的程序。
主要工作:读取STM32MP1的BOOT引脚电平,由电平判断启动设备,由选定的设备中读取FSBL代码,放进对应的RAM空间。
2 RAM代码(FSBL):完成整个时钟的初始化,初始化DDR,将SSBL加载到DDR中并运行SSBL。
3 SSBL:从外部存储器或者从网络中加载linux系统,启动linux内核。这里就涉及到uboot的两种模式,启动加载和下载模式。
4 Linux内核:内核初始化,挂载根文件系统,启动用户空间的init程序。
5 linux用户空间:由init程序切换到用户空间。
其中,2和3分别属于bootloader的两个阶段。
至于为啥要分两个阶段,那是因为前半段基本都是由汇编组成的,后半段因为设置了堆栈,所以转入C函数进行处理。
下面,就来具体分析uboot的启动流程。
- 关看门狗
- 初始化时钟
- 初始化SDRAM(DDR)
- 从NAND Flash中把第二段程序读到SDRAM(DDR)中
- 把程序设置好堆栈,指向内存,调用C函数
- 读出内核,启动内核
第一段是使用汇编来实现,依赖CPU体系结构的初始化,并调用第二阶段。
主要分为:
- 硬件设备初始化
- 为加载第二段程序准备RAM空间(初始化内存空间)
- 复制第二段程序到RAM空间(重定位)
- 设置好堆栈
- 跳转到第二段程序的C入口
第二段使用C语言来实现,用于更复杂的功能。
主要分为:
- 初始化本阶段要使用的硬件设备(至少要初始化一个串口以便程序员与bootloader通信)
- 检测系统内存镜像
- 将内核镜像与根文件系统镜像从Flash上读到RAM空间中(也可以从网络上加载)
- 为内核设置启动参数
- 调用内核
这里提一嘴,通常在进行驱动开发的时候,是用网络加载的形式进行的,因为这样进行开发的时候,修改内核再进行加载比较方便,不用进行烧写,具体会用到uboot的网络命令和环境参数设置,以及bootargs,bootcmd等参数的设置。