BootLoader 介绍 与 uboot 简介

1 BootLoader 的介绍

1.1 BootLoader 的概念

BootLoader 的引入

如果它 能将噪作系统内核复制到内存中运行,无论从本地〔比如Fh)还是从远端(比如通过网络),

Bootloader就是这么一小段程序,它 能将噪作系统内核复制到内存中运行,无论从本地(比如 Flash)还是从远端(比如通过网络),它在系统上电时开始执行初始化硬件设备、准各好软件环境,最后调用操作系统内核。

可以增强 Bootloader 的功能,比如增加网络功能、从上通过串口或网络下载文件、 烧写文件、将Flash上压缩的文件解压后再运行等,这就是、个功能更为强人的Bootloader, 也称为Monitor.实际上,在最终产品中用户并不需要这蚱功能,它们只是为了方便开发。

Bootloader的实现非常依赖于其体硬件,在嵌入式系统中硬件配置千差万别,即使是相同的CPU,它的外设(比如Flash)也可能不同,所以不可能有一个 Bootloader 支持所有的 CPU、所有的电路板。即使是支持CPU架构比较多的 U-Boot,也不是一拿来就可以使用的 (除非里面的配置刚好与你的板子相同),需要进行一些移植。

BootLoader 的启动方式

CPU 上电后会从某个地址开始执行。ARM 为 0x0000000 开始。嵌入式开发板中,需要把存储器件 ROM 或 Flash 等映射到这个地址,Bootloader 就存放在这个地址开始处.这样一上电就可以执行。

在开发时,通常需要使用各种命令操作 Bootloader。一般通过串囗来连接 pc 和开发板, 可以在串口上输入各种命令、观察运行结果等。这也只是对开发人员才有意义,用户使用产品时是不用接串口来控制 Bootloader 的。从这个观点来看,Bootloader 可以分为以下两种操作模式 ( OperationMode ).

  • 启动加载 ( Bootloading ) 模式。 上电后,Bootloader 从板了上的某个固态存储设备上将作系统加载到 RAM 中运行,整个过程并没有用户的介入。产品发布时,Bootloader 工作在这种模式下。
  • 下载(Downloading ) 模式。 在这种模式下,开发人员可以使用各种命令,通过串口连接或网络连接等通信手段从主机 ( Host ) 下载文件( 比如内核映象、文件系统映象 ), 将它们直接放在内存运行或是烧入 Flash 类固态存储设各中。

板子与主机间传输文件时,可以使用串口的 xmodem/ymodem/zmodem 协议,它们使用简单,只是速度比较慢;还可以使用网络通过 tftp 协议来传输,这时,主机上要开启 tftp、 nfs 服务:还有其他方法.比如 USB 等.

1.2 BootLoader 的结构和启动过程

概念

  1. 引导加载程序

    包括固化在固件( firmware )中的 boot 代码(可选)和 Bootloader 两大部分. 有些 CPU 在运行 Bootloader 之前先运行一段固化的程序(固件,firmware).比如 x86 结构的 CPU 就是先运行 BIOS 中的固件,然后才运行硬盘第一个分区( MBR )中的 Bootlloader, 在大多嵌入式系统中并没有周件,Bootloader 是上电后执行的第一个程序。

  2. Linux 内核

    特定羊嵌入式板子的定制内核以及内核的启动参数.内核的启动参数可以是内核默认的,或是由 Bootloader 传递给它的。

  3. 文件系统

    包括根文件系统和建立于 Flash 内存设备之上的文件系统。里而包含了 Linux 系统能够运行所必需的应用程序、库等,比如可以给用户提供噪作 Linux 的控制面的 shell 程序、动态连接的程序运行时需要的 glibc 或 uCIibc 库等。

  4. 用户应用程序

    特定于用户的应用程序,它们也存储在文件系统中。有时在用户应用程序和内核层之间 可能还会包括一个嵌入式图形用户界面。常用的嵌入式 GUI 有:Qtopia 和 MiniGUI 等。 显然,在嵌入系统的固态存储设各上有相应的分区来存储它们,如图所示为一个典型的分区结构。

在这里插入图片描述
在这里插入图片描述

