BootLoader之u-boot
1、BootLoader的概念
引导加载(BootLoader)程序是系统上电后运行的第一段软件代码。BootLoader的主要运行任务就是将内核映像从硬盘上读到RAM中,然后跳转到内核的入口点去执行,即启动操作系统。
在嵌入式系统中,整个系统的加载启动任务就完全由BootLoader来完成。在基于S3C2440的嵌入式系统中,系统上电或复位时,通常都从地址0X00000000处开始执行(取指令,地址0X00000000是由制造商预先安排的),而在这个地址处安排的通常就是系统的BootLoader程序。
简单地说,BootLoader就是在操作系统内核运行之前的一段小程序。通过这段小程序我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软、硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。
通常,BootLoader时严重依赖于硬件而实现的,特别时在嵌入式领域,因此在嵌入式领域中想构建一个通用的BootLoader几乎时不可能的。
BootLoader除了依赖CPU的体系结构,还依赖于具体的嵌入式板级设备的配置,对于两块嵌入式板,即使时基于同一种CPU构建的,嵌入式板的接线不同,嵌入式板的板级设备不同,两者的BootLoader不能通用。
在基于S3C2440的嵌入式系统中,系统上电或复位时,通常都从地址0X00000000处开始执行(取指令,地址0X00000000是由制造商预先安排的),而基于该CPU构建的嵌入式系统通常都有某种类型的固态存储设备被映射到这个预先安排的地址上,如ROM、EEPROM或FLASH。
目标机和宿主机之间一般通过串口建立链接,在执行BootLoader时通常会通过串口打印输出信息,或者从串口读取用户控制字符等。
2、BootLoader的操作模式
大多数BootLoader都包含两种不同的操作模式(Operation Mode):“启动加载模式”和“下载模式”,这种区别只对开发者才有意义。从最终用户的角度看,BootLoader的作用就是用来引导、加载操作系统的。
启动加载(Boot Loading)模式:这种模式也成为“自主”模式,即BootLoader从目标机上的某个固态存储设备上将操作系统加载到RAM中运行,整个过程并没有用户的介入。
下载(DownLoading)模式:在这种模式下,目标机上的BootLoader将通过串口链接或网络链接等通信手段从宿主机下载文件,比如下载Linux内核镜像和根文件系统映像。从宿主机下载的文件通常首先被BootLoader保存到目标机的RAM中,然后在被BootLoader写到目标机上的FLASH类固态存储设备中。BootLoader的这种模式通常在第一次安装Linux内核与根文件系统时被使用;此外,以后系统更新也会使用这种模式。
一般功能强大的BootLoader,如U-BOOT,同时支持这两种工作模式,而且允许用户在这两种模式之间来回切换。比如,U-BOOT在启动时处于正常的启动加载模式,但它会延时10秒等待终端用户按下任意键而将U-BOOT切换到下载模式。如果在10秒内没有用户按键,则U-BOOT继续启动Linux内核。
BootLoader与宿主机有两种方式进行通信,一是借助串口,但通信速度有限;二是通过以太网并借助TFTP或NFS协议来下载文件,此时宿主机必须有一个软件用来提供TFTP或NFS服务。
3、BootLoader的分类
(1)X86的工作站和服务器上一般使用LILO和GRUB;
(2)不同ARM处理器芯片都有自己的BootLoader,如armboot、vivi和blob等;
(3)PowerPC平台的BootLoader是PPCBOOT;
(4)MIPS芯片的BootLoader是YAMON;
(5)SH平台的BootLoader是sh-boot;Redboot在这种平台上也很好用。
4、多平台引导程序——U-BOOT
4.1、U-BOOT简介
U-BOOT,即Universal BootLoader,其源码目录、编译形式与Linux内核很相似。事实上,不少U-BOOT源码就是相应的Linux内核源程序的简化。U-BOOT支持ARM、PowerPC、X86、MIPS体系结构的上百种芯片,已经成为功能最多、灵活性最强、且开发最积极的开源BootLoader。
4.2、U-BOOT的源码结构
以u-boot-2009.11为例来讲解U-BOOT的源码。
u-boot-2009.11顶层目录下有30多个子目录,这些目录可分为3类:
(1)第一类目录与处理器的体系架构或开发板硬件直接相关;
(2)第二类目录是一些通用的函数或驱动程序;
(3)第三类目录是u-boot的应用程序、工具或文档。
下面列出u-boot顶层目录下各级目录的存放原则
board目录,存放与开发板或电路板相关的文件的目录;
cpu目录,存放与CPU架构相关的文件的目录;
lib_xxx目录,存放与CPU结构体系相关的库文件,如与ARM相关的库文件就存放在lib_arm目录中;
include目录,存放u-boot使用的头文件,还有支持各种硬件平台的汇编文件、系统的配置文件和支持文件系统的文件。该目录下的configs子目录中存放有与开发板相关的配置头文件;
common目录,实现u-boot命令行下支持的命令,每一条命令都对应一个文件;
lib_generic目录,存放通用库函数的文件;
net目录,存放与网络协议相关的文件,如TFTP、NFS等;
fs目录,存放支持文件系统的文件的目录;
drivers目录,u-boot支持的设备驱动程序都放在该目录中,如各种网卡,串口,USB等;
disk目录,存放对磁盘的支持文件;
doc目录,存放u-boot的说明文档;
tools目录,存放生成u-boot的工具,如mkimage、crc等;
examples目录,存放一些可以独立运行的应用程序小例子,如HelloWorld;
4.3、u-boot的配置编译
u-boot的源码是通过GUN Makefile组织编译的。u-boot顶层目录下的Makefile完成对开发板的整体配置,然后递归调用各级子目录下的Makefile,最后把所有编译过的程序链接成u-boot映像。
4.3.1、u-boot的基本配置编译方法
在u-boot的顶层目录中执行如下两个命令:
#make <board_name>_config,如#make TQ2440_config或#make smdk2410_config,用来配置u-boot
#make,开始编译u-boot的过程
4.3.2、u-boot顶层目录下的Makefile
每一种开发板都需要在u-boot顶层目录下的Makefile文件中设置自己的配置、规制。例如smdk2410开发板的规制定义如下:
执行配置u-boot的命令:make smdk2410_config,将通过u-boot顶层目录下的mkconfig脚本文件生成配置文件include/config.mk,该文件内容如下:
ARCH=arm
CPU=arm920t
BOARD=smdk2410
VENDOR=samsung
SOC=s3c24x0
配置文件include/config.mk定义了ARCH、CPU、BOARD、VENDOR、SOC等变量,确定了硬件平台所依赖的目录文件。则smdk2410硬件平台所依赖的相关目录文件有:
board/samsung/smdk2410/
cpu/arm920t/
cpu/arm920t/s3c24x0/
lib_arm/
include/asm_arm/
include/configs/smdk2410.h
u-boot顶层目录下的Makefile文件的编译选项和规则在u-boot顶层目录下的config.mk文件中定义。通过ARCH 、CPU、BOARD、VENDOR、SOC等变量为不同硬件平台定义不同的编译选项和规则,不同体系结构的硬件的编译规则分别包含在各自的lib_xxx(xxx表示某个体系架构,如lib_arm)目录下的config.mk文件中。
4.3.3、开发板配置头文件
在移植u-boot时,出来要编译u-boot顶层目录下的Makefile文件,还有为开发板定义配置选项或参数。这个头文件就是/include/configs/<board_name>.h。board_name即为相应的开发板名称,可自定义。该头文件中定义的选项或参数宏都以CONFIG_为前缀,用来选择处理器、设备接口、命令、属性等。以/include/configs/smdk2410.h为例
4.3.4、u-boot的编译结果
u-boot编译完成后,可以在u-boot的顶层目录下得到各种格式的映像文件和符号表,例如:
System.map:u-boot映像的符号表;
u-boot:u-boot映像的ELF格式;
u-boot.bin:u-boot映像原始的二进制格式;
u-boot.srec:u-boot映像的S-Record格式;
一般u-boot.bin最为常用,直接按照二进制格式下载,并且按照绝对地址烧写到Flash中即可。
4.3.5、u-boot的工具
在u-boot的顶层目录下的tools目录中会生成u-boot工具,如:
bmp_logo:制作logo的位图结构体;
img2srec:转换S-Record格式映像;
envcrc:校验u-boot内部的环境变量;
mkimage:制作uImage格式映像;
gen_eth_addr:生成以太网接口MAC地址;
updater:u-boot自动更新升级工具;
4.4、u-boot编译过程分析
4.4.1、根据宿主机构建环境配置过程----分析make命令的执行过程
在u-boot顶层目录的Makefile文件的开题有一些与主机软硬件环境相关的代码,在每次执行make命令时,这些代码都被执行一次。
(1)定义主机的系统架构
u-boot顶层目录下的Makefile文件通过如下代码定义了主机的系统架构HOSTARCH。
“sed -e”表示后面跟的是一串命令脚本,而表达式“s/abc/def”表示要从标准输入中查找内容为“abc”的,然后替换成“def”。其中,“abc”表达式可以使用“.”作为通配符。
命令“uname -m”将会输出主机CPU的体系架构类型。如果使用X86型的CPU主机,执行“uname -m”命令后将输出i.86(其中“.”为通配符,i286、i386、i686等可以统一成i.86),“i.86”可以匹配命令“sed –e s/i.86/i386”中的“i.86”,因此,在该计算机上执行u-boot的Makefile,可以将主机的系统架构HOSTARCH设置成“i386”。
(2)定义宿主机操作系统的类型
u-boot顶层目录下的Makefile文件通过如下代码定义了宿主机操作系统的类型HOSTOS。
执行“uname -s”命令可以输出宿主机操作系统的内核名称。假如宿主机上使用的是Ubuntu 14.04系统,执行“uname -s”命令,将输出“Linux”字符串。“tr '[:upper:]' '[:lower:]'”的作用是将标准输入中的所有大写字母转换为相应的小写字母。因此,执行结果为将宿主机操作系统的类型HOSTOS设置为“linux”。
(3)定义shell脚本的解释器
u-boot顶层目录下的Makefile文件通过如下代码定义了shell脚本的解释器SHELL。
"$$BASH"的作用实质上是生成字符串"$BASH"(前一个$符号的作用是指明第二个$是普通的字符)。若当前Makefile的shell中定义了"$BASH"环境变量,且文件"$BASH"是可执行文件,则SHELL的值为"$BASH"。若"/bin/bash"是可执行文件,则SHELL的值为"/bin/bash"。若以上两条均不成立,则将"sh"赋值给SHELL变量。
(4)设定编译输出目录
u-boot顶层目录下的Makefile文件通过如下代码设定u-boot的编译输出目录。
在上面的代码中,第88行的"$(origin O)"表示执行"origin O"函数的输出。"$(origin variable)"的输出结果是一个字符串,由变量variable定义的方式决定,如果variable是在作为make命令的参数时定义的,则origin函数的返回值为" command line "。例如,执行”make O=/tmp/build all”命令时,"$(origin O)"的值为"command line",而BUILD_DIR将被设置为"/tmp/build "
在u-boot顶层目录下的Makefile文件的接下来的代码中,98行表示若${BUILD_DIR}表示的目录不存在,则创建该目录。101行和102行表示若${BUILD_DIR}为空,则将其值赋为当前目录的路径(即u-boot的顶层目录),并检查${BUILD_DIR}目录是否存在。
在u-boot顶层目录下的Makefile文件的接下来的代码中,上述代码用来确定与u-boot源码目录和输出目录相关的变量。CURDIR变量表示执行make命令的工作目录,由于make命令在u-boot顶层目录执行,因此,CURDIR变量的值就是u-boot的顶层目录。源码目录SRCTREE和src也就是u-boot的顶层目录。而输出目录OBJTREE和obj就是BUILD_DIR所指定的目录。若没有定义BUILD_DIR环境变量,则OBJTREE和obj都是u-boot的顶层目录。第111行的MKCONFIG表示u-boot顶层目录下的mkconfig脚本。
4.4.2、与目标板相关的配置过程----分析make smdk2410_config命令的执行过程
make <board_name>_config命令的执行过程即为具体开发板(目标板)的相关配置过程。(主要分析将编译目标输出到u-boot顶层目录的情况)
u-boot顶层目录下的Makefile文件中存在与具体的开发板相关的配置规制,如smdk2410的配置。
其中的依赖”unconfig”的定义如下:
第500行的“@”的作用是执行该命令时不在shell中显示。“obj”变量就是u-boot编译的输出目录,因为此依赖”unconfig”的作用是清除上次make *_config命令生成的配置文件(如/include/config.h和/include/config.mk等)。
第3045行其实涉及到一个语法知识。$(@:_config=)表示将传进来的所有参数中的_config替换为空,其中“@”是指规则的目标,在这里就是“smdk2410_config”。语法知识:$(text:patternA=patternB)这样的语法表示把text变量中的每一个元素中结尾的patternA文本替换为patternB,然后输出。因此,$(@:_config=)的作用就是将传进来的参数“smdk2410_config”中的_config去掉(替换为空),得到smdk2410。
由此可见,第3045行代码实际上执行了如下命令:
#./mkconfig smdk2410 arm arm920t smdk2410 samsung s3c24x0
即将smdk2410、arm、arm920t、smdk2410、samsung、s3c24x0作为6个参数传递给当前目录(u-boot的顶层目录)下的mkconfig文件。
在u-boot的顶层目录下mkconfig文件中,给出了mkconfig的用法:
u-boot的顶层目录下Makefile文件传递给mkconfig文件的6个参数的含义如下:
①smdk2410——Target(目标板型号)
②arm——Architecture(目标板的CPU架构)
③arm920t——CPU(具体使用的CPU型号)
④smdk2410——Board(开发板名称)
⑤samsung——VENDOR(生产厂家名)
⑥s3c24x0——SOC(片上系统)
在u-boot的顶层目录下mkconfig文件究竟做了什么?
(1)确定开发板名称
在u-boot的顶层目录下mkconfig文件中有如下一段代码:
在上述代码中,环境变量$#表示传递给mkconfig脚本的参数的个数,这里的命令有6个参数,因此$#=6。shift的作用是使$1=$2,$2=$3,$3=$4……,而原来的$1将丢失。while循环的作用是以此处理传递给mkconfig脚本的选项,由于并没有传递给mkconfig脚本任何选项,因此while循环中的代码不起作用。第25行代码将BOARD_NAME的值设置为$1,在这里就是“smdk2410”。
(2)检查参数的合法性
在u-boot的顶层目录下mkconfig文件中有如下一段代码用来检查参数的个数和参数是否正确。
在上述代码中,当参数个数小于4个或大于6个都被认为是错误的。
(3)创建到目标板相关目录的链接
若将u-boot的编译目标输出到外部目录(在4.4.1中“设定编译输出目录”中有讲述),则mkconfig文件中下列代码有效:
若将u-boot的编译输出目录设定为u-boot源文件所在的目录(即,u-boot的顶层目录),则会在u-boot的顶层目录中的include目录下建立到asm-arm目录的符号链接asm,mkconfig文件中的源代码如下:其中ln -s asm-$2 asm命令中$2代表传递给mkconfig文件的第二个参数arm,则该命令实际为:ln -s asm-arm asm。
接着u-boot的顶层目录中的mkconfig文件通过下面的代码建立符号链接include/asm-arm/arch。
在上述代码中,若$6(SOC)为NULL,则是其链接到/include/asm-arm/arch-arm920t目录,否则使其链接到/include/asm-arm/arch-s3c24x0目录。(事实上,/include/asm-arm/arch-arm920t目录并不存在,因此$6不能为空,否则编译会失败!)
若目标板是arm架构,则上面的代码将建立符号链接/include/asm-arm/proc,使其链接到proc-armv目录。
小结:建立以上链接的好处是:编译u-boot时,直接进入链接文件指向的目录进行编译,而不用根据不同的开发板来选择不同目录。
(4)构建include/config.mk文件
在u-boot的顶层目录中的mkconfig文件中,构建include/config.mk文件的代码如下:
上面的代码会将如下内容写入文件/include/config.mk文件中
ARCH =arm
CPU =arm920t
BOARD =smdk2410
VENDOR =samsung
SOC =s3c24x0
(5)构建/include/config.h文件
u-boot的顶层目录中的mkconfig文件中最后一部分代码如下:
在上述代码中,若APPEND为no,则创建新的/include/config.h文件。若APPEND为yes,则将新的配置内容追加到/include/config.h文件的后面。由于APPEND保持值为no,因此/include/config.h文件被创建,并添加如下内容:
/* Automatically generated - do not edit */
#include <configs/$1.h>即#include <configs/smdk2410.h>
#include <asm/config.h>
下面总结执行“make smdk2410_config”命令的结果(针对编译输出目录为u-boot顶层目录的情况)
1)创建到目标板相关文件的链接,也就是执行如下命令
,即ln –s asm-arm asm
,即ln –s arch-s3c24x0 asm-arm/arch
,即ln –s proc-armv asm-arm/proc
2)创建include/config.mk文件,内容如下:
ARCH =arm
CPU =arm920t
BOARD =smdk2410
VENDOR =samsung
SOC =s3c24x0
3)创建与目标板相关的头文件include/config.h,具体内容如下:
/* Automatically generated - do not edit */
#include <configs/$1.h>即#include <configs/smdk2410.h>
#include <asm/config.h>
4.4.3、make命令的执行过程
若没有执行“make <board_name>_config”命令就直接执行“make”命令,则会出现如下错误信息,然后停止编译。
System not configured - see README
那么u-boot在编译时是如何知道用户没有执行“make <board_name>_config”命令的呢?u-boot顶层目录下的Makefile文件中有如下几段代码:
若include/config.mk文件存在,则$(wildcard $(obj)include/config.mk)命令的执行结果是文件“$(obj)include/config.mk”展开的字符串,否则结果为空。由于include/config.mk文件是在执行make <board_name>_config命令的过程中生成的,若没有执行make <board_name>_config命令,则include/config.mk文件就不存在,因此make执行else分支代码,在输出“System not configured - see README
”信息后返回。
下面来分析make命令正常执行并最终生成u-boot镜像的过程。
(1)include/autoconf.mk文件的生成过程
在make命令正常执行的过程中,在u-boot的顶层目录下的Makefile文件中会有如下代码来包含如下头文件。
include/autoconf.mk文件中存放的是与开发板相关的一些宏定义。在make命令执行的过程中,需要根据这些宏定义来确定执行哪些操作。下面简要分析include/autoconf.mk文件的生成过程,Makefile文件下有include/autoconf.mk文件的生成规则:
include/autoconf.mk文件依赖于make <board_name>_config命令生成的include/config.h,因此执行make <board_name>_config命令后再执行make命令将更新include/autoconf.mk文件的内容。
编译选项-dM的作用是输出include/common.h文件中定义的所有宏。根据上面的规则,编译器提取include/common.h文件中定义的宏,然后输出给tools/scripts/define2mk.sed脚本处理,处理结果就是include/autoconf.mk文件。其中tools/scripts/define2mk.sed脚本主要完成了在include/common.h文件中查找和处理以“CONFIG_”开头的宏定义。
include/common.h文件包含了include/config.h文件,而include/config.h文件又包含了configs/smdk2410.h、asm/config.h文件,事实上include/common.h文件中的宏定义在这两个文件中,因此,include/autoconf.mk文件实质上就是对文件configs/smdk2410.h、asm/config.h中以“CONFIG_”开头的宏定义进行处理的结果。
接着分析Makefile文件的执行。
上述代码将执行make smdk2410_config命令生成的include/config.mk文件包含进来。
上述代码表示,若主机架构与开发板架构相同,则使用主机的编译器而不使用交叉编译器。
上述代码表示将u-boot的顶层目录下的config.mk文件包含进来,该文件包含了对编译的一些设置。
(2)u-boot的顶层目录下的config.mk文件的执行过程
①设置obj与src
在u-boot的顶层目录下的config.mk文件中有如下代码:
由于目标输出到源代码目录下(即u-boot的顶级目录),因此执行完上述代码后,obj与src都为空。
②设置编译选项
u-boot的顶层目录下的config.mk文件中有如下代码:
上述代码中有3个变量表示交叉编译器的编译选项。执行make命令时会检查交叉编译器支持的编译选项,然后将适当的选项添加到这3个变量中。
u-boot的顶层目录下的config.mk文件中有如下代码:
变量CC和CFLAGS在config.mk文件后面的代码中有定义,其中CC对于ARM平台就是arm-linux-gcc。函数cc-option用于检查编译器CC是否支持某选项。参数$1和$2是传递给cc-option函数的两个参数,函数cc-option调用CC编译器检查是否支持参数1($1)表示的选项,若支持,则返回参数1($1)表示的选项,否则返回参数2($2)表示的选项(因此,CC编译器必须支持参数1或参数2表示的选项,若两个都不支持,则编译出错)。
③指定交叉编译工具
在u-boot的顶层目录下的config.mk文件中,指定交叉编译工具的代码如下:
对于ARM开发板,上面代码中的CROSS_COMPILE是在lib_arm/config.mk文件中第24行定义的。
这一行代码指定了使用前缀为“arm-linux-”的编译工具,即arm-linux-gcc、arm-linux-ld等。
④包含与开发板相关的配置文件
u-boot的顶层目录下的config.mk文件中有如下代码:
在上面的代码中,“$(ARCH)”的值是“arm”,因此就将lib_arm/config.mk包含进来。lib_arm/config.mk文件中第24行定义了交叉编译器的前缀,还添加了一些与CPU架构相关的编译选项,最后还指定了cpu/arm920t/u-boot.lds为u-boot的链接脚本。
u-boot的顶层目录下的config.mk文件中有如下代码:
在上面的代码中,$(CPU)的值是“arm920t”,因此,上述代码将cpu/arm920t/config.mk包含了进来,这个脚本主要设定了与arm920t处理器相关的编译选项。
u-boot的顶层目录下的config.mk文件中有如下代码:
在上面的代码中,$(SOC)的值是s3c24x0,因此make命令尝试将cpu/arm920t/s3c24x0/config.mk包含进来,但是这个文件并不存在,但是因为使用的是sinclude命令,因此并不会报错。
u-boot的顶层目录下的config.mk文件中有如下代码:
上述代码中,$(BOARD)的值是smdk2410,VENDOR的值为samsung,因此BOARDDIR的值是samsung/smdk2410。BOARDDIR变量表示开发板特有的代码所在的目录。
u-boot的顶层目录下的config.mk文件中有如下代码:
上述代码将board/samsung/smdk2410/config.mk包含进来。board/samsung/smdk2410/config.mk脚本只有一行代码。编译u-boot时将使用TEXT_BASE作为代码段连接的起始地址。
(board/samsung/smdk2410/config.mk脚本的一行代码)
u-boot的顶层目录下的config.mk文件中有如下代码:
上述代码用来设置连接器。执行完该代码后,LDFLAGS中包含了-Bstatic -T u-boot.lds和-Ttext 0x33F80000的字样。
⑤指定隐含的编译规则
u-boot的顶层目录下的config.mk文件中有如下代码:
例如,根据上述代码,以“.s”结尾的目标文件将根据第一条规则由同名但后缀为“.S”的源文件生产,若不存在以“.S”结尾的同名文件,则根据最后一条规则,由同名的“.c”文件生产。
(3)u-boot镜像的生成过程
u-boot顶级目录下的Makefile文件中有如下代码:
上述代码定义了LIBS变量,用以指明u-boot需要的库文件,包括平台/开发板相关的目录、通用目录下相应的库,它们都是通过相应的子目录编译得到的。
LIBS变量中与平台相关的库有一下几个:
cpu/$(CPU)/start.o
board/$(VENDOR)/common/lib$(VENDOR).a
cpu/$(CPU)/lib$(CPU).a
cpu/$(CPU)/$(SOC)/lib$(SOC).a
lib_$(ARCH)/lib$(ARCH).a
u-boot顶级目录下的Makefile文件中有如下代码:
上述代码定义了Makefile文件第一目标all的依赖,对于smdk2410、U_BOOT_NAND与U_BOOT_ONENAND为空,而u-boot.srec、u-boot.bin、System.map都依赖于u-boot,因此执行make命令时将生成u-boot、u-boot.srec、u-boot.bin、System.map。其中u-boot是ELF文件,u-boot.srec是Motorola S-Record format文件,System.map是u-boot的符号表,u-boot.bin是可以直接烧写到开发板中运行的二进制文件。
u-boot顶级目录下的Makefile文件中有如下代码:
上述代码定义了ELF格式的u-boot文件的生成规则。这里生成的$(obj)u-boot目标就是ELF格式的u-boot文件。由于CONFIG_KALLSYMS未定义,因此ifeq ($(CONFIG_KALLSYMS),y)与endif间的代码不起作用。其中depend、$(SUBDIRS)、$(OBJS)、$(LIBBOARD)、$(LIBS)、$(LDSCRIPT)和$(obj)u-boot.lds是$(obj)u-boot的依赖,而$(GEN_UBOOT)是编译命令。
下面分析$(obj)u-boot的各个依赖
- depend依赖
u-boot顶级目录下的Makefile文件中有如下代码:
以上代码定义了生成depend依赖的规则。执行该规则将依次进入$(SUBDIRS)表示的子目录中,并执行“make _depend”命令,生成各个子目录的.depend文件,在.depend文件中列出每个目标文件的依赖文件。
②$(SUBDIRS)依赖
u-boot顶级目录下的Makefile文件中有如下代码:
以上代码定义了变量SUBDIRS。$(SUBDIRS)的依赖规则如下,这表示生成它的方式是执行tool、examples/standalone、examples/api目录下的Makefile。
(u-boot顶级目录下的Makefile文件中)
③$(OBJS)依赖
u-boot顶级目录下的Makefile文件中有如下代码:
编译以上代码得到变量OBJS的值是“cpu/arm920t/start.o”。以上代码表明,$(OBJS)表示的目标文件都是通过进入cpu/$(CPU)目录(即cpu/arm920t)后执行make命令编译得到的。
④$(LIBBOARD)依赖
u-boot顶级目录下的Makefile文件中有如下代码:
根据以上代码,对于smdk2410开发板,LIBBOARD的值是$(obj)board/samsung/smdk2410/libsmdk2410.a。
以上u-boot顶层目录中Makefile文件中的代码规定了$(LIBBOARD)依赖的生成规则,对于smdk2410,它表示进入board/samsung/smdk2410/目录中,并执行该目录下的Makefile文件,生成libsmdk2410.a文件。
⑤$(LIBS)依赖
u-boot顶级目录下的Makefile文件中有如下代码:
上述代码定义了变量LIBS中每个元素的编译规则,该规则表明,LIBS表示的每个库文件,都是由进入相应的子目录后再执行“make”命令编译出来的。例如,对于LIBS中的“common/libcommon.a”成员,程序将进入commom目录执行make命令,生成libcommon.a文件。
⑥$(LDSCRIPT)依赖
LDSCRIPT变量的值是在lib_arm/config.mk文件中定义的,代码如下:
(lib_arm/config.mk文件中的代码)
在u-boot顶层目录下的Makefile文件中,有如下代码:
以上代码定义了生成$(LDSCRIPT)依赖的规则。对于smdk2410评估板,$(MAKE) -C $(dir $@) $(notdir $@)命令被转换成make -C cpu/arm920t u-boot.lds,也就表示跳到cpu/arm920t/目录下,执行make u-boot.lds命令。
⑦$(obj)u-boot.lds依赖
在u-boot顶层目录下的Makefile文件中,有如下代码:
以上代码定义了$(obj)u-boot.lds依赖的生成规则,其执行结果是将cpu/arm920t/u-boot.lds文件经过编译器简单处理后输出到u-boot顶层目录下的u-boot.lds文件中。其中cpu/arm920t/u-boot.lds文件的内容如下:
cpu/arm920t/u-boot.lds文件决定了生成的u-boot镜像中所以.o文件和.a文件的链接地址,其各行代码的含义如下:
32行:指定输出可执行文件的32位ARM指令,小端模式的ELF格式;
33行:指定输出可执行文件的平台为ARM;
34行:指定程序的入口为_start;
37行:指明目标代码的起始地址从0x0位置开始,“.”代表当前位置;
39行:表示此处4字节对齐;
42行:表示start.o是代码段的第一个.o文件;
43行:表示这是代码段的其余部分;
47行:这是只读数据段;
50行:这是数据段;
61行:表示_bss_start标号指向bss段的开始位置;
62行:这是bss段。
说明:BSS段通常是指用于存放程序中未初始化的全局变量的一块内存区域。BSS是英文字母Block Started by Symbol的缩写,该段中的变量在使用前由系统初始化为0。
在u-boot顶层目录下的Makefile文件中,有如下代码:
以上代码定义了编译生成u-boot镜像的命令GEN_UBOOT。第340行命令中间的“;”可将该命令分成两个部分,第一部分如下:
UNDEF_SYM=`$(OBJDUMP) -x $(LIBBOARD) $(LIBS) | \
sed -n -e 's/.*\($(SYM_PREFIX)__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`
替换相应的变量后,相当于执行下面的指令:
UNDEF_SYM=`arm-linux-objdump -x board/samsung/smdk2410/libsmdk2410.a ... | \
sed -n -e 's/.*\__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`
该命令将编译u-boot所生成的库中包含__u_boot_cmd_的所有标号,替换为-u__u_boot_cmd_形式的标号,排序(sort),并保证唯一(unique),然后将所有由这样的标号组成的字符串幅值给UNDEF_SYM。
第二部分指令如下,主要功能是连接产生u-boot镜像。
cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) \
--start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
-Map u-boot.map -o u-boot
其中,LNDIR就是OBJTREE,LD和LDFLAGS的值在前面也提及过,替换后,上述命令相当于如下代码:
cd $(OBJTREE) && arm-linux-ld –Bstatic –T u-boot.lds –Ttext 0x33F80000 $UNDEF_SYM cpu/arm920t/start.o --start-group lib_generic/libgeneric.a ...board/samsung/smdk2410/libsmdk2410.a --end-group ... -Map u-boot.map -o u-boot
其中,–Bstatic表示使用静态方式链接库文件,–T u-boot.lds表示使用u-boot.lds作为连接器脚本,–Ttext 0x33F80000指定代码段的起始地址为TEXT_BASE(0x33F80000),--start-group ... --end-group指定了一组需要链接的库文件,Map u-boot.map表示产生符号表u-boot.map。
编译出u-boot镜像后,可以用file命令查看它的格式,发现它是ELF格式的文件。ELF格式的文件如果没有解析加载器是无法直接运行的,因此在u-boot顶层目录下的Makefile文件中有如下命令来讲ELF格式的u-boot文件转换成二进制格式的u-boot文件:
该命令展开后就是:
arm-linux-objcopy—gap-fill=0xff –O binary u-boot u-boot.bin
其中,-O binary选项指定输出的文件为二进制文件,而—gap-fill=0xff选项指定选用“0xff”填充段与段之间的空闲区域。执行该条命令后生成的u-boot.bin镜像文件就可以直接烧写到开发板中运行。
u-boot常用的命令(范展源P18页)