目录
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源码分析
BootLoader 是在操作系统运行之前执行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射表,从而建立适当的系统软硬件环境,为最终调用操作系统内核做好准备。
U-Boot ,全称Universal Boot Loader,是由德国DENX小组的开发,并遵循GPL条款的开放源码项目。它的主要功能是完成硬件设备的初始化、操作系统代码的搬运,并提供一个控制台及一个命令集在操作系统运行前操控硬件设备。之所有将它称之为“通用”是因为它可以引导多种操作系统和支持大多数的CPU架构。
它所支持的常见操作系统:Linux、NetBSD、VxWorks 等,支持的CPU 有:ARM、MIPS、AVR32、Blackfin 、x86、NIOS、PowerPC 等。
U-Boot的源代码可以从http://ftp.denx.de/pub/u-boot/获得。我们使用U-Boot 1.1.6版本代码作为分析的样本。
学习一个软件,尤其是开源软件,首先应该从分析软件的工程结构开始。一个好的软件有良好的工程结构,对于学习和理解软件的架构以及工作流程都有很好的帮助。
从网站上下载得到U-Boot 源码包,例如:U-Boot-1.1.6.tar.bz2.解压就可以得到全部U-Boot 源程序。在顶层目录下有18 个子目录,分别存放和管理不同的源程序。这些目录中所要存放的文件有其规则,可以分为3 类:
U-Boot的源代码布 局和Linux类似,使用了 按照模块划分的结构, 并且充分考虑了体系结 构和跨平台问题。
U-Boot支持十几种架构,包含对几十种处理器、数百种开发板的支持。对于特定的开发板,配置编译过程需要修改其中部分程序。在board目录下找到与自己的开发板相近的配置,然后在这基础上做些修改就可以实现相应的功能。
以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目录下新建一个开发板
U-Boot还没有类似Linux一样的可视化配置界面,要手动修改配置文件include/configs/
配置完成后,执行“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格式。
对于.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。
OUTPUT_FORMAT("elf32littlearm","elf32littlearm","elf32littlearm")
;指定输出可执行文件是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段的结束位置
}
第一阶段源码分析
第二阶段的源码分析
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}。
其中:
对于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函数。
最常用的几个命令如下:
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 指定制作映象的源文件
do_bootm函数分析
do_bootm_linux函数分析