"Bootparameters” 分区中存放一些可设置的参数,比如 IP 地址、串口波特率、要传递给内核的命令行参数等。正常启动过程中,Bootloader 首先运行,然后它将内核复制到内存中(也有些内核可在固态存储设备上百接运行),并且在内存某个固定的地址设置好要传递给内核的参数,最后运行内核。内核启动之后,它会挂接(mount)根文件系统("Root filesystem”)启动文件系统中的应用程序。

BootLoader 的两个阶段

BootLoader 的启动过程可以分为单阶段(SingleStage)、多阶段(Multi-Stage)两种。通常多阶段的Bootloader能提供史为复杂的功能以及更好的可移植性。

从固态存储设备上启动 的 BootLoader 人多都是两阶段的启动过程.第一阶段使用汇编来实现,它完成一些依赖于 CPU体系结构的初始化.并调用第过阶段的代码;第二阶段她通常使用C语言来实现,这 样可以实现更复杂的功能,而且代码会有更好的可读性和可移植性。

一般而言这两个阶段完成的功能可以如下分类。

  1. Bootloader 第一阶段的功能。

    • 硬件设备初始化.
    • 为加载 BootLoader 的第二阶段代码准备RAM空间.
    • 复制 BootLoader 的第二阶段代码到RAM空间中.
    • 设置好栈.
    • 跳转到第二阶段代码的 C 入口点.

    在第一阶段进行的硬件初始化一般包括:关闭 WATCHDOG、关中断、设置 CPU 的速度 和时钟频率、RAM 初始化等。这并不都是必需的,比如 S3C2410/S3C2440 的开发板所使用的 U-Boot 中,就将 CPU 的速度和时钟频率的设置放在第二阶段。

    甚至将第二阶段的代码复制到RAM空间中也不是必需的,对于 NOR Flash 等存储设.完全可以在上面直接执行代码,只不过相比在 RAM 中执行效率大为降低。

  2. BootLoader 第二阶段的功能

    • 初始化本阶段要使用到的硬件设备。
    • 检测系統内存映射(memorymap)。
    • 将内核映象和根文件系統映象从 Flash 上读到 RAM 空间中.
    • 为内核设置启动参数.
    • 调用内核

    为了方便开发,至少要初始化一个串口以便程序员与 BootLoader 进行交互。

    所谓检测内有映射,就是确定板上使用了多少内存、它们的地址空间是什么。由于嵌入式开发中 Bootloader 多是针对某类板子进行编写,所以可以根据板子的情况直接设置,不需要考虑可以适用于各类情况的复杂算法。

    Flash 上的内核映象有可能是经过乐缩的,在读到RAM之后,还需要进行解压。当然, 对于有自解压功能的内核,不需要 BootLoader 来解压。

    将根文件系统映象复制到 RAM 中,这不是必需的,这取决于是什么类型的根文件系统, 以及内核访问它的方法。

    将内核存放在适当的位置后,直接跳到它的入口点即可调用内核。调用内核之前,下列条件要满足。

    1. CPU 寄存器的设置

      • R0 = 0.
      • R1 = 机器类型 ID; 对于 ARM 结构的 CPU, 其机器类型 ID可以参见 linux/arch/arm/tools/mach-types.
      • R2 启动参数标记列表在 RAM 中起始基地址.
    2. CPU 工作模式

      • 必须禁止中断(IRQs 和 FIQs)
      • CPU 必须为 SVC 模式
    3. Cache 和 MMU 的设置

      • MMU 必须关闭
      • 指令 Cache 可以打开也可以关闭
      • 数据 Cache 必须关闭

      如果使用 c 语言,可以使用下面示例调用内核:

      void (*theKernel)(int zero, int arch, u32 params_addr) = 
          (void (*)(int, int, u32))KERNEL_RAM_BAE;
      ...
      thekernel(0, ARCH_NUMBER, (u32)kernel_params_start );
      
  3. Bootloader 与内核的交互

    Bootloader 与内核的交互是单向的,Bootloader 将各类参数传给内核,由于它们不能同时运行,传递办法只有一个:Bootloader 将参数放在某个约定的地方之后,再启动内核,内核 启动后从这个地方获得参数。

    除了纳定好参数存放的地址外,还要规定参数的结构。Linux2.4.x 以后的内核都期望以标记列表( tagged list )的形式来传递启动参数。

    标记,就是一种数据结构:标记列表,就是挨着存放的多个标记。标记列表以标记 ATAG_CORE 开始,以标记ATAG_NONE 结束。 标记的数据结构为 tag,它由一个 tag-header 结构和一个联合(union)组成。tag-header 结构表示标记的类型及长度,比如是表示内存还是表示命令行参数等,对于不同类型的标记使用不同的联合( union )比如表示内存时使用 tag-mem32,表示命令行时使用 tag-cmdline。

    tag 和 tag_header ( linux2.6.9: include /asm-arm/setup.h ) :

    struct tag_header{
        u32 size;
        u32 tag;
    }
    struct tag{
        struct tag_header hdr;
        union{
            struct tag_core			core;
            struct tag_mem32		mem;
            struct tag_videotext	videotext;
            struct tag_ramdisk 		ramdisk;
            struct tag_initrd		initrd;
            struct tag_serialnr		serialnr;
            struct tag_revision		revision;
            struct tag_videolfb		videolfb;
            
            /*
            ** acrn specific
            */
            struct tag_acorn 		acorn;
            
            /*
            ** DC21285 specific
            */
            struct tag_memclk memclk;
        }u;
    };
    

    举例:

    1. 设置标记 ATAG_CORE

      标记列表以 ATAG_CORE 开始,假设 BootLoader 与内核约定的参数存放地址为 0x30000100,则可以以下面代码设置标记 ATAG_CORE:

      /* The list must start with an ATAG_CORE node */
      #define ATAG_CORE	0x54410001
      
      struct tag_core {
      	u32 flags;		/* bit 0 = read-only */
      	u32 pagesize;
      	u32 rootdev;
      };
      
      params = (struct tag*) 0x30000100;
      params->hdr.tag = ATAG_CORE;
      params->hdr.size = tag_size (tag_core);
      params->u.core.flags = 0;
      params->u.core.pagesize = 0;
      params->u.core.rootdev = 0;
      
      params = tag_next (params);
      #define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size))
      
    2. 设置内存标记

      /* it is allowed to have multiple ATAG_MEM nodes */
      #define ATAG_MEM	0x54410002
      
      struct tag_mem32 {
      	u32	size;
      	u32	start;	/* physical start address */
      };
      
      params->hdr.tag = ATAG_MEM;
      params->hdr.size = tag_size (tag_msm32);
      params->u.mem.start = 0x30000000;	//内存起始地址
      params->u.mem.size = 0x40000000;	//内存大小
      params = tag_next (params);
      
    3. 设置命令行标记

      命令行就是一个字符串,它被用来控制内核的一些行为。比如root=/dev/mtdblock 2 init=/linuxrc console=ttySAC0表示根文件系统在 MTD2 分区上,系统启动后执行的第一个程 序为 /linuxrc,控制台为 ttySAC0(即第一个串凵)。

      命令行可以在 Bootloader 中通过命令发置好,然后按如下构造标记传给内核。

      /* command line: \0 terminated string */
      #define ATAG_CMDLINE	0x54410009
      
      struct tag_cmdline {
      	char	cmdline[1];	/* this is the minimum size */
      };
      
      char *p = "root=/dev/mtdblock 2 init=/linuxrc  console=ttySAC0";
      params->hdr.tag = ATAG_CMDLINE;
      params->hdr.size = (sizeof (struct tag_header) + strlen(p) + 4) >> 2;
      strcpy(params->u.cmdline.cmdline, p);
      params = tag_next (params);
      
    4. 设置标记 ATAG_NONE

      标记列表以 ATAG_NONE 结束

      /* The list ends with an ATAG_NONE node. */
      #define ATAG_NONE	0x00000000
      
      struct tag_header {
      	u32 size;
      	u32 tag;
      };
      
      params->hdr.tag = ATAG_NONE;
      params->hdr.size = 0;
      

