U-boot在开发板上移植过程详解(1)---bootloader架构分析
时间:2011-05-20 来源:☆&寒 烟☆
本例中采用的同样是前边一贯的实验板,这里就不对板子资源做进一步介绍了。
我们知道,bootloader是系统上电后最初加载运行的代码。它提供了处理器上电复位后最开始需要执行的初始化代码。在PC机上引导程序一般由BIOS开始执行,然后读取硬盘中位于MBR(Main Boot Record,主引导记录)中的Bootloader(例如LILO或GRUB),并进一步引导操作系统的启动。然而在嵌入式系统中通常没有像BIOS那样的固件程序,因此整个系统的加载启动就完全由bootloader来完成。它主要的功能是加载与引导内核映像。
一个嵌入式的存储设备通过通常包括四个分区,第一分区存放的当然是u-boot,第二个分区存放着u-boot要传给系统内核的参数,第三个分区是系统内核(kernel),第四个分区则是根文件系统。如下图所示:
图一 固态存储设备的典型空间分配结构
第一部分:Bootloader启动模式
Bootloader的启动过程可以是单阶段的,也可以是多阶段的。多阶段的bootloader比单阶段的bootloader提供更为复杂的功能。以及更好的移植性,比如U-bot。
第一阶段:
Bootloader执行最基本的硬件初始化操作。如关闭中断,关闭看门狗以避免处理器被复位,以及关闭MMU功能,关闭处理器缓存(数据缓存一定要关闭,指令缓存可以打开),设置系统时钟,初始化内存等。这一阶段代码通常由汇编代码编写,为了运行下一阶段的C语言程序还必须设置好堆栈。如果是从NAND Flash启动,则必须通过NAND Flash控制器将bootloader代码复制到内存。
第二阶段:
这一阶段一般用C语言编写,大致分为一下几步:
1)初始化各种硬件设备,比如设置处理器正常工作的时钟频率,初始化串口等。
2)检测系统内存,主要是确定系统内存容量以及其地址空间信息。
3)将内核映像文件加载到内存。
4)准备内核引导参数。
5)跳转到内核的第一条指令处,开始执行内核初始化代码,控制权转移到内核代码,bootload的使命结束。
第二部分:Bootloader的操作模式
一般的bootload而都包含两种不同的操作模式:启动加载模式和下载模式
启动加载模式:这种模式也称自主模式,即bootloader从目标机上的某个固体存储设备上将操作系统加载到内存中运行,这个过程没有用户的介入。这种模式是正
常的工作模式,最终的产品发布时必须工作在这种模式下。
下载模式:在这种模式下,目标机上的bootloader将通过串口,网络连接或者其他通信手段从主机下载文件,比如下载内核映像或根文件系统映像等。从主机下载的文件通常被保存在目标机的内存中,然后再写入到目标机上的Flash等固态存储设备中。这种工作模式通常在第一次安装内核与跟文件系统时使用。或者在系统更新时使用。进行嵌入式系统调试时一般也让bootloader工作在这一模式下。
第三部分:Arm bootloader的特点
要实现一个通用的bootloader是一件不可能的事情,但是还是可以根据Arm的体系结构,从理论上总结出一些Arm平台上bootloader的共性,这些共性只能局限于bootloader的基本功能。
对于一个运行于Arm平台的系统来说,bootloader作为引导与加载内核映像的工具需要提供一下几个功能:
1)bootloader必须能够初始化内存。
2)虽然系统的启动并不一定依赖串口,但一般来说bootloader应该初始化至少一个串口,通过它与主机进行通信,以便进行开发,调试和维护工作。
3)这是linux内核所要求的,如果不给出内核参数,则内核就会使用其默认参数。
4)一般来说,内核映像必须在内存运行,所以必须从其他非易失存储介质上复制到内存。
5)让执行流程跳转到内核映像的入口。
启动内核时,系统必须处于指定的状态,包括处理器模式,MMU和缓存的设置,寄存器的设置等方面。
处理器模式)处理器应处于SVC模式,在这种特权模式下,内核才能执行所有的指令。中断必须关闭。在异常向量表尚未初始化的情况下,如果发生中断,将导致系统崩溃。一般来说,bootloader本身也没有必要支持中断的实现,这属于内核的管理范围。
MMU和缓存设置)MMU必须关闭。启动MMU进入保护模式是内核的工作。而bootloader本身工作在实模式下,所有对地址的操作使用的都是物理地址,不存在虚拟地址。数据缓存必须关闭,bootloader的主要功能是装载内核映像,映像数据必须真实写回内存中,不能仅放在处理器的缓存中,所以数据缓存必须关闭。指令缓存可以打开,一般情况下,推荐将指令缓存也关闭。
寄存器设置)寄存器R0的值应为0,R1的值表示机器类型,R2的值则是引导参数列表在内存中的起始地址。这三个寄存器是在最后启动内核时需要设置的。
第四部分:U-boot源码分析
在实际使用中,U-boot被固话在CPU的上电/复位启动地址处(通常在非易失存储器中)。每当嵌入式设备上电/复位时,CPU总是从启动地址(U-boot)处启动。U-bo
ot启动后,首先初始化各种硬件设备,如CPU,缓存,存储器,MMU,总线控制器,各种I/O接口等,然后从远程主机或者本地非易失存储设备中装载可执行文件或操作系统
,为整个嵌入式系统准备运行环境。要使用U-boot,最初必须使用某种硬件支持的方式将U-boot映像写入非易失存储器中。比如我这里板子上没有任何的bootloader
可以使用JTAG接口将U-boot映像写入Flash的开始处。
U-boot采用了一种高度模块化的编程方式,不同功能类别的代码分别放在不同的目录中,几个U-boot常用到的目录分析如下所示:
1 board)这个目录中存放了所有U-boot支持的目标板的子目录。在这个目录中一般是针对特定目标板的初始化和操作代码。
2 cpu)这个目录中存放了U-boot支持的所有CPU类型。
3 common)这个目录中存放独立于处理器体系架构的通用代码,包括U-boot的一些公共命令的实现。一般来说,其中以cmd_*.c命令的文件就是相对命令的实
现。比如cmd_bootm.c就是对命令bootm的实现。
4 drivers)这个目录中存放的是各种外设接口的驱动程序。
5 fs)这个目录中存放了U-boot支持的文件系统。
6 include)这个目录是存放各种CPU及目标板的头文件和配置文件的公共目录,其中的configs目录存放了各种目标板的配置头文件。针对不同的板子,里边的配置
文件要根据实际情况进行修改。
7 lib_XXX)这个目录存放XXX体系架构的处理器的相关支持。
8 net)这个目录用于存放与网络功能相关的文件。
下节,就要开始对U-boot源码中的关键功能的实现进行分析,主要是从一下几个方面:
1)使用汇编语言编写的第一阶段代码
2)第二阶段代码命令的实现
3)第二阶段操作系统引导机制的实现