Uboot分析(一)

BootLoader指系统启动后,在操作系统内核运行之前运行的一段小程序。通过BootLoader,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。通常,BootLoader是严重地依赖于硬件而实现的,特别是在嵌入式世界。因此,在嵌入式世界里建立一个通用的 BootLoader 几乎是不可能的。尽管如此,我们仍然可以对BootLoader归纳出一些通用的概念来,以指导用户特定的BootLoader设计与实现。

BootLoader的操作模式一般分为自启动模式和交互模式。

自启动模式:BootLoaderd从目标机上的某个固态设备上将操作系统加载到RAM中运行,整个过程没有用户的介入;

交互模式:目标机上的BootLoader将通过串口或网络等通信手段从开发板上下载内核映像和根文件系统映像等到RAM中,可以写到目标机上的固态存储介质中,或者直接进行系统的引导。也可以通过串口接收用户的命令。

BootLoader基本功能:

初始化相关硬件;

把BootLoader自搬移到内存中;

执行用户的命令(访问环境变量;通过网络/串口通信;读写RAM/Flash);

加载并执行内核。

 一个嵌入式Linux系统从软件的角度看通常可以分为四个部分:BootLoader、Linux内核、跟文件系统及用户的应用程序。BootLoader处于系统的最底层,运行于系统启动的最初阶段。

系统加电或复位后,所有CPU都会从某个地址开始执行,这是由处理器设计决定的。比如,X86的复位向量在高地址端,ARM处理器在复位时从地址0x00000000取第一条指令。嵌入式系统的开发板都要把板上ROM或Flash映射到这个地址。因此,必须把Bootloader程序存储在相应的Flash位置。系统加电后,CPU将首先执行它。

 BootLoader的启动过程可以是单阶段的,也可以是多阶段的。多阶段一般比单阶段的提供更为复杂的功能,以及更好的可移植性。从固态存储设备上启动的bootloader大多数是二阶段的启动过程。

  BootLoader 的实现依赖于CPU的体系结构,因此大多数 BootLoader 都分为stage1 和stage2 两大部分。依赖于CPU体系结构的代码,比如设备初始化代码等,通常都放在 stage1中,而且通常都用汇编语言来实现,以达到短小精悍的目的。而stage2 则通常用C 语言来实现,这样可以实现更复杂的功能,而且代码会具有更好的可读性和可移植性。

  BootLoader 的 stage1 通常包括以下步骤:

  ·硬件设备初始化;//屏蔽所有的中断、关闭处理器内部指令/数据Cache等

  ·为加载BootLoader的stage2准备RAM空间;

  ·拷贝BootLoader的stage2 到RAM空间中;

  ·设置好堆栈并将bss段清零;

  ·跳转到 stage2 的 C 入口点。

  Boot Loader的stage2通常包括以下步骤:

  ·初始化本阶段要使用到的硬件设备;

  ·检测系统内存映射(memory map);

  ·将内核映像和根文件系统映像从flash上读到 RAM 空间中;

  ·为内核设置启动参数;

  ·调用内核。

为什么bootloader的初始部分要用汇编?一种解释是有些操作必须用汇编实现,如协处理器寄存器的操作。更重要的问题在于,c程序需要一个具体的运行环境,如代码段,初始化的数据段,BSS段,栈,堆等。尤其是栈,它承担着C函数调用参数传递,局部变量的存储等工作。再者,启动时仅有Nand Flash的前4K内容在stepping stone中运行,这如何能保证C程序的完整性呢?因此,通常的做法是将第一阶段的汇编代码在单独的模块实现,并链接到程序的开始处。然后有它将完整的bootloader程序映像文件从Nand Flash中搬运至SDRAM中,并设置好上面谈到的各个段。这么以来,第二阶段的代码就会在SDRAM中运行。

第一阶段汇编代码的入口处,一般首先放置的是cpu异常的跳转代码,如IRQ,FIQ,SWI,Undef等。中断源将中断请求送至cpu的中断控制器,通过中断控制器仲裁,决定被响应与否或响应的顺序。例如IRQ异常,cpu会跳转到IRQ异常跳转指令处,该指令修改pc地址使其指向IRQ异常处理例程。在处理历程中,程序通过判断中断源的偏移量,确定该IRQ异常的具体类型,计算出这种IRQ异常的中断响应函数,这个过程是通过查阅IRQ中断向量表来实现的。IRQ中断向量表中定义了具体IRQ中断响应函数的地址,这些地址可以在第二阶段根据需要而设置,例如Timer的中断响应函数等等。

第二阶段首先做的是设置时钟。复位后,cpu使用外部时钟源,而非MPLL。通过设置MPLL,从而初始化HCLK,FCLK和PCLK,后者给cpu和外设提供稳定的时钟。接着可以对中断控制器和串口进行初始化,这样就可以通过串口向PC终端输出一些交互信息了。

接着打开MMU,指令缓存和数据缓存。这里可以设置协处理器CP15的Register 13(ProcID)为0,并建立从虚地址到物理地址的直接映射关系。

以上工作完成后,将存放于Nand Flash中的kernel和启动参数搬运到内存中的特定区域,重新设置好时钟(与内核中的保持一致),关闭MMU,指令缓存和数据缓存,跳转到内核的起始地址就可以运行了。

posted @ 2012-12-08 13:48  雪中飞  阅读(2399)  评论(6编辑  收藏  举报