1.3 常用 BootLoader 介绍

在这里插入图片描述

2 U-Boot 分析与移植

2.1 U-Boot 源码结构

源码目录结构(U-Boot-1.1.6.tar.bz2) :

根目录下共有26个子目录,可以分为4类。

  1. 平台相关的或开发板相关的。
  2. 通用的函数。
  3. 通用的设备驱动程序。
  4. U-Boot工具、示例程序、文档。

各目录说明:

  1. arch\arm\cpu\u-boot.lds就是这个 U-Boot 的链接脚本,与根目录下得 u-boot.lds 是同一个。

  2. board\freescale\mx6ullevk为与使用的板子的硬件相关,为重点。

  3. configs 文件夹 此文件夹为 uboot 默认配置文件,以 _defconfig 结尾命名,每个文件对于不同的板子。在编译 uboot 之前一定要使用 defconfig 来配置 uboot!

    .config 文件中都是以 “CONFIG_” 开始的配置项,这些配置项就是 Makefile 中的 变量,因此后面都跟有相应的值,uboot 的顶层 Makefile 或子 Makefile 会调用这些变量值。 在 .config 中会有大量的变量值为 ‘y’,这些为 ‘y’ 的变量一般用于控制某项功能是否使能,为 ‘y’ 的话就表示功能使能,比如:

    CONFIG_CMD_BOOTM=y #在 config 中的一个文件的语句
    
    ifndef CONFIG_SPL_BUILD
    # core command
    obj-y += boot.o
    obj-$(CONFIG_CMD_BOOTM) += bootm.o #obj-y += bootm.o
    obj-y += help.o
    obj-y += version.o
    

    也就是给 obj-y 追加了一个 “bootm.o”,obj-y 包含着所有要编译的文件对应的.o 文件,这 里表示需要编译文件 cmd/bootm.c。相当于通过 “CONFIG_CMD_BOOTD=y” 来使能 bootm 这 个命令,进而编译 cmd/bootm.c 这个文件,这个文件实现了命令 bootm。在 uboot 和 Linux 内核 中都是采用这种方法来选择使能某个功能,编译对应的源码文件。

  4. .u-boot.xxx_cmd 文件都是编译生成的,都是一些命令文件,通过执行该命令,产生一些文件和配置。

  5. u-boot.xxx 文件

    • u-boot:编译出来的 ELF 格式的 uboot 镜像文件。
    • u-boot.bin:编译出来的二进制格式的 uboot 可执行镜像文件。
    • u-boot.cfg:uboot 的另外一种配置文件。
    • u-boot.imx:u-boot.bin 添加头部信息以后的文件,NXP 的 CPU 专用文件。
    • u-boot.lds:链接脚本。
    • u-boot.map:uboot 映射文件,通过查看此文件可以知道某个函数被链接到了哪个地址上。
    • u-boot.srec:S-Record 格式的镜像文件。
    • u-boot.sym:uboot 符号文件。
    • u-boot-nodtb.bin:和 u-boot.bin 一样,u-boot.bin 就是 u-boot-nodtb.bin 的复制文件。
  6. README,介绍U-Boot

  7. System.map 内存映射表
    在这里插入图片描述 在这里插入图片描述

