ARM9嵌入式Linux开发-U-Boot分析

目录

1 再讲系统启动与BootLoader

2 U-Boot简介

3 U-Boot代码分析

3.1 获取U-Boot源码

3.2 U-Boot源码结构

3.3 U-Boot配置

3.4 U-Boot编译

3.5 U-Boot连接

3.5.1 lds文件简介

3.5.2 U-Boot.lds的分析

3.6 U-Boot启动过程源码分析

4 U-Boot引导操作系统分析

4.1 U-Boot命令的基本格式和执行过程

4.2引导Linux

4.2.1 mkimage简介

4.2.2源码分析


1 再讲系统启动与BootLoader

BootLoader 是在操作系统运行之前执行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射表,从而建立适当的系统软硬件环境,为最终调用操作系统内核做好准备。

2 U-Boot简介

U-Boot ,全称Universal Boot Loader,是由德国DENX小组的开发,并遵循GPL条款的开放源码项目。它的主要功能是完成硬件设备的初始化、操作系统代码的搬运,并提供一个控制台及一个命令集在操作系统运行前操控硬件设备。之所有将它称之为“通用”是因为它可以引导多种操作系统和支持大多数的CPU架构。     

它所支持的常见操作系统:Linux、NetBSD、VxWorks 等,支持的CPU 有:ARM、MIPS、AVR32、Blackfin 、x86、NIOS、PowerPC 等。

3 U-Boot代码分析

3.1 获取U-Boot源码

U-Boot的源代码可以从http://ftp.denx.de/pub/u-boot/获得。我们使用U-Boot 1.1.6版本代码作为分析的样本。

3.2 U-Boot源码结构

学习一个软件,尤其是开源软件,首先应该从分析软件的工程结构开始。一个好的软件有良好的工程结构,对于学习和理解软件的架构以及工作流程都有很好的帮助。         

从网站上下载得到U-Boot 源码包,例如:U-Boot-1.1.6.tar.bz2.解压就可以得到全部U-Boot 源程序。在顶层目录下有18 个子目录,分别存放和管理不同的源程序。这些目录中所要存放的文件有其规则,可以分为3 类:

  • 第1类目录与处理器体系结构或者开发板硬件直接相关;
  • 第2类目录是一些通用的函数和驱动程序;
  • 第3类目录是U-Boot的应用程序、工具或者文档。

U-Boot的源代码布 局和Linux类似,使用了 按照模块划分的结构, 并且充分考虑了体系结 构和跨平台问题。

ARM9嵌入式Linux开发-U-Boot分析_第1张图片

U-Boot支持十几种架构,包含对几十种处理器、数百种开发板的支持。对于特定的开发板,配置编译过程需要修改其中部分程序。在board目录下找到与自己的开发板相近的配置,然后在这基础上做些修改就可以实现相应的功能。

3.3 U-Boot配置

以smdk2410开发板板为例,其配置的方法是:

make smdk2410_config

实际的作用就是执行 ./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0

mkconfig文件中给出了它的用法

# Parameters:  Target  Architecture  CPU  Board [VENDOR] [SOC]

注:S3C2440和S3C2410被称为SOC,上面出除了有CPU(arm920t)外,还集成了UART、IIC、IIS和USB控制器等外设,smdk210是三星公司针对S3C2410开发的一块评估板。

执行上述命令后,将产生如下结果:

(1)开发板名称BOARD_NAME等于smdk2410;

(2)创建到平台/开发板相关的头文件的连接,如下:

ln -s asm-arm asm

ln -s arch-s3c24x0 asm-arm/arch

ln -s proc-armv asm-arm/proc

执行上述命令后,将产生如下结果:

(3)创建顶层Makefile包含的文件include/config.mk,内容如下

ARCH=arm

CPU=arm920t

BOARD=smdk2410

VENDOR=NULL     #如果为NULL,此行则没有

SOC=s3c24x0

执行上述命令后,将产生如下结果:

(4)创建开发板相关的头文件include/config.h,内容如下

/*Automatically generated -do not edit*/ #include

从以上四个结果可知,如果要在board目录下新建一个开发板的目录,需要在include/configs目录下也建立一个文件.h里面存放的就是开发板的配置信息。     

U-Boot还没有类似Linux一样的可视化配置界面,要手动修改配置文件include/configs/.h来裁剪和设置 U-Boot。

3.4 U-Boot编译

配置完成后,执行“make all”即可编译。 U-Boot的编译过程:

(1)首先编译cpu/$(CPU)/start.S,对于不同的CPU,还可能编译cpu/$(CPU)下的其他文件;

(2)然后,对于平台/开发板相关的每个目录、每个通用目录都使用各自的Makefile生成相应的库。

