Bootloader,称为引导加载程序,是嵌入式系统加电后运行的第一段代码,相当于PC机的BIOS。
•Bootloader通常固化在硬件上某个固态存储设备上,加电后自启动。
•通过Bootloader这段代码,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。
•Bootloader的主要任务是:正确地调用内核来加载操作系统。
•在基于ARM的嵌入式系统中,系统在上电或复位时通常都从地址0x0000,0000处开始执行,而在这个地址处安排的通常就是系统的Bootloader。
•Bootloader是严重地依赖于硬件而实现的。每种不同体系结构的处理器都有不同的Bootloader。不过Bootloader的发展也趋于支持多种体系结构,如现在比较成熟的vivi、RedBoot和U-Boot等。
1、vivi
vivi是韩国Mizi公司开发的Bootloader,适用于ARM9处理器。
2、RedBoot
RedBoot也称作红帽(Red Hat)嵌入式调试引导程序,是一种用于嵌入式系统的独立开放源代码引导/装载器。
3、U-Boot
U-Boot(Universal Bootloader)是一款目前功能较为强大的开源Bootloader程序,它支持多种处理器平台,包括ARM、MIPS等。
Bootloader操作模式
•大多数Bootloader都包含两种不同的操作模式:“启动加载”模式和“下载”模式,这种区别仅对于开发人员才有意义。
•从最终用户的角度看,Bootloader的作用就是用来加载操作系统,而并不存在所谓的启动加载模式与下载工作模式的区别。
1、启动加载(Bootloading)模式
l启动加载模式称为“自举”(Autonomous)模式。即Bootloader从目标机上的某个固态存储设备上将操作系统加载到 RAM 中运行,整个过程并没有用户的介入。
l启动加载模式是 Bootloader的正常工作模式,在嵌入式产品发布的时侯,Bootloader必须工作在这种模式下。
2、下载(Downloading)模式
l在这种模式下,目标机上的Bootloader将通过串口连接或网络连接等通信手段从主机上下载文件。
l比如:下载内核映像和根文件系统映像等。从主机下载的文件通常首先被Bootloader保存到目标机的RAM中,然后再被 Bootloader写到目标机上的FLASH 类固态存储设备中。
lBootloader的这种模式通常在第一次安装内核与根文件系统时被使用;此外,以后的系统更新也会使用到这种工作模式。
l工作于这种模式下的Bootloader通常都会向它的终端用户提供一个简单的命令行接口。 如在RedBoot下,将出现“RedBoot>”提示符。
•像RedBoot或U-Boot等功能强大的Bootloader通常都可同时支持这两种工作模式,而且允许用户在这两种工作模式之间进行切换。
•比如,RedBoot在启动时处于正常的启动加载模式,但是它会延时3秒等待终端用户按下任意键而将RedBoot切换到下载模式。如在等待时间内没有接收到用户按键,则继续启动 Linux 内核。
Bootloader启动流程
•Bootloader的启动通常可以分为stage1和stage2两个阶段。
•1、stage1阶段
•stage1主要包含依赖于CPU的体系结构,比如设备初始化代码等。通常都用汇编语言来实现。这个阶段的任务有:
•(1)、基本的硬件设备初始化。
•这是 Boot Loader 一开始就执行的操作,其目的是为 stage2 的执行以及随后kernel 的执行准备好一些基本的硬件环境。
它通常包括以下步骤:屏蔽所有的中断(为中断提供服务通常是操作系统设备驱动程序的责任,因此在Bootloader 的执行全过程中可以不必响应任何中断。)、设置 CPU 的速度和时钟频率、关闭处理器内部指令/数据cache(通常使用内部指令和cache可以提高系统性能,但由于cache的使用可能改变访问主存的数量、类型和时间,因此Bootloader的执行通常不需要。)等。
•(2)、为stage2准备包括RAM空间。
•为了获得更快的执行速度,通常把 stage2 加载到 RAM 空间中来执行,因此必须为加载 Boot Loader 的 stage2 准备好一段可用的 RAM 空间范围。
•由于stage2通常是C语言执行代码,因此在考虑空间大小时,除了stage2可执行映像的大小外,还必须把堆栈空间也考虑进来。此外,空间大小最好是 memory page 大小(通常是4KB)的倍数。一般而言,1M的RAM空间已经足够了。具体的地址范围可以任意安排,但是,将stage2安排到整个RAM空间的最顶1M是一种值得推荐的方法。此外,还必须确保所安排的地址范围确实是可读写的RAM空间。
•为了后面的叙述方便,这里把RAM 空间范围的大小记为:stage2_size(字节),把起始地址和终止地址分别记为:stage2_start 和 stage2_end(这两个地址均以 4 字节边界对齐)。因此,stage2_end=stage2_start+stage2_size。
•(3)、拷贝stage2到RAM空间。
•(4)、设置好堆栈。
•堆栈指针的设置是为了执行 C 语言代码作好准备。通常我们可以把 sp 的值设置为(stage2_end-4),也即前面所提到的1MB 的 RAM 空间的最顶端(堆栈向下生长)。
•(5)、跳转到stage2的C入口点。
•在上述一切都就绪后,就可以跳转到 Boot Loader 的 stage2 去执行了。
•2、stage2阶段
•stage2通常用C语言来实现,以便实现更复杂的功能,也使程序有更好的可读性和可移植性。这个阶段的主要任务有:
•(1)、初始化本阶段要使用到的硬件。
•这通常包括:初始化至少一个串口,以便和终端用户进行 I/O 输出信息等。
•(2)、检测系统内存映射(memory map)。
•所谓内存映射就是指在整个物理地址空间中有哪些地址范围被分配用来作为寻址系统的 RAM 单元。虽然 CPU 通常预留出一大段足够的地址空间给系统 RAM,但是在搭建具体的嵌入式系统时却不一定会实现 CPU 预留的全部 RAM 地址空间。也就是说,具体的嵌入式系统往往只把 CPU 预留的全部 RAM 地址空间中的一部分映射到 RAM 单元上,而让剩下的那部分预留 RAM 地址空间处于未使用状态。
• 基于上述这个事实,因此 Boot Loader 的 stage2 必须在它想干点什么 (比如,将存储在 flash 上的内核映像读到 RAM 空间中) 之前检测整个系统的内存映射情况,也即它必须知道 CPU 预留的全部 RAM 地址空间中的哪些被真正映射到 RAM 地址单元,哪些是处于 “unused” 状态的。
•(3)、将kernel和根文件系统映像从flash上读到RAM空间中。
•由于像 ARM 这样的嵌入式 CPU 通常都是在统一的内存地址空间中寻址 Flash 等固态存储设备的,因此从 Flash 上读取数据与从 RAM 单元中读取数据并没有什么不同。这一步骤包括两部分内容:规划kernel和根文件系统所占用的内存范围和将它们从flash上进行拷贝。
•(4)、为kernel设置启动参数。
•这是在调用内核之前应该做的准备工作。Linux 2.4.x 以后的内核都期望以标记列表(tagged list)的形式来传递启动参数。启动参数标记列表以标记 ATAG_CORE 开始,以标记 ATAG_NONE 结束。在嵌入式 Linux 系统中,通常需要由 Boot Loader 设置的常见启动参数有:ATAG_CORE、ATAG_MEM(内存映射)、ATAG_NONE等。
•(5)、调用内核。
•Bootloader调用Linux kernel的方法是直接跳转到内核的第一条指令处。在跳转时必须满足下列条件。
•(1)、CPU寄存器的设置:R0为0;R1为机器类型ID(机器类型参见 linux/arch/arm/tools/mach-types目录);R2为启动参数,标记列表在RAM中的起始基地址。
•(2)、CPU模式:必须禁止中断(IRQs和FIQs);CPU必须设置为SVC模式。
•(3)、Cache 和 MMU 的设置:MMU 必须关闭;指令 Cache 可以打开也可以关闭; 数据 Cache 必须关闭。