Bootloader介绍
体系结构不同的 CPU 都有不同的 Bootloader,有些 Bootloader 支持不同类型体系结构的处理器,如 U-boot。通常,Bootloader 不但以来于 CPU 的体系结构,而且依赖于特定的嵌入式板及设备的配置,即对于两块不同的嵌入式板而言,即使它们是基于同一种 CPU 而构建的,要运行在一块开发板上的 Bootloader 程序能够运行在另一块开发板上,通常需要修改 Bootloader 的源程序以适应不同的开发板.
Bootloader 与嵌入式 Linux 系统的关系
从软件的角度可以将嵌入式 Linux 系统分成 4 个层次,4 个层次由低层到高层分别如下所示。
- 引导加载程序:包括固化在固件中(firmware)中的 boot 代码(可选)和 Bootloader两大部分。
- 内核:给具体类型开发板定制的内核及控制内核引导系统的参数。
- 文件系统:包括根文件系统和建立与 FLASH 内存设备上的文件系统。
- 用户应用程序:用户的应用程序,包括 GUI、Web 服务器、数据库、网络协议栈
Bootloader 基本概念
Bootloader 是在操作系统内核运行前执行的一段小程序, 类似在启动 Windows 系统前运行的 BIOS 系统。通过这段小程序,完成了对必要硬件的初始化,创建内核需要的信息并将这些信息通过相关机制传递给内核, 从而将系统的软硬件环境带到一个合适的状态, 最终调用操作系统内核,起到引导和加载内核的功能.
Bootloader 的安装媒介
系统每次加电或复位后,CPU 都会固定从预先设定的地址上取指令。基于 CPU 构建的嵌入式系统通常都有某种类型的固态存储设备(比如 ROM、EEPROM 或 FLASH 等)被映射到这个预先设定的地址上。
Bootloader 启动过程分类
Bootloader 启动过程分为单阶段和多阶段两种。相对单阶段 Bootloader 而言,多阶段的Bootloader 则功能更加复杂,可移植性更加优越。从固态存储设备上启动 Bootloader 一般可分为两个阶段的启动过程,即启动过程可以分为 stage1 和 stage2 两部分.
Bootloader 的操作模式
- 启动加载模式:这种模式也称为“自主”模式,即 Bootloader 从目标机上的某个固态存储设备上将操作系统加载到 RAM 中运行,整个过程并没有用户的介入。启动加载
- 下载模式:这种模式下,目标机上的 Bootloader 将通过串口连接、网络连接或 USB连接等通信手段从主机 (Host) 下载文件, 如下载内核映射文件和文件系统映像文件等。从主机下载的文件通常首先被Bootloader保存到目标板的ROM中, 然后再被Bootloader写到目标板上的 FLASH 类固态存储设备中。Bootloader 的这种模式通常在第一次安装内核与根文件系统时被使用,或者在系统更新时使用。Bootloader 工作在下载模式时,通常都会提供一个命令行接口给它的终端用户,以供用户通过命令行控制 Bootloader的工作.
Bootloader 启动过程
Bootloader 的启动过程分为 stage1 和 stage2 两个阶段,通常 stage1 是用汇编语言完成,而 stage2 则用 C 语言来实现,以便于在 stage2 阶段实现更加复杂的功能和取得更好的代码可读性及可移植性。下面介绍两个阶段分别完成不同工作。
stage1 完成的工作
(1)基本的硬件初始化包括以下工作:
◆ 屏蔽所有中断。为中断提供服务通常是操作系统设备驱动程式的责任,因此在
Bootloader 的启动全过程中可以不必响应任何中断。屏蔽中断可以通过写 CPU 的中断屏蔽
寄存器或状态寄存器(比如 ARM 的 CPSR 寄存器)来完成。
◆ 设置 CPU 速度和时钟频率。
◆ 初始化 RAM。 包括正确设置系统内存控制器的功能寄存器, 以及各个内存库控制寄存器
等。
◆ 初始化 LED。典型地,通过 GPIO 来驱动 LED,其目的是检查当前系统的状态是 OK 还
是 ERROR。如果板子上没有 LED,那么也可以通过初始化 UART 向串口打印 Bootloader
的 Logo 字符信息来完成这一点。
◆ 关闭 CPU 内部指令和数据 cache 灯。
Bootloader 的启动全过程中可以不必响应任何中断。屏蔽中断可以通过写 CPU 的中断屏蔽
寄存器或状态寄存器(比如 ARM 的 CPSR 寄存器)来完成。
◆ 设置 CPU 速度和时钟频率。
◆ 初始化 RAM。 包括正确设置系统内存控制器的功能寄存器, 以及各个内存库控制寄存器
等。
◆ 初始化 LED。典型地,通过 GPIO 来驱动 LED,其目的是检查当前系统的状态是 OK 还
是 ERROR。如果板子上没有 LED,那么也可以通过初始化 UART 向串口打印 Bootloader
的 Logo 字符信息来完成这一点。
◆ 关闭 CPU 内部指令和数据 cache 灯。
(2)准备 RAM 空间加载 stage2。为了获得更快的执行速度,通常把 stage2 加载到 RAM 空
间来执行,所以必须准备好一段可用的 RAM 空间范围用来加载 Bootloader 的 stage2。
(3)复制 stage2 到 RAM 中。执行复制时要确定两类地址:第一,stage2 的可执行映像在
固态存储的存放起始地址和终止地址;第二,RAM 空间的起始地址。
(4)设置堆栈指针 SP。设置堆栈指针是为执行 C 语言代码做准备,在设置堆栈指针 SP 之
前,也可以关闭 LED 灯,以提示用户我们准备跳转到 stage2。
(5)跳转到 stage2 的 C 入口点。在 ARM 处理器中,实现跳转的方法是通过修改 PC 寄存
器为合适的地址。
间来执行,所以必须准备好一段可用的 RAM 空间范围用来加载 Bootloader 的 stage2。
(3)复制 stage2 到 RAM 中。执行复制时要确定两类地址:第一,stage2 的可执行映像在
固态存储的存放起始地址和终止地址;第二,RAM 空间的起始地址。
(4)设置堆栈指针 SP。设置堆栈指针是为执行 C 语言代码做准备,在设置堆栈指针 SP 之
前,也可以关闭 LED 灯,以提示用户我们准备跳转到 stage2。
(5)跳转到 stage2 的 C 入口点。在 ARM 处理器中,实现跳转的方法是通过修改 PC 寄存
器为合适的地址。
.stage2 完成的工作
(1)使用汇编语言跳转到 main()入口函数。用汇编语言写一段 trampoline 小程序,并
将这段 trampoline 小程序作为 stage2 可执行映像的执行入口点。然后再 trampoline 汇编小程
序中用 CPU 跳转指令跳入 main()函数中去执行;而当 main()函数返回时,CPU 执行路径再
次回到 trampoline 程序。这种方法的思路是:用这段 trampoline 小程序作为 main()函数的外
部包裹(external wrapper) 。
(2)初始化本阶段要使用到的硬件设备。初始化至少一个串口,以便和终端用户进行
I/O 输出信息;初始化计时器等。
(3)检测系统的内存映射。所谓内存映射就是指在整个 4GB 物理内存地址空间中, 有
哪些地址范围被分配用来寻址系统的 ARM 单元。
(4)加载内核映像文件和根文件系统映像文件。包括规划内存分配布局和从 FLASH
上复制数据。 规划内存分配布局包括, 内核映像所占用的内存范围和根文件系统所占用的内
存范围。
(5)设置内核的启动参数。在将内核映像和根文件系统映像复制到 ARM 空间中后,
就可以准备启动 Linux 内核了。在调用内核之前,应该做进一步准备工作,即设置 Linux 内
核的启动参数。
将这段 trampoline 小程序作为 stage2 可执行映像的执行入口点。然后再 trampoline 汇编小程
序中用 CPU 跳转指令跳入 main()函数中去执行;而当 main()函数返回时,CPU 执行路径再
次回到 trampoline 程序。这种方法的思路是:用这段 trampoline 小程序作为 main()函数的外
部包裹(external wrapper) 。
(2)初始化本阶段要使用到的硬件设备。初始化至少一个串口,以便和终端用户进行
I/O 输出信息;初始化计时器等。
(3)检测系统的内存映射。所谓内存映射就是指在整个 4GB 物理内存地址空间中, 有
哪些地址范围被分配用来寻址系统的 ARM 单元。
(4)加载内核映像文件和根文件系统映像文件。包括规划内存分配布局和从 FLASH
上复制数据。 规划内存分配布局包括, 内核映像所占用的内存范围和根文件系统所占用的内
存范围。
(5)设置内核的启动参数。在将内核映像和根文件系统映像复制到 ARM 空间中后,
就可以准备启动 Linux 内核了。在调用内核之前,应该做进一步准备工作,即设置 Linux 内
核的启动参数。