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 的结构和启动过程
概念
-
引导加载程序
包括固化在固件( firmware )中的 boot 代码(可选)和 Bootloader 两大部分. 有些 CPU 在运行 Bootloader 之前先运行一段固化的程序(固件,firmware).比如 x86 结构的 CPU 就是先运行 BIOS 中的固件,然后才运行硬盘第一个分区( MBR )中的 Bootlloader, 在大多嵌入式系统中并没有周件,Bootloader 是上电后执行的第一个程序。
-
Linux 内核
特定羊嵌入式板子的定制内核以及内核的启动参数.内核的启动参数可以是内核默认的,或是由 Bootloader 传递给它的。
-
文件系统
包括根文件系统和建立于 Flash 内存设备之上的文件系统。里而包含了 Linux 系统能够运行所必需的应用程序、库等,比如可以给用户提供噪作 Linux 的控制面的 shell 程序、动态连接的程序运行时需要的 glibc 或 uCIibc 库等。
-
用户应用程序
特定于用户的应用程序,它们也存储在文件系统中。有时在用户应用程序和内核层之间 可能还会包括一个嵌入式图形用户界面。常用的嵌入式 GUI 有:Qtopia 和 MiniGUI 等。 显然,在嵌入系统的固态存储设各上有相应的分区来存储它们,如图所示为一个典型的分区结构。
在这里插入图片描述
"Bootparameters” 分区中存放一些可设置的参数,比如 IP 地址、串口波特率、要传递给内核的命令行参数等。正常启动过程中,Bootloader 首先运行,然后它将内核复制到内存中(也有些内核可在固态存储设备上百接运行),并且在内存某个固定的地址设置好要传递给内核的参数,最后运行内核。内核启动之后,它会挂接(mount)根文件系统("Root filesystem”)启动文件系统中的应用程序。
BootLoader 的两个阶段
BootLoader 的启动过程可以分为单阶段(SingleStage)、多阶段(Multi-Stage)两种。通常多阶段的Bootloader能提供史为复杂的功能以及更好的可移植性。
从固态存储设备上启动 的 BootLoader 人多都是两阶段的启动过程.第一阶段使用汇编来实现,它完成一些依赖于 CPU体系结构的初始化.并调用第过阶段的代码;第二阶段她通常使用C语言来实现,这 样可以实现更复杂的功能,而且代码会有更好的可读性和可移植性。
一般而言这两个阶段完成的功能可以如下分类。
-
Bootloader 第一阶段的功能。
- 硬件设备初始化.
- 为加载 BootLoader 的第二阶段代码准备RAM空间.
- 复制 BootLoader 的第二阶段代码到RAM空间中.
- 设置好栈.
- 跳转到第二阶段代码的 C 入口点.
在第一阶段进行的硬件初始化一般包括:关闭 WATCHDOG、关中断、设置 CPU 的速度 和时钟频率、RAM 初始化等。这并不都是必需的,比如 S3C2410/S3C2440 的开发板所使用的 U-Boot 中,就将 CPU 的速度和时钟频率的设置放在第二阶段。
甚至将第二阶段的代码复制到RAM空间中也不是必需的,对于 NOR Flash 等存储设.完全可以在上面直接执行代码,只不过相比在 RAM 中执行效率大为降低。
-
BootLoader 第二阶段的功能
- 初始化本阶段要使用到的硬件设备。
- 检测系統内存映射(memorymap)。
- 将内核映象和根文件系統映象从 Flash 上读到 RAM 空间中.
- 为内核设置启动参数.
- 调用内核
为了方便开发,至少要初始化一个串口以便程序员与 BootLoader 进行交互。
所谓检测内有映射,就是确定板上使用了多少内存、它们的地址空间是什么。由于嵌入式开发中 Bootloader 多是针对某类板子进行编写,所以可以根据板子的情况直接设置,不需要考虑可以适用于各类情况的复杂算法。
Flash 上的内核映象有可能是经过乐缩的,在读到RAM之后,还需要进行解压。当然, 对于有自解压功能的内核,不需要 BootLoader 来解压。
将根文件系统映象复制到 RAM 中,这不是必需的,这取决于是什么类型的根文件系统, 以及内核访问它的方法。
将内核存放在适当的位置后,直接跳到它的入口点即可调用内核。调用内核之前,下列条件要满足。
-
CPU 寄存器的设置
- R0 = 0.
- R1 = 机器类型 ID; 对于 ARM 结构的 CPU, 其机器类型 ID可以参见 linux/arch/arm/tools/mach-types.
- R2 启动参数标记列表在 RAM 中起始基地址.
-
CPU 工作模式
- 必须禁止中断(IRQs 和 FIQs)
- CPU 必须为 SVC 模式
-
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 );
-
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; };
举例:
-
设置标记 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))
-
设置内存标记
/* 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);
-
设置命令行标记
命令行就是一个字符串,它被用来控制内核的一些行为。比如
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);
-
设置标记 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类。
- 平台相关的或开发板相关的。
- 通用的函数。
- 通用的设备驱动程序。
- U-Boot工具、示例程序、文档。
各目录说明:
-
arch\arm\cpu\u-boot.lds
就是这个 U-Boot 的链接脚本,与根目录下得 u-boot.lds 是同一个。 -
board\freescale\mx6ullevk
为与使用的板子的硬件相关,为重点。 -
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 内核 中都是采用这种方法来选择使能某个功能,编译对应的源码文件。
-
.u-boot.xxx_cmd 文件都是编译生成的,都是一些命令文件,通过执行该命令,产生一些文件和配置。
-
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 的复制文件。
-
README,介绍U-Boot
-
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_basec
、 cpu/arm920t/s3c24x0/nand_flash.c
文件中的相关函数。 目前 U-Boot-l.1.6支持10种架构,根目录下]0个类似Iib_i386的目录;31个型号(类 型)的CPU,cpu目录下有3]个子目录:2]4种开发板,board目录下有2]4个了目录,很 容易从中找到与自己的板子相似的配置,在上面稍作修改即可使用。