上一篇帖子翻译和分析了下README,这篇帖子打算分析一下Makefile,分析过程参照韦东山的视频,以及网上一些帖子,在此对他们的工作表示感谢。
首先,什么是Makefile?我们知道u-boot下面有很多的目录和文件,最终生成一个u-boot.bin需要编译进去很多的文件,哪些先编译,哪些后编译,用什么编译器等等,我们在编译的时候只输入了make这个指令,但其实做了很多事,这些事是谁做的,就是Makefile,它这些文件有序的串联起来,组织它们之间的关系,替我们管理了它们。Makefile有自己的语法规则,就像C和C++一样,可以极大的提高软件的开发效率。下面开始具体分析。
版本:U-BOOT-2014.04
编译u-boot第一步配置:make <boardname>_config
第二步编译:make
一.分析配置过程
我们在配置的是后是输入的make mini2440_config,在Makefile里面,我们看到这样的代码:
460行:%_config:: outputmakefile
@$(MKCONFIG) -A $(@:_config=)
当我们输入makemini2440_config的时候,Makefile就会匹配到%_config,%是通配符的意思,输入XXX_CONFIG就会匹配到%_config。
关于双冒号::和后面的outputmakefile,查了下gn make的中文手册,写的很多,我理解的是双冒号规则给我们提供一种根据依赖的更新情况而执行不同的命令来重建同一目标的机制。意思就是说在make的时候目标文件永远要是最新的,否则就会自己根据依赖规则重建目标文件。
$(MKCONFIG)代表着变量MKCONFIG的值,前面加@是代表在make时候不输出make信息。我们看到在Makefile中MKCONFIG的变量定义在第169行:
MKCONFIG :=$(srctree)/mkconfig
export MKCONFIG
这里可以看到,MKCONFIG在$(srctree)就是sourcetree下,有一个文件叫mkconfig,其实我们打开u-boot就可以看到mkconfig这个文件。
我们再来看看$(srctree)是怎么定义的,利用ue搜索,可以找到在第160行:
srctree :=$(if $(KBUILD_SRC),$(KBUILD_SRC),$(CURDIR))
objtree :=$(CURDIR)
src :=$(srctree)
obj :=$(objtree)
红色字体的意思是:如果我们定义了KBUILD_SRC,那srctree=KBUILD_SRC,如果没有定义,那么srctree=CURDIR。那我们再次搜索KBUILD_SRC,发现在第102行:
# KBUILD_SRC is set on invocation of make in OBJdirectory
# KBUILD_SRC is not intended to be used by the regularuser (for now)
ifeq ($(KBUILD_SRC),)
# OK, Make called in directory where kernel srcresides
# Do we want to locate output files in a separatedirectory?
ifeq ("$(origin O)", "commandline")
KBUILD_OUTPUT:= $(O)
endif
上面这段话说的就是在make的时候使用make的时候加入参数make 0=/XX/XX目录,可以指定输出文件的目录,这个用法在第86行:
# kbuild supports saving output files in a separatedirectory.
# To locate output files in a separate directory twosyntaxes are supported.
# In both cases the working directory must be the rootof the kernel src.
# 1) O=
# Use "make O=dir/to/store/output/files/"
这里我们并未使用,所以srctree就是在当前目录。
回到上面,也就是说@$(MKCONFIG)实际上指的就是当前目录下的mkconfig文件。
后面,- A的意思暂时没找到
最后$(@:_config=)的解释其实就是代表mini2440,为什么呢?这里把_config替换成了空,原本@代表的目标:mini2440_config就变成了mini2440,也就是上面的整个语句变成了:./mkconfig –A mini2440.
二.分析mkconfig文件
我们打开mkconfig文件之后,首先看到前6行的说明:
#!/bin/sh -e
# Script to create header files and links to configure
# U-Boot for a specific board.
#
# Parameters: Target Architecture CPU Board [VENDOR] [SOC]
其实功能说的很明白,第一行说此脚本使用/bin/sh来解释。
第三行说这是一个用来创建头文件和链接配置的脚本文件。
下面是说为u-boot的特定板子,意思是为自己的开发板配置时使用。
最后给出了参数:目标 架构 CPU 开发板[商家][SOC]
这些其实就是后面我们的一些列参数,所以我们在拿到一个工程文件的时候,可以多阅读以下这些开头的说明部分,可以加深我们对它的理解。
现在看24行第一部分代码:
if [ \( $# -eq 2 \) -a \( "$1" ="-A" \) ] ; then
# Automaticmode
line=`awk'($0 !~ /^#/ && $7 ~ /^'"$2"'$/) { print $1, $2, $3, $4, $5,$6, $7, $8 }' $srctree/boards.cfg`
if [ -z"$line" ] ; then
echo"make: *** No rule to make target \`$2_config'. Stop." >&2
exit1
fi
set ${line}
# adddefault board name if needed
[ $# = 3 ]&& set ${line} ${1}
fi
第一行里面,$# -eq 2的意思是参数的个数是不是两个,我们这里shell脚本语句还有以下的用法:
$0: shell或shell脚本的名字
$*:以一对双引号给出参数列表
$@:将各个参数分别加双引号返回
$#:参数的个数
$_:代表上一个命令的最后一个参数
$$:代表所在命令的PID
$!:代表最后执行的后台命令的PID
$?:代表上一个命令执行后的退出状态
参数是两个,符合要求,-a相当于C语言里面的&&就是与的意思,$1表示第一个参数,"$1" = "-A"表示输入的第一个参数是不是‘-A’,前面没找到-A的意思,难道是作为一个参数的校验位?先不管这个了。第一条语句的意思是是否是两个参数,并且第一个参数是-A,我们这里是符合的,那么久会执行:
line=`awk '($0 !~ /^#/ && $7 ~/^'"$2"'$/) { print $1, $2, $3, $4, $5, $6, $7, $8 }'$srctree/boards.cfg`
我们首先看看awk这个命令,awk本身其实是一个非常有效的处理多栏位的小型数据的工具,它的用法一般也是配合两个单引号加大括号并配合print指令输出。
($0 !~ /^#/ && $7 ~ /^'"$2"'$/)括号里面的意思,$0 !~ /^#/表示不处理以#开头的行,这样是避免注释部分被处理。$7 ~ /^'"$2"'$/这里表示第七栏必须等于输入的第二个参数,也就是mini2440。后面表示当前目录下的boards.cfg文件,我们打开看下。
这是我们初步移植的时候添加进去的,可以看到有这么一行,确实如此。
将其打印出来以后,也就是
line=Active arm arm920t s3c24x0 mini2440 - mini2440 -
一共八个参数,其中两个短破折号,第六个我没有填进去。这几个参数的意义我想不需要多解释,根据下面这一行对号入座:
1 2 3 4 5 6 7 8
# Status, Arch,CPU:SPLCPU, SoC, Vendor, Board name, Target, Options,
下面:
if [ -z"$line" ] ; then
echo"make: *** No rule to make target \`$2_config'. Stop." >&2
exit1
fi
说的是如果没有匹配到这一系列的字符串,就是line为空,那么就输出没有规则创建目标mini2440_config这条信息,所以我们还未移植的时候如果输入make mini2440_config就会出现这个报错信息。
再下面:
set ${line}
# adddefault board name if needed
[ $# = 3 ]&& set ${line} ${1}
set ${line}的意思就是上面讲各个变量对号入座,1等于什么,2等于什么这样子。本身set的含义是设置变量的新变量值,以及设置脚本的位置参数。
下面[ $# = 3] && set ${line} ${1}中的&&是先判断[ $# =3 ]是否成立,成立再执行后面的语句,这里我们的输入参数不是三个,所以后面的忽略。
下面37行:
while [ $# -gt 0 ] ; do
case"$1" in
--) shift ;break ;;
-a) shift ;APPEND=yes ;;
-n) shift ;BOARD_NAME="${7%_config}" ; shift ;;
-t) shift ;TARGETS="`echo $1 | sed 's:_: :g'` ${TARGETS}" ; shift ;;
*) break ;;
esac
done
[ $# -lt 7 ] && exit 1
[ $# -gt 8 ] && exit 1
这部分是说如果输入参数大于0个就进入循环,然后匹配第一个参数,是否等于--,-a,-n,-t,事实上这里我们是没有匹配到的,所以直接跳过去了。下面的话如果参数个数小于7个,退出,大于8个,退出。
第50行开始:
# Strip all options and/or _config suffixes
CONFIG_NAME="${7%_config}"
[ "${BOARD_NAME}" ] ||BOARD_NAME="${7%_config}"
arch="$2"
cpu=`echo $3 | awk 'BEGIN {FS = ":"} ;{print $1}'`
spl_cpu=`echo $3 | awk 'BEGIN {FS = ":"} ;{print $2}'`
if [ "$6" = "<none>" ] ;then
board=
elif [ "$6" = "-" ] ; then
board=${BOARD_NAME}
else
board="$6"
fi
[ "$5" != "-" ] &&vendor="$5"
[ "$4" != "-" ] &&soc="$4"
[ $# -gt 7 ] && [ "$8" !="-" ] && {
# check ifwe have a board config name in the options field
# theoptions field mave have a board config name and a list
# ofoptions, both separated by a colon (':'); the options are
# separatedby commas (',').
#
# Check forboard name
tmp="${8%:*}"
if ["$tmp" ] ; then
CONFIG_NAME="$tmp"
fi
# Check ifwe only have a colon...
if ["${tmp}" != "$8" ] ; then
options=${8#*:}
TARGETS="`echo${options} | sed 's:,: :g'` ${TARGETS}"
fi
}
if [ "${ARCH}" -a "${ARCH}" !="${arch}" ]; then
echo"Failed: \$ARCH=${ARCH}, should be '${arch}' for ${BOARD_NAME}"1>&2
exit 1
fi
其中:
CONFIG_NAME="${7%_config}"意思是说CONFIG_NAME=mini2440,这个%是截取符号,保留左边,删掉右边。
arch=”$2”,就是等于第二个参数arm
cpu=`echo $3 | awk 'BEGIN {FS = ":"} ;{print $1}'`
spl_cpu=`echo $3 | awk 'BEGIN {FS = ":"} ;{print $2}'`
以上两段的意思是,CPU:SPLCPU,分别等于第三个参数的第一个和第二个参数。
if [ "$6" = "<none>" ] ;then
board=
elif [ "$6" = "-" ] ; then
board=${BOARD_NAME}
else
board="$6"
fi
把第六个参数的值赋给board。
[ "$5" != "-" ] &&vendor="$5"
[ "$4" != "-" ] &&soc="$4"
如果第四个参数和第五个参数不等于短破折号,那就赋给vender和soc。
后面检查有没有第八个参数,以及arch参数是否匹配,可以忽略。
第93行开始:
if [ "${arch}" = "aarch64" ]; then
arch="arm"
fi
条件不成立,跳过,如果成立了还是会赋给arch=arm,这个aarch64是arm v8机构的64位指令集的内核,新添加进来的吧。
if [ "$options" ] ; then
echo"Configuring for ${BOARD_NAME} - Board: ${CONFIG_NAME}, Options:${options}"
else
echo"Configuring for ${BOARD_NAME} board..."
fi
如果设置了options项就输出*****,如果没有配置options也就是第八个参数,那就打印“Configuringfor ${BOARD_NAME} board...”,我们这里没有第八个参数,所以在make mini2440_config之后会看到这一行信息:
Configuring for mini2440 board...
三.分析mkconfig下建立软链接过程
第106行开始:
if [ -n "$KBUILD_SRC" ] ; then
mkdir -p${objtree}/include
LNPREFIX=${srctree}/arch/${arch}/include/asm/
cd${objtree}/include
mkdir -pasm
else
cdarch/${arch}/include
fi
rm -f asm/arch
if [ -z "${soc}" ] ; then
ln -s${LNPREFIX}arch-${cpu} asm/arch
else
ln -s${LNPREFIX}arch-${soc} asm/arch
fi
if [ "${arch}" = "arm" ] ; then
rm -fasm/proc
ln -s${LNPREFIX}proc-armv asm/proc
fi
if [ -z "$KBUILD_SRC" ] ; then
cd${srctree}/include
fi
分析:首先通过语句
if [ -n "$KBUILD_SRC" ] ; then
mkdir -p${objtree}/include
LNPREFIX=${srctree}/arch/${arch}/include/asm/
cd${objtree}/include
mkdir -pasm
else
cdarch/${arch}/include
fi
判断KBUILD_SRC是否为空,-n是判断字符串是否为空,非空为真,此处由于上面我们也提到的,没有指定KBUILD_SRC,所以这里为空,条件不成立,直接执行else后面的语句:
cd arch/${arch}/include
实际执行的是:
cd arch/arm/include
再下面:
rm -f asm/arch
关于这一句,韦东山视频里面一点而过,就说删除arch,还有网上的帖子里面说删除asm下arch目录,其实我觉得都是不对的,rm -rf才是删除目录,rm -f一般是删除文件,但是也可以删除软链接,所以,这里应该是先删除之前建立的软链接,我们打开之前编译过的u-boot的文件夹看到如下:
这里的arch是一个软链接,所以这句的意思是删除之前建立的软链接。还有一个proc下面会提到。
if [ -z"${soc}" ] ; then
ln -s${LNPREFIX}arch-${cpu} asm/arch
else
ln -s${LNPREFIX}arch-${soc} asm/arch
fi
-z是判断是否为空,这里我们的${soc}=s3c24x0,不为空,所以执行
ln -s ${LNPREFIX}arch-${soc} asm/arch
之前并未定义LNPREFIX,所以这里相当于执行:
ln -s arch-s3c24x0 asm/arch
也就是创建asm下arch软链接,到arch-s3c24x0,可以看到在编译好的u-boot下有这样的软链接。
if [ "${arch}" = "arm" ] ; then
rm -fasm/proc
ln -s${LNPREFIX}proc-armv asm/proc
fi
if [ -z "$KBUILD_SRC" ] ; then
cd${srctree}/include
fi
if里面的条件判断显然是成立的,这里${arch}=arm,同样删除asm下原来的软链接proc,再建立新的软链接proc到proc-armv,如下图。
最后判断判断KBUILD_SRC是否为空,显然是空的,那么进入到u-boot目录下的include目录。至此,软链接工作结束。有人问为什么要建立软链接,因为我们在源文件里面使用头文件的时候,不同的平台需要每次都更改源文件,比如include<arch-arm.h>,但是我们在pc上时就要改成include<arch-i386.h>,这样我们在根据不同平台编译的时候,进行软链接,在使用的时候我们的源文件就直接写成include<arch..h>,这样就具有更大的通用性和可移植性。
四.生成config.mk和头文件分析
第135行开始:
( echo "ARCH = ${arch}"
if [ ! -z "$spl_cpu" ] ; then
echo 'ifeq ($(CONFIG_SPL_BUILD),y)'
echo "CPU = ${spl_cpu}"
echo "else"
echo "CPU = ${cpu}"
echo "endif"
else
echo "CPU = ${cpu}"
fi
echo "BOARD = ${board}"
[ "${vendor}" ] && echo"VENDOR = ${vendor}"
[ "${soc}" ] && echo "SOC = ${soc}"
exit 0 ) > config.mk
这段话什么意思呢,先这样看(echo“*****” exit 0)>config.mk,其实就是说导出一些信息,到u-boot/include/config.mk文件里面,这里我们没有给出spl_cpu,所以很简单就是输出arch=arm,board=mini2440这样的信息,我们打开之前初步编译好的看到如下信息:
一目了然吧。
再下面:
if [ -z "${vendor}" ] ; then
BOARDDIR=${board}
else
BOARDDIR=${vendor}/${board}
fi
这里检查是否给出了vender部分,我在编译的时候给出了mini2440,所以不为空,这样BOARDDIR=mini2440/mini2440(当然你也可以写别的名字)
第161行:
if [ "$APPEND" = "yes" ] # Append to existing config file
then
echo>> config.h
else
>config.h # Create new configfile
fi
echo "/* Automatically generated - do not edit*/" >>config.h
for i in ${TARGETS} ; do
i="`echo${i} | sed '/=/ {s/=/ /;q; } ; { s/$/ 1/; }'`"
echo"#define CONFIG_${i}" >>config.h ;
done
echo "#define CONFIG_SYS_ARCH \"${arch}\"" >> config.h
echo "#define CONFIG_SYS_CPU \"${cpu}\"" >> config.h
echo "#define CONFIG_SYS_BOARD\"${board}\"" >> config.h
[ "${vendor}" ] && echo"#define CONFIG_SYS_VENDOR \"${vendor}\"" >> config.h
[ "${soc}" ] && echo "#define CONFIG_SYS_SOC \"${soc}\"" >> config.h
[ "${board}" ] && echo "#define CONFIG_BOARDDIR board/$BOARDDIR">> config.h
cat << EOF >> config.h
#include <config_cmd_defaults.h>
#include <config_defaults.h>
#include <configs/${CONFIG_NAME}.h>
#include <asm/config.h>
#include <config_fallbacks.h>
#include <config_uncmd_spl.h>
EOF
exit 0
这段首先判断append是否为空,显然为空,直接创建config.h文件,这个是在u-boot/include/目录下,打印"/* Automatically generated - do not edit*/" 到config.h的第一行。
for i in ${TARGETS} ; do
i="`echo${i} | sed '/=/ {s/=/ /;q; } ; { s/$/ 1/; }'`"
echo"#define CONFIG_${i}" >>config.h ;
done
这段话应该是根据目标板子的名称输出CONFIG_MINI2440到config.h,但是我的config.h里面没有。。。不知道为何。
在下面的语句就比较简单了,cat<< EOF >> config.h意思是把下面的内容添加到congfig.h里面,知道出现了EOF标记为止,程序最后确实有一个EOF。
看看config.h就一目了然了。
这样的话,整个mkconfig已经分析结束了,配置过程也已经分析结束。下面分析编译过程。
五.分析编译过程
我们从上到下分析一遍,当然有一些我认为不重要的,就略过,不清楚的地方我也会标出来,第8行:
VERSION = 2014
PATCHLEVEL = 04
SUBLEVEL =
EXTRAVERSION =
NAME =
这里就是指出了U-BOOT的版本号。2014.04版本。
第26行:
unexport LC_ALL
LC_COLLATE=C
LC_NUMERIC=C
export LC_COLLATE LC_NUMERIC
这部分具体含义不是很清楚,查了下,在shell中,LC_ALL是为了去除所有的本地化的设置,LC_COLLATE=C和LC_NUMERIC=C分别代表定义该环境的排序和比较规则以及非货币的数字显示格式。这里我理解应该是设定一下U-BOOT的语言环境。
第51行开始到114行:
ifeq ("$(origin V)", "commandline")
KBUILD_VERBOSE= $(V)
endif
ifndef KBUILD_VERBOSE
KBUILD_VERBOSE= 0
endif
类似这样的都是指在编译的时候有没有加什么命令行,比如make V=1之类的,一般用来指定模块以及生成的代码放在哪里,这里我们直接make。
第160行:
srctree :=$(if $(KBUILD_SRC),$(KBUILD_SRC),$(CURDIR))
objtree :=$(CURDIR)
src :=$(srctree)
obj :=$(objtree)
VPATH :=$(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD))
export srctree objtree VPATH
MKCONFIG :=$(srctree)/mkconfig
export MKCONFIG
指定srctree,objtree这些,这里没有定义KBUILD_SRC所以,srctree和objtree都是当前目录。
第177行:
HOSTARCH := $(shell uname -m | \
sed -es/i.86/x86/ \
-e s/sun4u/sparc64/ \
-e s/arm.*/arm/ \
-e s/sa110/arm/ \
-e s/ppc64/powerpc/ \
-e s/ppc/powerpc/ \
-e s/macppc/powerpc/\
-e s/sh.*/sh/)
HOSTOS := $(shell uname -s | tr '[:upper:]''[:lower:]' | \
sed -e 's/\(cygwin\).*/cygwin/')
export HOSTARCHHOSTOS
这部分使用shell的uname –m来获得machine名称,用uname –s来获得内核名称,再将其导出。也就是u-boot要知道自己下面是谁,上面是谁,哈哈。
sed -e是一个替换指令,可以将后面的替换成前面的。
第198行:
ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE? =
endif
CROSS_COMPILE=arm-linux-
这部分制定了交叉编译的工具前缀。
第204行:
CONFIG_SHELL := $(shell if [ -x "$$BASH" ];then echo $$BASH; \
else if [ -x /bin/bash ]; then echo/bin/bash; \
else echo sh; fi ; fi)
HOSTCC =gcc
HOSTCFLAGS =-Wall -Wstrict-prototypes -O2 -fomit-frame-pointer
这部分制定了我们的shell名称:/bin/bash,编译器套件名称:gcc,以及一些编译参数,-Wall表示要提示所有的warning。
第328行:
CC =$(CROSS_COMPILE)gcc
CPP =$(CC) -E
AR =$(CROSS_COMPILE)ar
NM =$(CROSS_COMPILE)nm
LDR =$(CROSS_COMPILE)ldr
STRIP = $(CROSS_COMPILE)strip
OBJCOPY =$(CROSS_COMPILE)objcopy
OBJDUMP =$(CROSS_COMPILE)objdump
AWK =awk
RANLIB =$(CROSS_COMPILE)RANLIB
DTC =dtc
CHECK =sparse
CHECKFLAGS := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ \
-Wbitwise -Wno-return-void -D__CHECK_ENDIAN__$(CF)
KBUILD_CPPFLAGS := -D__KERNEL__
KBUILD_CFLAGS := -Wall -Wstrict-prototypes \
-Wno-format-security \
-fno-builtin -ffreestanding
KBUILD_AFLAGS := -D__ASSEMBLY__
这里的功能和上部分基本相同,定义了一些工具还有参数,具体干嘛的不用细看。
第477行:
include $(srctree)/config.mk
包含了config.mk文件,之前着重分析过了,这里就是这样将其包含进去的。
第554行:
UBOOTINCLUDE := \
-Iinclude\
$(if$(KBUILD_SRC), -I$(srctree)/include) \
-I$(srctree)/arch/$(ARCH)/include
这个-Iinclude表示指定头文件的搜索路径,要是前面的I变成小写i,那就是指定链接库。这里就是指定头文件在/arch/arm/include下。
第570行:
head-y := $(CPUDIR)/start.o
这里是设置其目标文件,start.o要放在第一个,这里的start.o我理解的是/arch/arm/cpu/arm920t下的start.o不知道是否正确。
从676行开始到644行:
libs-y += lib/
libs-$(HAVE_VENDOR_COMMON_LIB) +=board/$(VENDOR)/common/
libs-y += $(CPUDIR)/
*********
libs-y :=$(patsubst %/, %/built-in.o, $(libs-y))
u-boot-init := $(head-y)
u-boot-main := $(libs-y)
这部分是将需要的初始化文件以及主要是库文件添加进去。
第699行开始到712行:
ALL-y += u-boot.srec u-boot.bin System.map
************
ALL-$(CONFIG_REMAKE_ELF) += u-boot.elf
这部分其实交代了最重要的三个目标文件:u-boot.srec u-boot.bin System.map,下面从725行到856行:
LDFLAGS_u-boot += $(LDFLAGS_FINAL)
ifneq ($(CONFIG_SYS_TEXT_BASE),)
LDFLAGS_u-boot += -Ttext $(CONFIG_SYS_TEXT_BASE)
endif
********
u-boot-signed.sb: u-boot.bin spl/u-boot-spl.bin
$(Q)$(MAKE)$(build)=arch/arm/cpu/arm926ejs/mxs u-boot-signed.sb
u-boot.sb: u-boot.bin spl/u-boot-spl.bin
$(Q)$(MAKE)$(build)=arch/arm/cpu/arm926ejs/mxs u-boot.sb
这里会根据配置执行make以及depend的依赖关系分别调用各子目录,生成所有的obj文件,具体生成过程水平有限啊。
第913行:
quiet_cmd_u-boot__ ?= LD $@
cmd_u-boot__ ?= $(LD) $(LDFLAGS) $(LDFLAGS_u-boot) -o $@ \
-T u-boot.lds $(u-boot-init) \
--start-group $(u-boot-main) --end-group \
$(PLATFORM_LIBS) -Map u-boot.map
u-boot: $(u-boot-init)$(u-boot-main) u-boot.lds
$(callif_changed,u-boot__)
ifeq ($(CONFIG_KALLSYMS),y)
smap=`$(callSYSTEM_MAP,u-boot) | \
awk'$$2 ~ /[tTwW]/ {printf $$1 $$3 "\\\\000"}'` ; \
$(CC)$(c_flags) -DSYSTEM_MAP="\"$${smap}\"" \
-c$(srctree)/common/system_map.c -o common/system_map.o
$(callcmd,u-boot__) common/system_map.o
endif
上面交代了u-boot是如何组装起来的,组装规则是u-boot.lds这个文件,,其实就是把start.o和各个子目录makefile生成的库文件按照LDFLAGS连接在一起,生成ELF文件u-boot 和连接时内存分配图文件u-boot.map。这里,我们的u-boot.bin文件我理解的是从u-boot.elf拷贝过来的。
剩下的u-boot的代码,对特定的平台和配置做了一些处理,最后清楚中间文件,配置文件的工作。
六.分析u-boot.lds文件
第1行开始:
OUTPUT_FORMAT("elf32-littlearm","elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
指定输出文件的格式为elf格式的,arm 32位指令,小端。
输出平台架构为arm
指定起始段的段名为_start
第4行到第19行:
SECTIONS
{
. = 0x00000000;// 指定可执行image文件的全局入口点,这里mini2440的flash起始地址为0x30008000,修改这里就可以。
. = ALIGN(4); //4字节对其方式
.text : //此处指代码段
{
*(.__image_copy_start)//印象文件复制的起始地址,未修改还是0x00000000
arch/arm/cpu/arm920t/start.o(.text*)//代码段第一部分
*(.text*) //此处存放其余部分的代码,后面会添加//从nand启动
}
. = ALIGN(4); //4字节对其方式
.rodata : {*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
以上存放只读数据段
. = ALIGN(4);
.data : { //可读写的已初始化的数据段
*(.data*)
}
第22行:
. = ALIGN(4);
.u_boot_list :{
KEEP(*(SORT(.u_boot_list*)));
}
. = ALIGN(4);
指定存放u-boot的命令段,在老的u-boot版本里,这部分是
u_boot_cmd: { *(.u_boot_cmd) }这样一段。
第51行:
.bss_start __rel_dyn_start (OVERLAY) : {
KEEP(*(.__bss_start));
__bss_base =.;
}
.bss __bss_base(OVERLAY) : {
*(.bss*)
. = ALIGN(4);
__bss_limit =.;
}
.bss_end__bss_limit (OVERLAY) : {
KEEP(*(.__bss_end));
}
上面指定了bss段的开始位置为当前值。并且指定了结束位置。
至此,分析全部结束,下面开始分析u-boot的启动阶段,阅读源码,不得不说这是个很蛮大的工程。自己的能力也比较有限,如有错误,还请指出来,共同进步。