1. Boot Loader的主要任务及框架结构
bootloader的实现依赖于CPU架构,主要分为两个阶段stage1和stage2两部分;
stage1: 依赖于硬件设备的代码,如:硬件初始化等工作一般放在stage1中以汇编代码的形式实现,达到短小精悍的目的。
stage2: 主要以C语言实现,这样可以实现更复杂的功能,移植性和可读性也更好。
Bootloader的stage1包含以下步骤(按执行的先后顺序排列):
- 硬件设备初始化
- 为加载bootloader的stage2准备RAM空间
- 拷贝bootloader的stage2到RAM空间
- 设置好堆栈
- 跳转到stage2的C入口点
Bootloader的stage2包含以下步骤(按执行的先后顺序排列)
- 初始化本阶段要用到的硬件设备
- 检测系统内存映射(Memory Map)
- 将内核映像和根文件系统从flash读到RAM空间中
- 为内核设置启动参数
- 调用内核
1.1 Bootloader的Stage1
1.1.1 基本的硬件初始化
这是bootloader一开始就执行的操作,其目的:为stage2的执行以及随后的kernel的执行准备好一些基本的硬件环境。通常包含如下步骤(按执行的先后顺序):
1. 屏蔽所有的中断。为中断提供服务通常是OS设备驱动程序的责任,因此在bootloader的执行全过程中可以不必响应任何中断。中断屏蔽可以通过写CPU的中断屏蔽寄存器或状态寄存器来完成(ARM中写cpsr寄存器)
2. 设置CPU的速度及时钟频率。
3. RAM初始化(SDRAM)。包括正确地设置系统的内存控制器的功能寄存器以及各内存库控制寄存器等。
4. 初始化 LED。典型地,通过 GPIO 来驱动 LED,其目的是表明系统的状态是 OK 还是 Error。如果板子上没有 LED,那么也可以通过初始化 UART 向串口打印 Boot Loader 的 Logo 字符信息来完成这一点。
**5. 关闭 CPU 内部指令/数据 cache。
1.1.2 为加载 stage2 准备 RAM 空间
为了获得更快的执行速度,通常把 stage2 加载到 RAM 空间中来执行,因此必须为加载 Boot Loader 的 stage2 准备好一段可用的 RAM 空间范围。
由于 stage2 通常是 C 语言执行代码,因此在考虑空间大小时,除了 stage2 可执行映象的大小外,还必须把堆栈空间也考虑进来。此外,空间大小最好是 memory page 大小(通常是 4KB)的倍数。一般而言,1M 的 RAM 空间已经足够了。具体的地址范围可以任意安排,比如 blob 就将它的 stage2 可执行映像安排到从系统 RAM 起始地址 0xc0200000 开始的 1M 空间内执行。但是,将 stage2 安排到整个 RAM 空间的最顶 1MB(也即(RamEnd-1MB) - RamEnd)是一种值得推荐的方法。
为了后面的叙述方便,这里把所安排的 RAM 空间范围的大小记为:stage2_size(字节),把起始地址和终止地址分别记为:stage2_start 和 stage2_end(这两个地址均以 4 字节边界对齐)。因此:
stage2_end=stage2_start+stage2_size
另外,还必须确保所安排的地址范围的的确确是可读写的 RAM 空间,因此,必须对你所安排的地址范围进行测试。具体的测试方法可以采用类似于 blob 的方法,也即:以 memory page 为被测试单位,测试每个 memory page 开始的两个字是否是可读写的。为了后面叙述的方便,我们记这个检测算法为:test_mempage,其具体步骤如下:
1. 先保存 memory page 一开始两个字的内容。
2. 向这两个字中写入任意的数字。比如:向第一个字写入 0x55,第 2 个字写入 0xaa。
3. 然后,立即将这两个字的内容读回。显然,我们读到的内容应该分别是 0x55 和 0xaa。如果不是,则说明这个 memory page 所占据的地址范围不是一段有效的 RAM 空间。
4. 再向这两个字中写入任意的数字。比如:向第一个字写入 0xaa,第 2 个字中写入 0x55。
5. 然后,立即将这两个字的内容立即读回。显然,我们读到的内容应该分别是 0xaa 和 0x55。如果不是,则说明这个 memory page 所占据的地址范围不是一段有效的 RAM 空间。
6. 恢复这两个字的原始内容。测试完毕。
为了得到一段干净的 RAM 空间范围,我们也可以将所安排的 RAM 空间范围进行清零操作。
1.1.3 拷贝stage2到 RAM 中
拷贝时要确定两点:
- stage2 的可执行映象在固态存储设备的存放起始地址和终止地址;
- RAM 空间的起始地址。
1.1.4 设置堆栈指针sp
堆栈指针的设置是为了执行 C 语言代码作好准备。通常我们可以把 sp 的值设置为(stage2_end-4),也即在 3.1.2 节所安排的那个 1MB 的 RAM 空间的最顶端(堆栈向下生长)。
此外,在设置堆栈指针 sp 之前,也可以关闭 led 灯,以提示用户我们准备跳转到 stage2。
经过上述这些执行步骤后,系统的物理内存布局应该如下图2所示。
图2 bootloader 的 stage2 可执行映象刚被拷贝到 RAM 空间时的系统内存布局
1.1.4 跳转到 stage2 的 C 入口点
在上述一切都就绪后,就可以跳转到 Boot Loader 的 stage2 去执行了。比如,在 ARM 系统中,这可以通过修改 PC 寄存器为合适的地址来实现。