bootloader解析
bootloader的引出
- 不知道你有没有想过这样一个问题,当你按下电源开关的那一瞬间,第一行代码是如何在芯片上运行起来的呢?我们都知道嵌入式软件代码,是需要通过一定的方式,烧录在硬件芯片中,才能运行。我们所熟知的烧录方式,除了物理刻蚀以外,无论是通讯端口的传输,还是调试端口的烧录,多少都是需要驱动程序作支持,所以说是程序烧录程序,软件启动了软件。
- 这听起来就像是我们提着自己的鞋带,把自己给拎了起来,靴子Boot,鞋带Strap,接下来就是Loader,这就是BootStrapLoader最初的命名来源,我们也常简称它为BootLoader,所以也有中文翻译为自举。
程序是如何烧录、运行的
- 那么最初的软件是怎么烧录进去,运行起来的呢。BootLoader是芯片中最初运行的代码吗?
- 其实几乎每一块刚出厂的控制芯片,都在其内部非易失存储器ROM中烧录了属于它最基础的软件,cpu搬运并运行第一条控制代码的默认位置,就在ROM的地址空间,所以一切的起始其实是硬件。
X86架构
- 以x86架构的鼻祖8086为例,按下开关的一瞬间,芯片的reset引脚,接收到了电平跳变,在一连串电路的作用下,代码段寄存器和指令指针寄存器,分别恢复成0xFFFF和0x0000,它们组合而成的20位长度地址,正好对应于ROM存放第一条代码的位置,之后取出这里的指令,再次跳转到别处去。
ARM架构
- 而ARM架构的芯片也是类似的过程,对于32位的ARM芯片,上电后,PC指针寄存器直接复位至零地址,随后从中断向量表表头的reset向量处获取下一步跳转的地址。
- 这时候的代码,已经是以二进制的形式存储,处理器直接把它搬运到自身的缓存中运行。
- 有了这第一步运行起来的代码,就能够跳转到存放有更多更复杂代码的地址,执行一些硬件自检,初始化,提供基础的输入输出支持。
- 之后还能把更多的程序以及操作系统,从外部的存储空间加载到内部,代码就这样以一种接力方式流转了起来。
BootRom
- 所以我们把出厂就写在ROM(Read Only Memery)里,负责启动后续用户软件的软件称为BootRom或者是RomCode,虽然现在不会用严格意义上的只读存储器,来存放这部分程序,但至少ROM是一块掉电不易失的存储设备,现在都是使用EEPROM或者是NorFlash,且往往我们也没有权限去修改它,不然改错变砖,芯片就真的没药可治了。
- 不过BootRom对我们而言也不是完全的黑盒,大部分芯片都会有外部启动配置引脚,通常是以拨码开关的形式,复位后的一段时间内,BootRom会把引脚采集到的高低电平组合存储起来,作为后续动作和启动设备的选择依据。
PC
- 而对于PC而言,BootRom对应的其实就是我们常说的Bios,它同样留有启动配置途径,而且提供了交互界面,用于配置部分功能,和选择后续的引导设备。
引导代码
- 那么除了芯片自带的BootRom,我们可能还需要给自己实际的应用程序,写一个二次引导代码,或者更多层的引导代码,用作操作系统,文件系统加载,以及后续程序的刷新,也就是常说的在应用编程-IAP。当我们在说bootloader时,我们指的其实也大多数是这样的二次引导代码。
- 那么有了解过的朋友就可能会有疑问,上面的这些事好像大部分芯片的BootRom也能做,那么还需要bootloader做什么,其实原因就在于BootRom实现的功能和配置方法不够灵活,且不一定有你想要实现的功能, 而用作二次引导的bootloader,是开发人员可以完全控制的引导代码,你可能希望在上电启动后,跳转OS之前,或者是在检查代码更新请求的阶段,已经有一部分基础软件是可用的,那么就需要自己编写bootloader来实现。
实例解释
- 举个例子,以汽车上的智驾控制器而言,为它设计的bootloader,就需要提供符合UDS统一诊断服务标准的CAN诊断服务和刷新服务,这就要求我们必须要在另外的bootloader中把这些工作给做掉,而不能指望芯片自身的BootRom。
Bootloader的设计
- 在设计bootloader时,MCU的引导步骤,便开始和嵌入式Linux或PC有所不同。这一定程度上,也与不同芯片架构所采用的存储方案有关。
MCU
- 先来说MCU,与SOC相比,MCU的主要特称是单核和或多核同构的微处理器,主频一般不超过1G赫兹,一般没有MMU内存管理单元,通常最多只能运行RTOS实时操作系统。
- 常见的MCU,多基于ARM的cortex M和cortex R系列内核,英飞凌自有的tricore内核,TI自有的C28x系列内核等等。
- MCU下程序运行的主要介质基本都是NOR Flash,因为它和RAM一样有分离的地址线和数据线,并且可以以字节长度精确寻址,所以Nor Flash中的程序是不需要拷贝到RAM中运行的。
- 以英飞凌家的TC27x系列的MCU为例,它上电后的默认取址位置,是0x8FFF 8000,这就是它的BootRom在NorFlash中的地址,并且这块BootRom分为SSW(Startup Software),BSL(Bootstrap Loader)也就是厂商提供的系统bootloader,TF(Test Firmware)三个部分。
- SSW是每次上电必须要运行的,它会根据写在program flash(PF0)地址的前32byte中的配置字,来决定SSW执行完的跳转地址。
- 我们可以选择一个合适的跳转地址,比如0x80000020,放上自己写的bootloader。
- 也可以选择不跳转,运行厂家提供的系统bootloader,来实现对外部代码的接收。
-
不管你用不用它,但还是那句老话,我们没有权限对这片flash地址进行修改。
-
不同mcu芯片的启动形式呢,又都不尽相同,比如采用Coretex-M内核的stm32系列mcu,如前面所说,启动时会固定跳转到0地址处。
- 它可以选择把System Boot也就是ARM中的BootRom的地址,0x1FFF 0000映射到零地址处。
- 也可以选择把存放用户代码的internal flash或ram的地址映射到这里,这些选择都是通过两个外部引脚的电平配比来实现的,而你自己写的bootloader就应该放在internal flash的起始处,并且把它映射到零地址处。
- MCU下的bootloader主要需要完成的事情有:
- 1.关闭看门狗,初始化中断和trap向量表,进行时钟和外设初始化,让芯片正常运行起来。
- 2.提供CAN、UART、ETH等用于通讯功能的驱动,能够接受外部数据传输请求。
- 3.提供flash的读写与擦除驱动,设计服务来对通讯端口接收到的更新代码进行校验、存储,以及跳转操作系统或后续应用程序代码。
- 4.如有必要,还会开发一些基础诊断服务,串口交互程序等等。
嵌入式linux
- 那么运行嵌入式Linux的SOC和PC的这一过程有何不同呢,还是先看存储方案,运行嵌入式Linux的SOC,一般将它的操作系统、文件系统和它的应用程序放在nand flash中。
- 处理器运行代码前,需要先将代码从NAND搬运到SRAM中,相比MCU多了一道步骤。
PC
- PC的存储方案与之类似,NAND一般换做机械硬盘,或者同样属于NAND的固态硬盘一类,所以对于SOC的BootROM和PC的Bios而言,它们结束运行前的最终任务,是将某些代码从nand flash,搬运到内部SRAM中。
- 转移的重要内容就是bootloader,而一般SOC的bootloader又分为SPL(Secondary Progarm Loader)和UBOOT两个阶段,SPL的Secondary 就是相对BootROM而言,它就像是接力赛中的第二棒选手,那么首先被搬运到内部SRAM中的SPL,会初始化空间更大的外部的DRAM,在负责把Uboot搬运至外部RAM中去运行,这就完成了它第二棒的交接。
- 而uboot作为第三棒选手,它开始运行它的初始化程序,之后再根据系统环境变量将OS内核搬运到外部RAM中去运行,完成它这一棒的交接,同样的OS再去完成跟文件系统的加载。
- 总结来看,虽然对不同的芯片架构,在不同的芯片手册中,可能会对这几个阶段的引导代码,有着不同的称法和应用方式,但芯片的启动基本都逃不过这三层的引导结构:
- 第一层就是芯片出厂自带的BootRom,用于硬件自检和初始化,加载Bootloader、提供外部配置引脚。
- 第二层就是我们自己写的Bootloader层,可以通过它访问外部RAM、NANDFlash等更多的存储设备,初始化时钟、通讯等,接收、存储以及跳转代码。
- 第三层就是我们常见的app层,它包含操作系统、文件系统、用户应用程序等,可以方便被更新的代码,