U-Boot编译系统简要分析
注:本文的编写参考了以下网络文章:
1.U-Boot简介
在此不废话,相信大家都已经了解。
在此要说明的是,其源代码目录下的README对需要了解U-Boot及其移植方法的人有很大帮助。其中有说U-Boot的目录结构如下:
Directory Hierarchy:====================
/arch Architecture specific files
/arm Files generic to ARM architecture
/cpu CPU specific files
/arm720t Files specific to ARM 720 CPUs
/arm920t Files specific to ARM 920 CPUs
/at91 Files specific to Atmel AT91RM9200 CPU
/imx Files specific to Freescale MC9328 i.MX CPUs
/s3c24x0 Files specific to Samsung S3C24X0 CPUs
/arm925t Files specific to ARM 925 CPUs
/arm926ejs Files specific to ARM 926 CPUs
/arm1136 Files specific to ARM 1136 CPUs
/ixp Files specific to Intel XScale IXP CPUs
/pxa Files specific to Intel XScale PXA CPUs
/s3c44b0 Files specific to Samsung S3C44B0 CPUs
/sa1100 Files specific to Intel StrongARM SA1100 CPUs
/lib Architecture specific library files
/avr32 Files generic to AVR32 architecture
/cpu CPU specific files
/lib Architecture specific library files
/blackfin Files generic to Analog Devices Blackfin architecture
/cpu CPU specific files
/lib Architecture specific library files
/x86 Files generic to x86 architecture
/cpu CPU specific files
/lib Architecture specific library files
/m68k Files generic to m68k architecture
/cpu CPU specific files
/mcf52x2 Files specific to Freescale ColdFire MCF52x2 CPUs
/mcf5227x Files specific to Freescale ColdFire MCF5227x CPUs
/mcf532x Files specific to Freescale ColdFire MCF5329 CPUs
/mcf5445x Files specific to Freescale ColdFire MCF5445x CPUs
/mcf547x_8x Files specific to Freescale ColdFire MCF547x_8x CPUs
/lib Architecture specific library files
/microblaze Files generic to microblaze architecture
/cpu CPU specific files
/lib Architecture specific library files
/mips Files generic to MIPS architecture
/cpu CPU specific files
/mips32 Files specific to MIPS32 CPUs
/xburst Files specific to Ingenic XBurst CPUs
/lib Architecture specific library files
/nds32 Files generic to NDS32 architecture
/cpu CPU specific files
/n1213 Files specific to Andes Technology N1213 CPUs
/lib Architecture specific library files
/nios2 Files generic to Altera NIOS2 architecture
/cpu CPU specific files
/lib Architecture specific library files
/powerpc Files generic to PowerPC architecture
/cpu CPU specific files
/74xx_7xx Files specific to Freescale MPC74xx and 7xx CPUs
/mpc5xx Files specific to Freescale MPC5xx CPUs
/mpc5xxx Files specific to Freescale MPC5xxx CPUs
/mpc8xx Files specific to Freescale MPC8xx CPUs
/mpc8220 Files specific to Freescale MPC8220 CPUs
/mpc824x Files specific to Freescale MPC824x CPUs
/mpc8260 Files specific to Freescale MPC8260 CPUs
/mpc85xx Files specific to Freescale MPC85xx CPUs
/ppc4xx Files specific to AMCC PowerPC 4xx CPUs
/lib Architecture specific library files
/sh Files generic to SH architecture
/cpu CPU specific files
/sh2 Files specific to sh2 CPUs
/sh3 Files specific to sh3 CPUs
/sh4 Files specific to sh4 CPUs
/lib Architecture specific library files
/sparc Files generic to SPARC architecture
/cpu CPU specific files
/leon2 Files specific to Gaisler LEON2 SPARC CPU
/leon3 Files specific to Gaisler LEON3 SPARC CPU
/lib Architecture specific library files
/api Machine/arch independent API for external apps
/board Board dependent files
/common Misc architecture independent functions
/disk Code for disk drive partition handling
/doc Documentation (don't expect too much)
/drivers Commonly used device drivers
/examples Example code for standalone applications, etc.
/fs Filesystem code (cramfs, ext2, jffs2, etc.)
/include Header Files
/lib Files generic to all architectures
/libfdt Library files to support flattened device trees
/lzma Library files to support LZMA decompression
/lzo Library files to support LZO decompression
/net Networking code
/post Power On Self Test
/rtc Real Time Clock drivers
/tools Tools to build S-Record or U-Boot images, etc.
而其中的配置也分为两种,并详细介绍了各配置项:
There are two classes of configuration variables:
-
Configuration OPTIONS: These are selectable by the user and have names beginning with "CONFIG_".
-
Configuration SETTINGS: These depend on the hardware etc. and should not be meddled with if you don't know what you're doing; they have names beginning with "CONFIG_SYS_".
另外对U-boot的编译、移植方法及生成的映像的格式也作了介绍。
2.U-Boot的编译过程
- 编译步骤
根据U-Boot源代码README之介绍,U-Boot的编译分为两个步骤:
make NAME_CONFIG
make all
那么,这两部都做了什么操作呢,既然执行的是make命令,那么我们就不得不打开Makefile一探究竟了。 - Makefile初探
打开Makefile,我们首先是奔着make NAME_CONFIG来的,搜索xx_config发现,MAkefile里面只有为数不多的几个带xx_config的target,而有一个很特别:%_config。
根据《跟我一起写Makefile》p36之模式变量介绍:
- 在GNU的make中,还支持模式变量(Pattern-specific Variable ),模式变量的好处就是,我们可以给定一种“ 模式” ,可以把变量定义在符合这种模式的所有目标上。
我们知道,make的“ 模式” 一般是至少含有一个“%” 的,所以,我们可以以如下方式给所有以[.o] 结尾的目标定义目标变量: %.o : CFLAGS = -O
由此可见,只要看%_config的命令即可。
%_config依赖于unconfig,即配置之前先清除之前的配置,然后执行下面的命令:
@$(MKCONFIG) -A $(@:_config=)
事实上是执行了源代码根目录下之脚本,并把你传入的NAME_config去掉结尾的config传递过去( $(@:_config=) 参考《跟我一起写Makefile》p30之变量高级用法:
我们可以替换变量中的共有的部分,其格式是“$(var:a=b)” 或是“${var:a=b}” ,其意思是,把变量“var”中所有以“a”字串“ 结尾” 的“a”替换成“b”字串。这里的“ 结尾” 意思是“ 空格” 或是“ 结束符” 。*):
mkconfig -A NAME -
mkconfig详解
于是我们来到了mkconfig,shell脚本,哈哈,没什么好怕的了。
首先是在board.cfg中寻找你传进来的NAME的配置行,board.cfg的格式类似如下: smdk2410 arm arm920t - samsung s3c24x
第一个是配置文件名称,第二个是体系结构,第三个是cpu,第四个是board名称,若-,则board名称为去掉配置文件名_config后的部分。第五个是厂商vendor,第六个是soc的名称。如果有第七个参数则指定了选项,这里不做详细介绍。
得到这些参数之后干什么了呢?
仍以smdk2410为例,将arch目录下的inlcude/asm链接到include/asm:
cd ./include rm -f asm ln -s ../arch/${arch}/include/asm asm
将链接过来的asm目录里的arch目录删除,然后根据soc更新:
if [ -z "${soc}" ] ; then ln -s ${LNPREFIX}arch-${cpu} asm/arch else ln -s ${LNPREFIX}arch-${soc} asm/arch
如果是arm体系,还要更新asm目录下的proc目录:
if [ "${arch}" = "arm" ] ; then rm -f asm/proc ln -s ${LNPREFIX}proc-armv asm/proc fi
接着创建了include/config.mk文件,config.mk文件在后边编译时会被Makefile包含,并写入以下内容:
echo "ARCH = ${arch}" > config.mk echo "CPU = ${cpu}" >> config.mk echo "BOARD = ${board}" >> config.mk [ "${vendor}" ] && echo "VENDOR = ${vendor}" >> config.mk [ "${soc}" ] && echo "SOC = ${soc}" >> config.mk
在此vendor指没指定有一个很重要的区别,其board目录可能是vendor/board或者board:
if [ -z "${vendor}" ] ; then BOARDDIR=${board} else BOARDDIR=${vendor}/${board} fi
接下来生成include/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 cat << EOF >> config.h #define CONFIG_BOARDDIR board/$BOARDDIR #include <config_cmd_defaults.h> #include <config_defaults.h> #include <configs/${CONFIG_NAME}.h> #include <asm/config.h> #include <config_fallbacks.h> EOF
config.h包含了arch,cpu,board,vendor,soc,board_dir这些宏定义。
关于一开始的TARGETS循环,在boards.cfg中的选项字段即会转化为此处的宏定义,一可以使用-t传递参数给mkconfig。
至此,配置工作基本完成了,生成了include目录下的config.mk和config.h两个文件。
-
Makefile 二探
a.根据顶层README的介绍,编译U-Boot的第二个阶段就是
make all
于是再次打开Makefile
all: $(ALL-y) $(SUBDIR_EXAMPLES) $(obj)u-boot.srec: $(obj)u-boot $(OBJCOPY) -O srec $< $@ $(obj)u-boot.bin: $(obj)u-boot $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@ $(BOARD_SIZE_CHECK) $(SUBDIR_EXAMPLES): $(obj)u-boot ALL-y += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map
由此可见,编译的话首先是编译生成u-boot,然后objcopy出另外的两个镜像类型的文件。关键是u-boot的生成:
$(obj)u-boot: depend \ $(SUBDIR_TOOLS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds $(GEN_UBOOT)
b.$(SUBDIR_TOOLS)
SUBDIR_TOOLS = tools SUBDIRS = $(SUBDIR_TOOLS) $(SUBDIRS): depend $(MAKE) -C $@ all
因此SUBDIR_TOOLS是进入tools目录执行make命令(make -C tools)生成的。此目录的主要作用,就是会生成一系列的工具:
BIN_FILES-$(CONFIG_LCD_LOGO) += bmp_logo$(SFX) BIN_FILES-$(CONFIG_VIDEO_LOGO) += bmp_logo$(SFX) BIN_FILES-$(CONFIG_BUILD_ENVCRC) += envcrc$(SFX) BIN_FILES-$(CONFIG_CMD_NET) += gen_eth_addr$(SFX) BIN_FILES-$(CONFIG_CMD_LOADS) += img2srec$(SFX) BIN_FILES-$(CONFIG_XWAY_SWAP_BYTES) += xway-swap-bytes$(SFX) BIN_FILES-y += mkenvimage$(SFX) BIN_FILES-y += mkimage$(SFX) BIN_FILES-$(CONFIG_SMDK5250) += mksmdk5250spl$(SFX) BIN_FILES-$(CONFIG_MX28) += mxsboot$(SFX) BIN_FILES-$(CONFIG_NETCONSOLE) += ncb$(SFX) BIN_FILES-$(CONFIG_SHA1_CHECK_UB_IMG) += ubsha1$(SFX) BIN_FILES-$(CONFIG_KIRKWOOD) += kwboot$(SFX)
当然,最重要的是mkimage,还有bmp_logo等等,此处不详解了。
c.$(OBJS)
OBJS = $(CPUDIR)/start.o ifeq ($(CPU),x86) OBJS += $(CPUDIR)/start16.o OBJS += $(CPUDIR)/resetvec.o endif ifeq ($(CPU),ppc4xx) OBJS += $(CPUDIR)/resetvec.o endif ifeq ($(CPU),mpc85xx) OBJS += $(CPUDIR)/resetvec.o endif OBJS := $(addprefix $(obj),$(OBJS)) $(OBJS): depend $(MAKE) -C $(CPUDIR) $(if $(REMOTE_BUILD),$@,$(notdir $@))
由此可见,OBJS最最重要的就是进入CPUDIR编译出start.o了。
那么,CPUDIR是怎么定义的呢,顶层的Makefile中有:include $(obj)include/config.mk export ARCH CPU BOARD VENDOR SOC include $(TOPDIR)/config.mk
include/config.mk根据之前的解析是在make NAME_config时生成了,里面定义了ARCH,CPU,BOARD,VENDOR,SOC等这几个变量。
根据顶层目录下config之定义:
CPUDIR=arch/$(ARCH)/cpu/$(CPU) ifneq ($(SRCTREE)/$(CPUDIR),$(wildcard $(SRCTREE)/$(CPUDIR))) CPUDIR=arch/$(ARCH)/cpu endif
由此就进入了正确的某个CPU的目录并编译了正确start.o启动代码。
d.$(LIBBOARD)
LIBBOARD = board/$(BOARDDIR)/lib$(BOARD).o LIBBOARD := $(addprefix $(obj),$(LIBBOARD)) $(LIBBOARD): depend $(LIBS) $(MAKE) -C $(dir $(subst $(obj),,$@))
同理,顶层目录的config.mk对BOARDDIR的定义如下:
ifdef VENDOR BOARDDIR = $(VENDOR)/$(BOARD) else BOARDDIR = $(BOARD) endif
因此就是进入开发板的目录编译并链接,仍以smdk2410为例:
include $(TOPDIR)/config.mk LIB = $(obj)lib$(BOARD).o COBJS := smdk2410.o SOBJS := lowlevel_init.o SRCS := $(SOBJS:.o=.S) $(COBJS:.o=.c) OBJS := $(addprefix $(obj),$(COBJS)) SOBJS := $(addprefix $(obj),$(SOBJS)) $(LIB): $(obj).depend $(OBJS) $(SOBJS) $(call cmd_link_o_target, $(OBJS) $(SOBJS))
$(obj)lib$(BOARD).o依赖于smdk2410.o和lowlevel_init.o,编译吧,然后cmd_link_o_target进行链接。
cmd_link_o_target = $(if $(strip $1),\ $(LD) $(LDFLAGS) -r -o $@ $1,\ rm -f $@; $(AR) rcs $@ )
e.$(LIBS)
呃,这个是最麻烦的啦,哎。LIBS = lib/libgeneric.o LIBS += lib/lzma/liblzma.o LIBS += lib/lzo/liblzo.o LIBS += lib/zlib/libz.o ...... LIBS += $(CPUDIR)/lib$(CPU).o ifdef SOC LIBS += $(CPUDIR)/$(SOC)/lib$(SOC).o endif ...... LIBS += arch/$(ARCH)/lib/lib$(ARCH).o LIBS += fs/cramfs/libcramfs.o fs/fat/libfat.o fs/fdos/libfdos.o fs/jffs2/libjffs2.o \ fs/reiserfs/libreiserfs.o fs/ext2/libext2fs.o fs/yaffs2/libyaffs2.o \ fs/ubifs/libubifs.o LIBS += net/libnet.o LIBS += disk/libdisk.o LIBS += drivers/bios_emulator/libatibiosemu.o LIBS += drivers/block/libblock.o ......(省略N多驱动) LIBS += common/libcommon.o LIBS += lib/libfdt/libfdt.o LIBS += api/libapi.o LIBS += post/libpost.o ...... ifeq ($(SOC),s5pc1xx) LIBS += $(CPUDIR)/s5p-common/libs5p-common.o endif ifeq ($(SOC),exynos) LIBS += $(CPUDIR)/s5p-common/libs5p-common.o endif $(LIBS): depend $(SUBDIR_TOOLS) $(MAKE) -C $(dir $(subst $(obj),,$@))
虽然繁琐,但不复杂,就是编译N多个lib,分别进入其源目录执行make -C。
需要指出的是,LIBS += $(CPUDIR)/$(SOC)/lib$(SOC).o,即CPU目录的子目录是作为lib在此编译的,在编译start.o时并没有编译。
这样大部分的编译工作就完成了,CPU的初始化,板子的初始化,再到包含很多驱动的LIBS。
接下来就该链接了吧。
f. $(LDSCRIPT)
# If there is no specified link script, we look in a number of places for it ifndef LDSCRIPT ifeq ($(CONFIG_NAND_U_BOOT),y) LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot-nand.lds ifeq ($(wildcard $(LDSCRIPT)),) LDSCRIPT := $(TOPDIR)/$(CPUDIR)/u-boot-nand.lds endif endif ifeq ($(wildcard $(LDSCRIPT)),) LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds endif ifeq ($(wildcard $(LDSCRIPT)),) LDSCRIPT := $(TOPDIR)/$(CPUDIR)/u-boot.lds endif ifeq ($(wildcard $(LDSCRIPT)),) LDSCRIPT := $(TOPDIR)/arch/$(ARCH)/cpu/u-boot.lds # We don't expect a Makefile here LDSCRIPT_MAKEFILE_DIR = endif ifeq ($(wildcard $(LDSCRIPT)),) $(error could not find linker script) endif endif $(LDSCRIPT): depend $(MAKE) -C $(dir $@) $(notdir $@)
首先在N多个地方寻找链接脚本,找到之后,进入其目录执行make -Cg.$(obj)u-boot.lds
执行了一条命令:$(obj)u-boot.lds: $(LDSCRIPT) $(CPP) $(CPPFLAGS) $(LDPPFLAGS) -ansi -D__ASSEMBLY__ -P - <$^ >$@
将LDSCRIPT预处理下,然后生成$(obj)u-boot.ldsi.$(GEN_UBOOT)
这个简单,是结束,也是开始吧,仅仅一个命令。ifeq ($(CONFIG_SANDBOX),y) GEN_UBOOT = \ cd $(LNDIR) && $(CC) $(SYMS) -T $(obj)u-boot.lds \ -Wl,--start-group $(__LIBS) -Wl,--end-group \ $(PLATFORM_LIBS) -Wl,-Map -Wl,u-boot.map -o u-boot else GEN_UBOOT = \ UNDEF_SYM=`$(OBJDUMP) -x $(LIBBOARD) $(LIBS) | \ sed -n -e 's/.*\($(SYM_PREFIX)__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\ cd $(LNDIR) && $(LD) $(LDFLAGS) $(LDFLAGS_$(@F)) $$UNDEF_SYM $(__OBJS) \ --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \ -Map u-boot.map -o u-boot endif
因为编译已经完成,剩下的只是把编译出来的文件链接成u-boot。
3.总结
至此,U-Boot就编译完了。 呵呵,看起来总是比写起来简单,咱看人家写好的编译配置都这么麻烦,想来足见此工程之艰巨。 不管怎么说,还是让咱给大概看懂了。接下来就该是代码的事了吧,阅读代码,然后移植。。。