bootloader和uboot知识汇总

BootLoader

在专用的嵌入式板子运行操作系统已经变得越来越流行。一个嵌入式系统从软件的角度看通常可以分为三个层次:

  • 引导加载程序。包括固化在固件(firmware)中的Booter代码(可选),和Loader两大部分。
  • 操作系统。特定于嵌入式板子的定制内核以及内核的启动参数;以及在内核和应用程序之间可能还会包括一个嵌入式图形用户界面,常用的嵌入式GUI有:MicroWindows和MiniGUI等。
  • 用户应用程序。特定于用户的应用程序。

引导加载程序是系统加电后运行的第一段软件代码,称之为Bootloader。BootLoader是Booter和Loader的合写:前者意味着要初始化嵌入式系统硬件使之运行起来,至少是部分运行起来,与PC机中的BIOS作用相似;后者意味着将嵌入式操作系统映像加载到内存中,并跳转过去运行。如PC机中BIOS在完成硬件检测和资源分配后,将硬盘中的BootLoader读到系统的RAM中,然后将控制权交给OS BootLoader。BootLoader的主要运行任务就是将内核映象从硬盘上读到 RAM 中,然后跳转到内核的入口点去运行,也即开始启动操作系统。

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

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

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

Bootloader并不是嵌入式系统必不可少的组成部分,但是在系统中有Bootloader可以给嵌入式产品的开发调试带来很多便利。

说到底,bootloader是一段裸机程序,是直接与硬件打交道的。① 它在系统上电后开始执行(PC系统中在bootloader之前有个BIOS固件,嵌入式系统中一般没有),其最终目的是“初始化硬件设备,准备好软件环境,最后调用操作系统内核”。② 为了方便进行底层开发(比如调试内核等),bootloader增加了很多功能,主要有UART、网络、USB、读写Flash(Nor or Nand)、解压缩、LCD支持等等。这样的bootloader具有比较强大的功能,除了不能进行多任务调度,已经类似一个小型操作系统了,被称为“Monitor”。U-boot就是这样一种bootloader,全功能体积高达数百Kbytes,如果不需要这些开发调试功能,发布产品时它能配置到很小的体积。

那我们也知道其实OS(如linux)的Kernel其实也是一段裸机程序,也是直接掌管硬件的,不过这个程序极其复杂,许许多多的计算机专家和黑客为其耗费心血,如果三四百K的U-boot已经比较复杂,那么一个典型功能的linux Kernel编译后竟然能高达万Kbytes,光从代码量来看,linux的复杂度已经是U-boot的百倍。而其实远不止。

虽然kernel很complex,但终究不过是一段裸机代码,Bootloader进行所谓的“kernel引导”,其过程不过是从bootloader里的一句跳转代码,跳转到kernel代码处(执行kernel中的第一个函数),所谓传递参数也不过是bootloader和kernel约定一个内存地点存放。在这个过程中,bootloader和kernel虽然都处于同一个内存里,但是它们除了“引导”与“传递有限的参数”这样的关系,并无其它关系,完全是两个独立的程序。之所以在kernel之前用一个bootloader来引导(为什么开机不能直接执行kernel,所有事情都交给kernel做?),其思想类似于一个板级支持包:kernel假定执行的时候已经具备了一个基本的硬件运行条件,这个环境的初始化(最底层的一些硬件初始化、硬件信息设定)需要bootloader来完成,也许这样kernel的设计才能保持一定的一致性。

U-boot为了能支持很多不同的体系结构、SoC、电路板,设计了一种代码文件的组织结构(包括makefile文件),这种设计很大程度上借鉴了linux kernel,很多硬件驱动代码也直接从linux kernel中搬迁过去,因为linux kernel也是一种移植性非常好的程序。“在通用代码与系统特定代码之间,不惜代价,保持一条明确的分界线”,这是可移植程序设计的不变箴言。

二、Uboot

  U-Boot是由开源项目PPCBoot发展起来的,ARMboot并入了PPCBoot,和其他一些arch的Loader合称U-Boot。2002年12月17日第一个版本U-Boot-0.2.0发布,同时PPCBoot和ARMboot停止维护。

  U-Boot自发布以后已更新6次,最新版本为U-Boot-1.1.1,U-Boot的支持是持续性的。

  U-Boot支持的处理器构架包括PowerPC (MPC5xx,MPC8xx,MPC82xx,MPC7xx,MPC74xx,4xx), ARM (ARM7,ARM9,StrongARM,Xscale),MIPS (4Kc,5Kc),x86等等, U-Boot(Universal Bootloader)从名字就可以看出,它是在GPL下资源代码最完整的一个通用Boot Loader。

  U-Boot提供两种操作模式:启动加载(Boot loading)模式和下载(Downloading)模式,并具有大型Boot Loader的全部功能。主要特性为:

  -SCC/FEC以太网支持

  -BOOTP/TFTP引导

  -IP,MAC预置功能

  -在线读写FLASH,DOC, IDE,IIC,EEROM,RTC

  -支持串行口kermit,S-record下载代码

  -识别二进制、ELF32、pImage格式的Image,对Linux引导有特别的支持

  -监控(minitor)命令集:读写I/O,内存,寄存器、内存、外设测试功能等

  -脚本语言支持(类似BASH脚本)

  -支持WatchDog,LCD logo,状态指示功能等

  U-Boot的功能是如此之强大,涵盖了绝大部分处理器构架,提供大量外设驱动,支持多个文件系统,附带调试、脚本、引导等工具,特别支持Linux,为板级移植做了大量的工作。U-Boot1.1.1版本特别包含了对SA1100和44B0芯片的移植,所以44B0移植主要是针对Board 的移植,包括FLASH、内存配置以及串口波特率等等。U-Boot的完整功能性和后续不断的支持,使系统的升级维护变得十分方便。

