Bootloader
“Bootloader”俗称“引导加载程序”。在我们狭义上说的“Bootloader”是特指嵌入式设备中的引导程序,但是从广义的角度上来说,PC机中也存在“Bootloader”,比如Windows中的引导工具为“NTLOADER”,“Bootmgr”。“NTLOADER”是Windows 98和Windows XP时代的引导程序,而Windows Vista,Windows 7和Windows 10操作系统的引导程序则为“Bootmgr”。Linux操作系统也有自己特定使用的引导程序,这就是开源组织GNU推出的Grub(GRand Unified Bootloader简称“GRUB”),它可以引导非常多的操作系统,功能非常强大。然而对于普通程序猿来说,PC端的引导程序根本不需要我们涉及,它们往往会随着操作系统被一并自动安装进我们的PC,只有当安装了双系统的时候,开机会出现启动管理器让你选择进入哪个系统的时候,引导程序才会显出它的“真身”。
图1 双系统的开机引导
在嵌入式设备中,Bootloader程序的裁剪是一门必修课。嵌入式设备中,只要使用了Linux这种操作系统必须要使用引导程序,这些嵌入式Linux操作系统的引导程序用的是一个名叫“U-Boot”的开源软件。而早在嵌入式Linux刚刚兴起的时候,各家的硬件平台标准化远远没有现在这么高,因此对于不同的硬件平台,需要修改U-Boot的代码来适配。
图2 U-Boot的官方网站
接下来问题来了,为什么这种操作系统一定要用引导程序来引导呢?首先来看下U-Boot这种Bootloader的主要功能,这里主要是介绍早期的U-Boot功能,这些功能是整个嵌入式Linux操作系统成功运行的基础。U-Boot的运行分为两个阶段:
第一阶段是Low level init,即低级别的初始化,这一阶段主要完成以下一些工作:
设置异常向量;
设置CPU速度、时钟频率以及中断控制寄存器;
初始化内存控制器;
拷贝U-Boot第二阶段的功能代码到RAM空间;
设置堆栈,初始化数据段并跳转至第二阶段引导。
第二阶段主要完成的功能有:
初始化Flash设备;
初始化系统内存;
初始化NAND,显示,网络等其他设备;
进入Bootloader功能区(可选,功能可以为硬件测试,下载操作系统内核,下载文件系统等);
将Kernel和根文件系统从Flash映射到RAM中;
设定内核启动参数和启动内核。
u-boot.lds
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
*(.__image_copy_start)
CPUDIR/start.o (.text*)
*(.text*)
}
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
. = ALIGN(4);
.data : {
*(.data*)
arch/arm/cpu/armv7/start.s中节选代码
.globl _start
_start: b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
#ifdef CONFIG_SPL_BUILD
_undefined_instruction: .word _undefined_instruction
_software_interrupt: .word _software_interrupt
_prefetch_abort: .word _prefetch_abort
_data_abort: .word _data_abort
_not_used: .word _not_used
_irq: .word _irq
_fiq: .word _fiq
_pad: .word 0x12345678 /* now 16*4=64 */
#else
.globl _undefined_instruction
_undefined_instruction: .word undefined_instruction
.globl _software_interrupt
_software_interrupt: .word software_interrupt
.globl _prefetch_abort
_prefetch_abort: .word prefetch_abort
.globl _data_abort
_data_abort: .word data_abort
.globl _not_used
_not_used: .word not_used
.globl _irq
_irq: .word irq
.globl _fiq
_fiq: .word fiq
_pad: .word 0x12345678 /* now 16*4=64 */
#endif /* CONFIG_SPL_BUILD */
.global _end_vect
_end_vect:
.balignl 16,0xdeadbeef
在GCC的Make系统中,一个程序的入口是由链接文件决定的,也就是我们这里的start.s文件。在ARM系统上电的时候,会根据硬件启动方式的选择,进入不同的启动区域,但是这段代码无论无何都会被执行,只不过是从哪里映射到起始地址而已。
等到整个U-Boot运行起来之后,接下来用户就可以选择是直接引导程序,还是进行内核更新或者文件系统更新,这些软件的更新也是U-Boot提供的一些功能。
图3 U-Boot通过串口打印出的CLI界面
单片机的Bootloader
既然Linux操作系统进行启动时,必须要用Bootloader初始化硬件以及引导操作系统,那么在没有Linux操作系统的单片机中为什么还要用Bootloader?
单片机中使用Bootloader的主要作用有两个:
改变软件烧写方式;
方便软件更新(在线更新或者OTA)。
软件更新这一点很好理解,因为目前很多设备都可以联网,可以设置一个服务器自动推送一个新版本的固件来给单片机升级,既省去了客户服务现场更新软件的成本,又可以发挥软件行业敏捷开发的优点。
图4 OTA升级
而改变软件的烧写方式这一点可能有点困惑了,现在的MCU明明可以用J-Link,用串口等等方式来烧写,为什么还要去改变软件的烧写方式呢?
这个对于一些特殊的行业,比如工程车辆,叉车,挖掘机等,它们既不支持OTA在线升级,对外的标准口也只有一个CAN总线接口,这个接口既要用作车辆信息诊断的接口,又要对整个车辆网络中的所有控制器进行软件升级。这就必然要给CAN总线加入软件烧写的功能,而单片机的出场IAP基本只支持串口,并不支持CAN总线等特殊通讯口。因此一个好的Bootloader程序可以完美解决这个问题。
图5 林德叉车的CAN总线烧录器(串口转CAN)
下一篇文章,我们将来详细地介绍下如何设计一个满足OTA的STM32 Bootloader。