(3)将1、2步骤生成的.o、.a文件按照board/$(BOARD)/config.mk文件中指定的代码段的起始地址、board/$(BOARD)/U-Boot.lds连接脚本进行连接

(4)第3步得到的是ELF格式的U-Boot,后面的Makefile会将其转换成二进制格式和S-Record格式。

3.5 U-Boot连接

3.5.1 lds文件简介

对于.lds文件,它定义了整个程序编译之后的连接过程,决定了一个可执行程序的各个段的存储位置。先看一下GNU官方网站上对.lds文件形式的完整描述:

SECTIONS {     

...     

secname start BLOCK(align)(NOLOAD): AT (ldadr)     {contents}>region:phdr=fill     

... 

} 

secname和contents是必须的,其他的都是可选的。下面挑几个常用的看看:     

1、secname:段名     

2、contents:决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某段(代码段、数据段等)     

3、start:本段连接(运行)的地址,如果没有使用AT(ldadr),本段存储的地址也是start。GNU网站上说start可以用任意一种描述地址的符号来描述。     

4、AT(ldadr):定义本段存储(加载)的地址。

看一个简单的例子:
 

/* xyz.lds */ 
SECTIONS 
{     

    firtst 0x00000000 :{ x.o y.o }     
    second 0x30000000 : AT(4096){ z.o } 
}     

以上,x.o放在0x00000000地址开始处,y.o放在x.o后面,他们的运行地址也是0x00000000,即连接和存储地址相同(没有AT指定);z.o放在4096(0x1000,是AT指定的存储地址)开始处,但是它的运行地址在0x30000000,运行之前需要从0x1000(加载处)复制到0x30000000(运行处),此过程也就用到了读取Nand flash。     

这就是存储地址和连接(运行)地址的不同,称为加载时域和运行时域,可以在.lds连接脚本文件中分别指定。编写好的.lds文件,在用arm-linux-ld连接命令时带-T filename来调用执行,如

arm-linux-ld –T xyz.lds x.o y.o –o xyz.o。也用-Ttext参数直接指定连接地址,如

arm-linux-ld –T text 0x30000000 x.o y.o –o xyz.o。

3.5.2 U-Boot.lds的分析

OUTPUT_FORMAT("elf32­littlearm","elf32­littlearm","elf32­littlearm")  

;指定输出可执行文件是elf格式,32位ARM指令,小端

OUTPUT_ARCH(arm)  

;指定输出可执行文件的平台为ARM

ENTRY(_start)   ;指定输出可执行文件的起始代码段为_start.

SECTIONS
{
        .= 0x00000000 ; 从0x0位置开始
        .= ALIGN(4); 代码以4字节对齐
        .text :;指定代码段
        {
          cpu/arm920t/start.o (.text); 代码的第一个代码部分
          *(.text);其它代码部分
        }
        .= ALIGN(4)
        .rodata :{*(.rodata)};指定只读数据段
        .= ALIGN(4);
        .data :{*(.data)};指定可读/写数据段
        .= ALIGN(4);
        .got :{*(.got)};指定got段, got段式是uboot自定义的一个段, 非标准段
        __u_boot_cmd_start =.;把__u_boot_cmd_start赋值为当前位置, 即起始位置
        .u_boot_cmd :{*(.u_boot_cmd)};指定u_boot_cmd段, uboot把所有的uboot命令放在该段.
        __u_boot_cmd_end =.;把__u_boot_cmd_end赋值为当前位置,即结束位置
        .= ALIGN(4);
        __bss_start =.; 把__bss_start赋值为当前位置,即bss段的开始位置
        .bss :{*(.bss)}; 指定bss段
        _end =.; 把_end赋值为当前位置,即bss段的结束位置
}

3.6 U-Boot启动过程源码分析

第一阶段源码分析

ARM9嵌入式Linux开发-U-Boot分析_第2张图片

ARM9嵌入式Linux开发-U-Boot分析_第3张图片

第二阶段的源码分析

ARM9嵌入式Linux开发-U-Boot分析_第4张图片

4 U-Boot引导操作系统分析

4.1 U-Boot命令的基本格式和执行过程

U-Boot的命令为用户提供了交互功能,并且已经实现了几十个常用的命令。如果开发板需要很特殊的操作,可以添加新的U-Boot命令。U-Boot的每一个命令都是通过U_Boot_CMD宏定义的。这个宏在头文件中定义

#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage}。

其中:

  • name:命令的名字,他不是一个字符串,不能用双引号括起来; 
  • rep:运行这个命令后,下次直接回车是否可以再次运行; 
  • maxargs:最大的参数个数;
  • command:对应的函数指针; 
  • usage:一个字符串,简短的使用说明; 
  • help:一个字符串,比较详细的使用说明;