以版本2010.09的U-boot为例:

├── api 存放uboot提供的API函数
├── arch 与体系结构相关的代码,uboot的重头戏  +++
├── board 根据不同开发板定制的代码,代码也不少  +++
├── common 通用的代码,涵盖各个方面,已命令行处理为主
├── disk 磁盘分区相关代码
├── doc 文档,一堆README开头的文件
├── drivers 驱动,很丰富,每种类型的设备驱动占用一个子目录  +++
├── examples 示例程序
├── fs 文件系统,支持嵌入式开发板常见的文件系统
├── include 头文件,已通用的头文件为主  +++
├── lib 通用库文件
├── nand_spl NAND存储器相关代码
├── net 网络相关代码,小型的协议栈
├── onenand_ipl
├── post 加电自检程序
└── tools 辅助程序,用于编译和检查uboot目标文件

可以将这些代码分为4类

1、平台相关(arch)、或开发板相关(board)(系统特定代码)
2、通用的函数(include common)
3、通用驱动(disk  drivers  fs  nand_spl  onenand_ipl  net  post)
4、工具、实例、文档(api  doc  examples  tools)

除了引导kernel的主线之外,U-boot的支持的各种硬件操作驱动实现,有一定的层次性:为了实现“通用的函数”,首先要调用“通用驱动”,这些通用驱动如果有引用“平台相关”的宏或者外部函数,就需要调用到这些系统特定代码。其层次是" 2<-->3<-->1 " 。

一般来说,移植U-boot主要就是修改“1”项中的代码,以及在include/configs/XXX.h中打开需要的功能以及定义一些常量,具体的实现就是在arch和board子目录,来看一下这两个子目录的组织:

|--arch\
   |--config.mk  ===>配置脚本
  |--arm\
    |--cpu\ 子目录对应一种处理器的不同产品型号或者系列;
      |--arm920t\
        |--a320\
        |...
        |--s3c24x0\
          |--interrupts.c
          |--speed.c
          |--timer.c
          |--usb.c
          |--usb_ohci.c
          |--usb_ohci.h
        |--Makefile
        |--cpu.c
        |--interrupts.c
        |--start.S <==整个bootloader入口点
        |--u-boot.lds <==链接脚本
        |--Makefile 
     |--include\ 子目录是处理器用到的头文件;
       |--asm\
         |--arch-a320\
         |...
         |--arch_s3c24x0\
            |--memory.h
            |--s3c2400.h 
            |--s3c2410.h 
            |--s3c24x0_cpu.h 
            |--s3c24x0.h
         |...
         |--proc-armv\(公用)
          |--atomic.h
         |--bitops.h
         |--byteorder.h
         |--cache.h
         |--... (等共24个.h公用头文件)
     |--lib\ 目录对应用到处理器公用的代码;
      |--board.c
      |--config.mk
      |--... (等共16个文件)
|--board\
  |--ppmc7xx\
  |...
  |--samsung\
      |--goni\
      |--smdk2400\
      |--smdk2410\
          |--smdk2410.c
          |--flash.c
          |--lowlevel_init.S
          |--config.mk
          |--Makefile
          |--nand_read.c
          |--nand_read_save.c
  |...

我们知道ARM是ARM公司设计的一种体系结构,这个公司专门设计IP core出售给半导体厂商加上一些外围部件生产SoC,这些IP Core有很多代的产品,如v4、v5、v6、v7,还根据有没有thumb指令集(t)、DSP加强(e)、java硬件加速(j)等进一步细分。而每一代IP core都有不少半导体厂家生产SoC,如qualcomm、Samsung、TI等,因此ARM平台的组织最为复杂。

打开arch,其中所有的子目录代表一种体系结构11种;选取其中的ARM类,里面包含cpu、include、lib三个子目录,其中lib是这个体系结构的公用代码;include下面只有一个子目录asm,其中包含的是不同的SoC对应的头文件及一些公用头文件;cpu中是ARM体系结构中不同代际的IP core,每种IP core中都有一些子目录代表不同厂商的SoC以及公用代码。(其他体系结构的platform没这么复杂)。

board则是基于某种SoC设计的电路板,2010.09的版本支持有近290种board。一般选择相似的board进行U-boot的移植。当然board里面的代码都是跟特定的board有关的。因此,要分arch、IP-core、SoC、Board 4个层次来理解U-boot在ARM系统上的移植性,而其他体系结构有可能只有2-3层。

你可能感兴趣的:(bootloader和uboot知识汇总)