简单地说,Bootloader就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。
通常,Bootloader是严重地依赖于硬件而实现的,特别是在嵌入式世界中。因此,在嵌入式世界里建立一个通用的Bootloader几乎是不可能的。尽管如此,仍然可以对Bootloader归纳出一些通用的概念,以指导用户特定的Bootloader设计与实现。
每种不同的CPU体系结构都有不同的Bootloader。有些Bootloader也支持多种体系结构的CPU,如U-Boot就同时支持ARM体系结构和MIPS体系结构。除了依赖于CPU的体系结构外,Bootloader实际上也依赖于具体的嵌入式板级设备的配置。也就是说,对于两块不同的嵌入式板而言,即使它们是基于同一种CPU而构建的,要想让运行在一块板子上的Bootloader程序也能运行在另一块板子上,通常也都需要修改Bootloader的源程序。
当进行嵌入式开发时,通常需要使用各种命令操作Bootloader,一般通过串口来连接PC和开发板。可以在串口上输入各种命令和观察运行结果等。这也只是对开发人员才有意义,用户使用产品时是不用接串口来控制Bootloader的。从这个观点来看,Bootloader可以分为以下两种操作模式。
●启动加载(BootLoading)模式。上电后,Bootloader从板子上的某个固态存储设备上将操作系统加载到RAM中运行,整个过程并没有用户介入。产品发布时,Bootloader工作在这种模式下。
●下载(DownLoading)模式。在这种模式下,开发人员可以使用各种命令,通过串口连接或网络连接等通信手段从主机下载文件(如内核镜像和文件系统镜像等),将它们直接放在内存中运行或固化到Flash类固态存储设备中。
由于Bootloader的实现依赖于CPU的体系结构,因此大多数Bootloader都分为stage1和stage2两大部分。依赖于CPU体系结构的代码,如设备初始化代码等,通常都放在stage1中,而且通常都用汇编语言来实现,以达到短小精悍的目的。而stage2则通常用C语言来实现,这样可以实现复杂的功能,而且代码会具有更好的可读性和可移植性。
Bootloader的stage1通常包括以下步骤(以执行的先后为顺序):
1)硬件设备初始化。
2)为加载Bootloader的stage2准备RAM空间。
3)复制Bootloader的stage2到RAM空间中。
4)设置好堆栈。
5)跳转到stage2的C入口点。
Bootloader的stage2通常包括以下步骤(以执行的先后为顺序):
1)初始化本阶段要使用到的硬件设备。
2)检测系统内存映射(MemoryMap)。
3)将Kernel映像和根文件系统映像从Flash上读到RAM空间中。
4)为内核设置启动参数。(www.xing528.com)
5)调用内核。
这是Bootloader一开始就执行的操作,其目的是为stage2的执行以及随后的Kernel的执行准备好一些基本的硬件环境。它通常包括以下步骤(以执行的先后为顺序):
1)屏蔽所有的中断。为中断提供服务通常是OS设备驱动程序的责任,因此在Boot Loader的执行全过程中不必响应任何中断。中断屏蔽可以通过写CPU的中断屏蔽寄存器或状态寄存器(如ARM的CPSR寄存器等)来完成。
2)设置CPU的速度和时钟频率。
3)RAM初始化。
RAM初始化包括正确地设置系统的内存控制器的功能寄存器以及各内存库控制寄存器等。
4)初始化LED。典型地,通过GPIO来驱动LED,其目的是表明系统的状态是OK还是Error。如果板子上没有LED,那么也可以通过初始化UART向串口打印Bootloader的Logo字符信息来完成。
5)关闭CPU内部指令/数据Cache。为了获得更快的执行速度,通常把stage2加载到RAM空间中执行,因此必须为加载Bootloader的stage2准备好一段可用的RAM空间范围。
为了加载stage2须准备RAM空间。由于stage2通常是C语言执行代码,因此在考虑空间大小时,除了stage2可执行映像的大小外,还必须把堆栈空间也考虑进来。此外,空间大小最好是memorypage大小(通常是4KB)的倍数。一般而言,1MB的RAM空间已经足够了。具体的地址范围可以任意安排,如blob就将它的stage2可执行映像安排到从系统RAM起始地址0xc0200000开始的1MB空间内执行。但是,将stage2安排到整个RAM空间的最顶1MB(即(RamEnd-1MB)-RamEnd)是一种值得推荐的方法。接下来复制stage2到RAM中,设置堆栈指针SP,跳转到stage2的C入口点。
stage2的代码通常用C语言来实现,以便于实现更复杂的功能和取得更好的代码可读性和可移植性。但是,与普通C语言应用程序不同的是,在编译和链接Bootloader这样的程序时,不能使用glibc库中的任何支持函数。其原因是显而易见的,这就给我们带来一个问题,即从哪里跳转进main()函数呢?直接把main()函数的起始地址作为整个stage2执行映像的入口点或许是最直接的想法。但是这样做有两个缺点:①无法通过main()函数传递函数参数;②无法处理main()函数返回的情况。一种更为巧妙的方法是利用trampoline(弹簧床)的概念,即用汇编语言写一段trampoline小程序,并将这段trampoline小程序作为stage2可执行映像的执行入口点。然后,在trampoline汇编小程序中用CPU跳转指令跳入main()函数中去执行;当main()函数返回时,CPU执行路径再次回到trampoline程序。简言之,这种方法的思想是:用这段trampoline小程序作为main()函数的外部包裹(ExternalWrapper)。包括初始化本阶段要使用到的硬件设备,检测系统的内存映射,加载内核映像和根文件系统映像,设置内核的启动参数,调用内核。
本实例采用了韩国Mizi公司的vivi作为Bootloader。vivi有启动加载模式和下载模式两种工作模式。启动加载模式可以在一段时间后自行启动Linux内核,这是vivi的默认模式。在下载模式下,vivi为用户提供了一个命令行接口。通过接口可以使用vivi提供的一些命令,如load(把二进制文件载入Flash或RAM)、Part(显示、增加、删除、复位、保存MTD分区)、Param(设置参数)、Boot(启动系统)和Flash(管理Flash)等。
将vivi解压缩到当前工作目录下,然后修改vivi/Makefile里的一些变量设置:
●LINUX_INCLUDE_DIR=/usr/local/arm/2.95.3/include
LINUX_INCLUDE_DIR为交叉编译器的头文件对应目录。
●CROSS_COMPILE=/usr/local/arm/2.95.3/bin/arm-linux-CROSS_COMPILE为arm-linux安装的相应目录。这里采用的是arm-linux-gcc-2.95.3的交叉编译器来编译vivi,用高版本编译器编译会出错。把交叉编译器解压缩到/usr/local/arm目录下,交叉编译器可以在网上下载。
●ARM_GCC_LIBS=/usr/local/arm/2.95.3/lib/gcc-lib/arm-linux/2.95.3进入vivi目录执行makedistclean。然后输入“makemenuconfig”开始选择配置。保存配置后再输入“make”正式开始编译。最后会在目录下面生成“vivi”,这就是后面要固化到Flash中的Bootloader。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。