2012-02-02

bootloader概述(转)

引导加载程序是系 统加电后运行的第一段软件代码。回忆一下 PC 的体系结构我们可以知道,PC 机中的引导加载程序由 BIOS(其本质就是一段固件程序)和位于硬盘 MBR 中的 OS Boot Loader(比如,LILO GRUB 等)一起组成。BIOS 在完成硬件检测和资源分配后,将硬盘 MBR 中的 Boot Loader 读到系统的 RAM 中,然后将控制权交给 OS Boot LoaderBoot Loader 的主要运行任务就是将内核映象从硬盘上读到 RAM 中,然后跳转到内核的入口点去运行,也即开始启动操作系统。



而在嵌入式系统中, 通常并没有像 BIOS 那样的固件程序(注,有的嵌入式 CPU 也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由 Boot Loader 来完成。比如在一个基于 ARM9 core 的嵌入式系统中,系统在上电或复位时通常都从地址 0x00000000 处开始执行,而在这个地址处安排的通常就是系统的 Boot Loader 程序。

本文将从 Boot Loader 的概念、Boot Loader 的主要任务、Boot Loader 的框架结构以及 Boot Loader 的安装等四个方面来讨论嵌入式系统的 Boot Loader

Boot Loader 的概念
简单地说,Boot Loader 就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。

通 常,Boot Loader 是严重地依赖于硬件而实现的,特别是在嵌入式世界。因此,在嵌入式世界里建立一个通用的 Boot Loader 几乎是不可能的。尽管如此,我们仍然可以对 Boot Loader 归纳出一些通用的概念来,以指导用户特定的 Boot Loader 设计与实现。

1. Boot Loader 所支持的 CPU 和嵌入式板
每种不同的 CPU 体系结构都有不同的 Boot Loader。有些 Boot Loader 也支持多种体系结构的 CPU,比如 U-Boot 就同时支持 ARM 体系结构和MIPS 体系结构。除了依赖于 CPU 的体系结构外,Boot Loader 实际上也依赖于具体的嵌入式板级设备的配置。这也就是说,对于两块不同的嵌入式板而言,即使它们是基于同一种 CPU 而构建的,要想让运行在一块板子上的 Boot Loader 程序也能运行在另一块板子上,通常也都需要修改 Boot Loader 的源程序。

2. Boot Loader 的安装媒介(Installation Medium
系 统加电或复位后,所有的 CPU 通常都从某个由 CPU 制造商预先安排的地址上取指令。比如,基于 ARM7TDMI core CPU 在复位时通常都从地址 0x00000000 取它的第一条指令。而基于 CPU 构建的嵌入式系统通常都有某种类型的固态存储设备(比如:ROMEEPROM FLASH )被映射到这个预先安排的地址上。因此在系统加电后,CPU 将首先执行 Boot Loader 程序。

3. 用来控制 Boot Loader 的设备或机制
主机和目标机之间一般通过串口建立连接,Boot Loader 软件在执行时通常会通过串口来进行 I/O,比如:输出打印信息到串口,从串口读取用户控制字符等。

4. Boot Loader 的启动过程是单阶段(Single Stage)还是多阶段(Multi-Stage
通 常多阶段的 Boot Loader 能提供更为复杂的功能,以及更好的可移植性。从固态存储设备上启动的 Boot Loader 大多都是 2 阶段的启动过程,也即启动过程可以分为 stage 1 stage 2 两部分。而至于在 stage 1 stage 2 具体完成哪些任务将在下面讨论。

5. Boot Loader 的操作模式 (Operation Mode)
大多数 Boot Loader 都包含两种不同的操作模式:"启动加载"模式和"下载"模式,这种区别仅对于开发人员才有意义。但从最终用户的角度看,Boot Loader 的作用就是用来加载操作系统,而并不存在所谓的启动加载模式与下载工作模式的区别。

启动加载(Boot loading)模式:这 种模式也称为"自主"Autonomous)模式。也即 Boot Loader 从目标机上的某个固态存储设备上将操作系统加载到 RAM 中运行,整个过程并没有用户的介入。这种模式是 Boot Loader 的正常工作模式,因此在嵌入式产品发布的时侯,Boot Loader 显然必须工作在这种模式下。

下载(Downloading)模式:在这种模式下,目标机 上的 Boot Loader 将通过串口连接或网络连接等通信手段从主机(Host)下载文件,比如:下载内核映像和根文件系统映像等。从主机下载的文件通常首先被 Boot Loader 保存到目标机的 RAM 中,然后再被 Boot Loader 写到目标机上的FLASH 类固态存储设备中。Boot Loader 的这种模式通常在第一次安装内核与根文件系统时被使用;此外,以后的系统更新也会使用 Boot Loader 的这种工作模式。工作于这种模式下的 Boot Loader 通常都会向它的终端用户提供一个简单的命令行接口。

Blob U-Boot 等这样功能强大的 Boot Loader 通常同时支持这两种工作模式,而且允许用户在这两种工作模式之间进行切换。比如,U-boot 在启动时处于正常的启动加载模式,但是它会延时 10 秒等待终端用户按下任意键而将 U-boot切换到下载模式。如果在 10 秒内没有用户按键,则 U-boot 继续启动 Linux 内核。

6. BootLoader 与主机之间进行文件传输所用的通信设备及协议
最 常见的情况就是,目标机上的 Boot Loader 通过串口与主机之间进行文件传输,传输协议通常是 xmodemymodemzmodem 协议中的一种。但是,串口传输的速度是有限的,因此通过以太网连接并借助 TFTP 协议(或者NFS协议)来下载文件是个更好的选择。
此外,在论及这个话题时,主机方所用的软件也要考虑。比如,在通过以太网连接和 TFTP 协议(或者NFS协议)来下载文件时,主机方必须有一个软件用来的提供 TFTP(或者NFS) 服务。

Boot Loader 的主要任务与典型结构框架
从操作系统的角度看,Boot Loader 的总目标就是正确地调用内核来执行。
另 外,由于 Boot Loader 的实现依赖于 CPU 的体系结构,因此大多数 Boot Loader 都分为 stage1 stage2 两大部分。依赖于 CPU 体系结构的代码,比如设备初始化代码等,通常都放在 stage1 中,而且通常都用汇编语言来实现,以达到短小精悍的目的。而 stage2 则通常用C语言来实现,这样可以实现给复杂的功能,而且代码会具有更好的可读性和可移植性。
Boot Loader stage1 通常包括以下步骤(以执行的先后顺序)
  • 硬件设备初始化。
  • 为加载 Boot Loader stage2 准备 RAM 空间。
  • 拷贝 Boot Loader stage2 RAM 空间中。
  • 设置好堆栈。
  • 跳转到 stage2 C 入口点。
Boot Loader stage2 通常包括以下步骤(以执行的先后顺序)
  • 初始化本阶段要使用到的硬件设备。
  • 检测系统内存映射(memory map)
  • kernel 映像和根文件系统映像从 flash 上读到 RAM 空间中。
  • 为内核设置启动参数。
  • 调用内核。

Boot Loader  stage1

基本的硬件初始化

这是 Boot Loader 一开始就执行的操作,其目的是为 stage2 的执行以及随后的 kernel 的执行准备好一些基本的硬件环境。它通常包括以下步骤(以执行的先后顺序):

1屏蔽所有的中断。为中断提供服务通常是 OS 设备驱动程序的责任,因此在 Boot Loader 的执行全过程中可以不必响应任何中断。中断屏蔽可以通过写 CPU 的中断屏蔽寄存器或状态寄存器(比如 ARM CPSR 寄存器)来完成。

2设置 CPU 的速度和时钟频率。
3RAM 初始化。包括正确地设置系统的内存控制器的功能寄存器以及各内存库控制寄存器等。

4.  关闭 CPU 内部指令/数据 cache

拷贝 stage2 RAM
拷贝时要确定两点:(1) stage2 的可执行映象在固态存储设备的存放起始地址和终止地址;(2) RAM 空间的起始地址。

 设置堆栈指针 sp
堆栈指针的设置是为了执行 C 语言代码作好准备。

跳转到 stage2 C 入口点
在上述一切都就绪后,就可以跳转到 Boot Loader stage2 去执行了。比如,在 ARM 系统中,这可以通过修改 PC 寄存器为合适的地址来实现。

Boot Loader stage2

stage2 的代码通常用 C 语言来实现,以便于实现更复杂的功能和取得更好的代码可读性和可移植性。但是与普通 C 语言应用程序不同的是,在编译和链接 boot loader 这样的程序时,我们不能使用 glibc 库中的任何支持函数。其原因是显而易见的。

初始化本阶段要使用到的硬件设备
这通常包括:初始化至少一个串口,以便和终端用户进行 I/O 输出信息。设备初始化完成后,可以输出一些打印信息,程序名字字符串、版本号等。

加载内核映像
nor flashnand flash(需要编写nand flash裸驱动)上将内核映像拷贝到RAM

设置内核的启动参数
应该说,在将内核映像拷贝到 RAM 空间中后,就可以准备启动 Linux 内核了。但是在调用内核之前,应该作一步准备工作,即:设置 Linux 内核的启动参数。
Linux 2.4.x 以后的内核都期望以标记列表(tagged list)的形式来传递启动参数。启动参数标记列表以标记 ATAG_CORE 开始,以标记 ATAG_NONE 结束。每个标记由标识被传递参数的 tag_header 结构以及随后的参数值数据结构来组成。数据结构 tag tag_header 定义在 Linux 内核源码的include/asm/setup.h 头文件中。在嵌入式 Linux 系统中,通常需要由 Boot Loader 设置的常见启动参数有:ATAG_COREATAG_MEMATAG_CMDLINE

调用内核
Boot Loader 调用 Linux 内核的方法是直接跳转到内核的第一条指令处,也即直接跳转到 MEM_START0x8000 地址处。在跳转时,下列条件要满足:
1 CPU 寄存器的设置:
  • R00
  • R1=机器类型 ID;关于 Machine Type Number,可以参见 linux/arch/arm/tools/mach-types
  • R2=启动参数标记列表在 RAM 中起始基地址;
2 CPU 模式:
  • 必须禁止中断(IRQsFIQs);
  • CPU 必须 SVC 模式;
3 Cache MMU 的设置:
  • MMU 必须关闭;
  • 指令 Cache 可以打开也可以关闭;
  • 数据 Cache 必须关闭;
如果用 C 语言,可以像下列示例代码这样来调用内核:

void (*theKernel)(int zero, int arch, u32 params_addr) = (void (*)(int, int, u32))KERNEL_RAM_BASE;
……
theKernel(0, ARCH_NUMBER, (u32) kernel_params_start);

注:到此为止,绝大多数内容均来源于詹荣开所著嵌入式系统 Boot Loader 技术内幕,只作了少量修改,可视为对该文的转载
下表显示的是几种不同的bootloader

Bootloader
Monitor
描述
x86
ARM
PowerPC
LILO
Linux磁盘引导程序
GRUB
GNU LILO替代程序
Loadlin
DOS引导 Linux
ROLO
ROM引导 Linux而不需要 BIOS
Etherboot
通过以太网卡启动 Linux系统的固件
LinuxBIOS
完全替代 BUIS Linux引导程序
BLOB
LART等硬件平台的引导程序
U-boot
通用引导程序
RedBoot
基于 eCos的引导程序

X86的工作站和服务器上一般使用 LILOGRUB
ARM处理器的芯片商很多,所以每种芯片的开发板都有自己的Bootloader。结果ARM bootloader也变得多种多样。早有为ARM720处理器的开发板编写的固件,后来又有了armboot,以及StrongARM平台的blob, 还有S3C2410处理器开发板上的vivi等。现在armboot已经并入了U-Boot,所以U-Boot也支持ARM/XSCALE平台。U- Boot已经成为ARM平台事实上的标准Bootloader

PowerPC平台的处理器有标准的Bootloader,就是ppcbootPPCBOOT在合并armboot等之后,创建了U-Boot,成为各种体系结构开发板的通用引导程序。U-Boot仍然是 PowerPC平台的主要Bootloader

MIPS公司开发的 YAMON是标准的Bootloader,也有许多 MIPS芯片商为自己的开发板写了Bootloader。现在,U-Boot也已经支持 MIPS平台。

SH平台的标准 Bootloadersh-bootRedboot在这种平台上也很好用。
M68K平台没有标准的BootloaderRedbootU-boot能够支持 m68k系列的系统。