各目录间的层次结构(建议):

在这里插入图片描述

比如common/cmd_nand.c文件提供了操作 NAND Flash 的各种命令,这些命令通过调用 dnvers/nand/nandbase.c中的擦除、读写函数来实现。这函数针对 NAND Flash 的共性作了 一些封装,将平台/开发板相关的代码用宏或外部函数来代替。而这些宏与外部函数,如果与 平台相关,就要在下一层次的 CPU/XXX( XXX 表示某型号的 CPU )中实现;如果与开发板相关, 就要在下一层次的 board/xxx 目录(xxx 表示某款开发板)中实现。本书移植的 U-Boot,就

以增加烧写 yaffs 文件系统映象的功能为例,即在 common 目录下的 cmd-nand.c 中增加 命令。比如 nand write.yaffs,这个命令要调用 dnvers/nand/nand_util.c 中的相应函数,针对 yaffs 文件系统的特点依次调用擦除、烧写函数。而这些函数依赖于 dnvers/nand/nand_baseccpu/arm920t/s3c24x0/nand_flash.c文件中的相关函数。 目前 U-Boot-l.1.6支持10种架构,根目录下]0个类似Iib_i386的目录;31个型号(类 型)的CPU,cpu目录下有3]个子目录:2]4种开发板,board目录下有2]4个了目录,很 容易从中找到与自己的板子相似的配置,在上面稍作修改即可使用。

  • 0
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值