对于bootm命令,其定义如下:

U_BOOT_CMD(
 	bootm,	CFG_MAXARGS,	1,	do_bootm,
 	"bootm   - boot application image from memory\n",
 	"[addr [arg ...]]\n    - boot application image stored in memory\n"
 	"\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"
 	"\t'arg' can be the address of an initrd image\n"
#ifdef CONFIG_OF_FLAT_TREE
	"\tWhen booting a Linux kernel which requires a flat device-tree\n"
	"\ta third argument is required which is the address of the of the\n"
	"\tdevice-tree blob. To boot that kernel without an initrd image,\n"
	"\tuse a '-' for the second argument. If you do not pass a third\n"
	"\ta bd_info struct will be passed instead\n"
#endif
);

对于每个使用U_BOOT_CMD宏定义的命令,其实都在".u_boot_cmd"段中定义了一个cmd_tbl_t结构体,连接脚本中有如下代码:

___u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
___u_boot_cmd_end = .;

这几句话的意思其实就是指示链接器将所有.u_boot_cmd数据段中的内容全部放在一起,而且___u_boot_cmd_start和___u_boot_cmd_end是不会占用任何存储空间的,它们只是用来指示地址的两个符号而已。

u-boot的运行过程是首先进行一些初始化化工作,然后在一个死循环中不断接收串口的命令并进行解释执行。在执行命令的时候就是根据命令的名字在内存段___u_boot_cmd_start到___u_boot_cmd_end之间找到他的cmd_tbl_t结构体,然后调用它的函数,具体代码可参见common/main.c中的run_comman函数和common/command.c中的find_cmd函数。
最常用的几个命令如下:

  • go-在地址 'addr' 处开始程序执行
  • run-运行一个环境变量所定义的命令
  • bootm-从内存中进行运行经过mkimage加工的程序映象
  • loadb-通过串口线(kermit mode)来装载二进制文件
  • printenv-打印环境变量
  • setenv-设置环境变量
  • saveenv-保存环境变量到内存
  • tftp-通过网络下载文件
  • mm,cp,protect,erase-flash操作

4.2引导Linux

4.2.1 mkimage简介

bootm命令是用来引导经过u-boot的工具mkimage打包后的kernel image的。U-Boot源代码的tools/目录下有mkimage工具,这个工具可以用来制作不压缩或者压缩的多种可启动映象文件。     

mkimage在制作映象文件的时候,是在原来的可执行映象文件的前面加上一个0x40字节的头,记录参数所指定的信息,这样U-Boot才能识别这个映象是针对哪个CPU体系结构的,哪个OS的,哪种类型,加载内存中的哪个位置,入口点在内存的哪个位置以及映象名是什么。

root@czu:~/桌面# ./mkimage -n 'linux-2.6.30' -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -d zImage zImage.img

Image Name:   linux-2.6.30

Created:      Sat Mar  3 23:41:49 2012
Image Type:   ARM Linux Kernel Image (uncompressed)

Data Size:    1907440 Bytes = 1862.73 kB = 1.82 MB
Load Address: 0x30008000
Entry Point:  0x30008000
 

参数说明: 
-A 指定CPU的体系结构: 
取值 表示的体系结构 
alpha     Alpha 
arm       ARM 
x86       Intel x86 
ia64      IA64 
mips      MIPS 
mips64    MIPS 64 Bit 

-O 指定操作系统类型,可以取以下值: 
openbsd、netbsd、freebsd、4_4bsd、linux、svr4、esix、solaris、irix、sco、dell、ncr、lynxos、vxworks、psos、qnx、u-boot、rtems、artos 
-T 指定映象类型,可以取以下值: 
standalone、kernel、ramdisk、multi、firmware、script、filesystem 
-C 指定映象压缩方式,可以取以下值: 
none 不压缩 
gzip 用gzip的压缩方式 
bzip2 用bzip2的压缩方式 

-a 指定映象在内存中的加载地址,映象下载到内存中时,要按照用mkimage制作映象时,这个参数所指定的地址值来下载 
-e 指定映象运行的入口点地址,这个地址就是-a参数指定的值加上0x40(因为前面有个mkimage添加的0x40个字节的头) 
-n 指定映象名 
-d 指定制作映象的源文件

4.2.2源码分析

do_bootm函数分析    

ARM9嵌入式Linux开发-U-Boot分析_第5张图片

do_bootm_linux函数分析

ARM9嵌入式Linux开发-U-Boot分析_第6张图片

 

你可能感兴趣的:(ARM9嵌入式Linux开发)