原文:http://www.wowotech.net/u-boot/375.html
蜗窝科技
u-boot的makefile体系分析
目录
0 引言 1
1 U-boot的Makefile体系分析 1
1.1 makefile体系的结构 1
1.2 顶层makefile 1
1.2.1 _all依赖关系 1
1.2.2 u-boot依赖关系 3
1.3 通用规则 5
1.3.1 Makefile.build 5
1.3.2 Makefile.clean 6
1.3.3 Makefile.lib 6
1.3.4 Kbuild.include 6
1.3.5 小结 6
1.4 arch/$(ARCH)/Makefile 7
2 如何利用通用makefile规则库 7
2.1 功能需求 8
2.2 功能实现简述 8
2.3 使用简介 9
3 结语 9
0 引言
本人之前也一直有接触过u-boot,但是总感觉还有不甚了解的地方;通过拜读wowo之前发表的《u-boot分析》博文,让我对u-boot的认识更加深入,能够站在一个全局的角度来理解u-boot的体系结构。但唯有makefile方面还不能整体上理解;于是有分析u-boot的makefile的想法,最终形成此文。另外,linux内核的makefile与u-boot的makefile思路一致,掌握他们的构建方法,不仅对于研究linux内核有益,而且也可以作为日常使用。
1 U-boot的Makefile体系分析
根据readme,uboot的makefile可以分为5个组成部分,如1.1节所示。实际上,本人认为分为两个部分更简明扼要:
顶层makefile;
scrpits/目录下的通用规则。
顶层makefile总揽全局,决定需要编译的子目录;而通用规则依据各子目录的实际情况(子目录下的makefile配置)来决定编译内容。
1.1 makefile体系的结构
u-boot的makefile体系类似于linux内核,主要分为以下几个部分:
图1-1 makefile体系结构
顶层 Makefile
它是所有Makefile文件的核心,从总体上控制着内核的编译、连接
arch/$(ARCH)/Makefile
对应体系结构的Makefile,它用来决定哪些体系结构相关的文件参与内核的生成,并提供一些规则来生成特定格式的内核映像
scripts/Makefile.*
Makefile公用的通用规则、脚本等
子目录kbuild Makefiles
确切说,他们可能算不上makefile。各级子目录的Makefile相对简单,被上一层Makefile.build调用来编译当前目录的文件。
顶层.config
配置文件,配置内核时生成。所有的Makefile文件(包括顶层目录和各级子目录)都是根据.config来决定使用哪些文件的
1.2 顶层makefile
1.2.1 _all依赖关系
(1)默认目标依赖关系
顶层makefile中,首先找到的目标是_all伪目标:
PHONY := _all
_all:
然后_all目标又依赖于all:
PHONY += all
_all: all
all自身依赖于$(ALL-y):
all: $(ALL-y)
$(ALL-y)定义了多个目标:
# Always append ALL so that arch config.mk's can add custom ones
ALL-y += u-boot.srec u-boot.bin u-boot.sym System.map u-boot.cfg binary_size_check
ALL-$(CONFIG_ONENAND_U_BOOT) += u-boot-onenand.bin
ifeq ($(CONFIG_SPL_FSL_PBL),y)
ALL-$(CONFIG_RAMBOOT_PBL) += u-boot-with-spl-pbl.bin
else
ifneq ($(CONFIG_SECURE_BOOT), y)
ALL-$(CONFIG_RAMBOOT_PBL) += u-boot.pbl
endif
endif
ALL-$(CONFIG_SPL) += spl/u-boot-spl.bin
ALL-$(CONFIG_SPL_FRAMEWORK) += u-boot.img
ALL-$(CONFIG_TPL) += tpl/u-boot-tpl.bin
ALL-$(CONFIG_OF_SEPARATE) += u-boot.dtb
ifeq ($(CONFIG_SPL_FRAMEWORK),y)
ALL-$(CONFIG_OF_SEPARATE) += u-boot-dtb.img
endif
ALL-$(CONFIG_OF_HOSTFILE) += u-boot.dtb
ifneq ($(CONFIG_SPL_TARGET),)
ALL-$(CONFIG_SPL) += $(CONFIG_SPL_TARGET:"%"=%)
endif
ALL-$(CONFIG_REMAKE_ELF) += u-boot.elf
ALL-$(CONFIG_EFI_APP) += u-boot-app.efi
ALL-$(CONFIG_EFI_STUB) += u-boot-payload.efi
分析$(ALL-y)的定义:
a、ALL-y += u-boot.srec u-boot.bin u-boot.sym System.map u-boot.cfg binary_size_check是必选的;
b、ALL-$(xxxx)则是有条件才会选中。
另外。我们还可以看到spl的定义:
ALL-$(CONFIG_SPL) += spl/u-boot-spl.bin
在定义CONFIG_SPL的情况下,将构建u-boot-spl.bin,《X-003-UBOOT-基于Bubblegum-96平台的u-boot移植说明》即是这种情况。
本文只分析$(ALL-y)必选的目标:u-boot.srec u-boot.bin u-boot.sym System.map u-boot.cfg binary_size_check。
(2)u-boot.srec
u-boot.hex u-boot.srec: u-boot FORCE
$(call if_changed,objcopy)
u-boot.srec依赖于u-boot和FORCE。
(3)u-boot.bin
ifeq ($(CONFIG_OF_SEPARATE),y)
u-boot-dtb.bin: u-boot-nodtb.bin dts/dt.dtb FORCE
$(call if_changed,cat)
u-boot.bin: u-boot-dtb.bin FORCE
$(call if_changed,copy)
else
u-boot.bin: u-boot-nodtb.bin FORCE
$(call if_changed,copy)
endif
可以看到,当使能uboot的device tree时,将增加对 dts/dt.dtb 的依赖。
(4)u-boot.sym
u-boot.sym: u-boot FORCE
$(call if_changed,sym)
u-boot.sym也依赖于u-boot和FORCE。
(5)System.map
System.map: u-boot
@$(call SYSTEM_MAP,$<) > $@
u-boot.map依赖于u-boot
(6)u-boot.cfg
u-boot.cfg: include/config.h FORCE
$(call if_changed,cpp_cfg)
u-boot.cfg依赖于include/config.h。
(7)binary_size_check
binary_size_check: u-boot-nodtb.bin FORCE
… …
binary_size_check依赖于u-boot-nodtb.bin。
顶层makefile的这些依赖关系可以绘制为图1-2。
图1-2 u-boot顶层makefile依赖关系
1.2.2 u-boot依赖关系
u-boot: $(u-boot-init) $(u-boot-main) u-boot.lds FORCE
$(call if_changed,u-boot__)
ifeq ($(CONFIG_KALLSYMS),y)
$(call cmd,smap)
$(call cmd,u-boot__) common/system_map.o
endif
通常CONFIG_KALLSYMS等于空。
则u-boot依赖于:
$(u-boot-init) $(u-boot-main) u-boot.lds FORCE
其中$(u-boot-init)和$(u-boot-main)被定义为:
u-boot-init := $(head-y)
u-boot-main := $(libs-y)
(1)$(u-boot-init),即head-y
head-y是定义在arch/$(ARCH)/Makefile文件中,该文件被顶层makefile包含:
include arch/$(ARCH)/Makefile:
head-y := arch/arm/cpu/$(CPU)/start.o
(2)$(u-boot-main),即libs-y
在整个源码下搜索"libs-y",可以发现它在两个文件中定义:
顶层Makefile
arch/$(ARCH)/Makefile
首先定义的时候是类似"xxx/",然后经过一个函数加工为"xxx/built-in.o"。部分截图如下:
图1-3 libs-y部分内容
实际上,这些只是$(u-boot-init)和$(u-boot-main)的定义,而他们的依赖如下:
$(sort $(u-boot-init) $(u-boot-main)): $(u-boot-dirs) ;
继续看u-boot-dirs的定义:
u-boot-dirs := $(patsubst %/,%,$(filter %/, $(libs-y))) tools examples
u-boot-dirs定义就是$(libs-y)除去最后一个'/'后,再加上tools和examples。
而它的依赖为:
PHONY += $(u-boot-dirs)
$(u-boot-dirs): prepare scripts
$(Q)$(MAKE) $(build)=$@
(3)u-boot.lds
u-boot.lds: $(LDSCRIPT) prepare FORCE
$(call if_changed_dep,cpp_lds)
u-boot.lds的依赖稍微麻烦:
$(LDSCRIPT) prepare FORCE
$(LDSCRIPT)
ifndef LDSCRIPT
#LDSCRIPT := $(srctree)/board/$(BOARDDIR)/u-boot.lds.debug
ifdef CONFIG_SYS_LDSCRIPT
# need to strip off double quotes
LDSCRIPT := $(srctree)/$(CONFIG_SYS_LDSCRIPT:"%"=%)
endif
endif
# If there is no specified link script, we look in a number of places for it
ifndef LDSCRIPT
ifeq ($(wildcard $(LDSCRIPT)),)
LDSCRIPT := $(srctree)/board/$(BOARDDIR)/u-boot.lds
endif
ifeq ($(wildcard $(LDSCRIPT)),)
LDSCRIPT := $(srctree)/$(CPUDIR)/u-boot.lds
endif
ifeq ($(wildcard $(LDSCRIPT)),)
LDSCRIPT := $(srctree)/arch/$(ARCH)/cpu/u-boot.lds
endif
endif
该段脚本意思是优先使用LDSCRIPT;然后使用CONFIG_SYS_LDSCRIPT;最后再依次使用如下的lds文件:
$(srctree)/board/$(BOARDDIR)/u-boot.lds
$(srctree)/$(CPUDIR)/u-boot.lds
$(srctree)/arch/$(ARCH)/cpu/u-boot.lds
本人使用的zed板,就直接定义了CONFIG_SYS_LDSCRIPT="arch/arm/mach-zynq/u-boot.lds"。
prepare
该目标主要完成编译前的准备工作,其依赖关系如图1-4所示。
图1-4 prepare的依赖关系
FORCE
保证每次都重新生成目标。
(4)FORCE
保证每次都重新生成目标。
最后,可以归纳为图1-5所示的u-boot依赖关系图。
图1-5 u-boot依赖关系图
其中的prepare参考图1-4。
1.3 通用规则
顶层makefile是special的,是专为"u-boot"定制的,而script目录下的通用规则,则是makefile提炼出的核心:
Makefile.build
Makefile.clean
Makefile.lib
Kbuild.include
1.3.1 Makefile.build
被顶层Makefile所调用,与各级子目录的Makefile合起来构成一个完整的Makefile文件,定义built-in.o、.lib以及目标文件.o的生成规则。这个Makefile文件生成了子目录的.lib、built-in.o以及目标文件.o
通过图1-5,要构建u-boot的核心步骤是构建$(u-boot-dirs)目标。再看看它的构建规则:
$(u-boot-dirs): prepare scripts
$(Q)$(MAKE) $(build)=$@
$(Q)$(MAKE) $(build)=$@中,$(Q)可以忽略,主要看$(build),它定义在script/Kbuild.include文件中,它被顶层makefile包含:
build := -f $(srctree)/scripts/Makefile.build obj
因而$(Q)$(MAKE) $(build)=$@可展开为:
make -f ./scripts/Makefile.build obj=$@
意思是执行./scripts/Makefile.build,传递的参数为obj=$@。
首先看Makefile.build的默认目标:
PHONY := __build
__build:
__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
$(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
$(subdir-ym) $(always)
@:
由于在根makefile下:
KBUILD_MODULES :=
KBUILD_BUILTIN := 1
因此__build的依赖是:
__build: $(builtin-target) $(lib-target) $(extra-y) $(subdir-ym) $(always)
@:
这些依赖与传递进来的obj参数相关。
大概的依赖关系如图1-6所示。
图1-6 Makefile.build依赖关系示意图
从图1-6可知,目标最终,终于遍历到了源码文件。
Makefile.build总结:
首先根据传递进来的obj目录,包含obj目录下的Makefile或Kbuild(Kbuild优先)。通过该Makefile,获得obj-y/lib-y/extra-y变量值;
然后根据obj-y/lib-y/extra-y的值类型(xx.o或adc/),结合包含的Makefile.lib目录,对obj-y中的xxx/类型的目标,做变换获得:
obj-y := $(obj)/adc/built-in.o $(obj)/xx.o
subdir-ym := adc
最后回到Makefile.build,来构建builtin-target := $(obj)/built-in.o,它依赖于$(obj)/adc/built-in.o $(obj)/xx.o。
a、根据$(obj)/%.o目标,可以编译出$(obj)/xx.o;
b、根据$(subdir-ym)目标,将嵌套执行:
make -f ./scripts/Makefile.build obj=$(subdir-ym)
在该命令下,将会有obj-y=adc-uclass.o,然后编译出adc/adc-uclass.o,链接出adc/built-in.o;
c、在a、b步骤完成后,就得到了builtin-target所需的$(obj)/adc/built-in.o $(obj)/xx.o,进一步链接为$(obj)/built-in.o,完成链接工作。
1.3.2 Makefile.clean
被顶层Makefile所调用,用来删除目标文件等
1.3.3 Makefile.lib
被Makefile.build所调用,主要完成对一些变量的处理:
subdir-ym
将obj-y中的目录赋值给subdir-ym。该subdir-ym目标将嵌套调用Makefile.build。
obj-y
将obj-y中的xx.o目标添加目录前缀:xxx/xx.o
1.3.4 Kbuild.include
被Makefile.build所调用,定义了一些函数,如if_changed、if_changed_rule、echo-cmd
1.3.5 小结
对于图1-7所示的编译目录,各个通用规则的编译示例如图1-8所示。
图1-7 编译目录
图1-8 通用规则编译示例
图1-8中的绿色框即是源文件了,通过源文件一步步即可构建出顶层目标。
图1-8看起来有点冗繁,图1-9看起来更简洁。
图1-9 通用规则示例
1.4 arch/$(ARCH)/Makefile
该makefile被顶层makefile包含,主要作用是实现了head-y的赋值:
head-y := arch/arm/cpu/$(CPU)/start.o
head-y将被链接到特殊位置。
顺便还为顶层makefile的libs-y变量增加了值:
libs-y += arch/arm/cpu/$(CPU)/
libs-y += arch/arm/cpu/
libs-y += arch/arm/lib/
… …
这些变量全部为顶层makefile所使用。
2 如何利用通用makefile规则库
原计划是打算直接使用uboot的scripts/目录下的通用规则来编译我自己编写的简单c源码,可是折腾半天,发现挺复杂;倒不如依据前面对uboot的makefile体系的分析,重新编写一个更精简的makefile体系。
2.1 功能需求
(1)模仿uboot的编译模式:先在顶层makefile中指定需要编译的子目录(每个子目录构建一个built-in.o);然后在每个子目录下调用通用规则,生成该目录下的built-in.o。
(2)通用规则要满足以下功能:
a. 能自动遍历该子目录的子孙目录,并按秩序进行嵌套编译;
b. 可在该子目录的makefile里,进一步指定需要编译或不参加编译的源文件;
c. 同a,要能够嵌套的删除子孙目录下的生成的依赖文件、目标文件和built-in文件。
2.2 功能实现简述
依据上述的功能需求,参考uboot的makefile体系,编写了《my_prj3-v0.2》。
该实现基本按照uboot的makefile体系思路构成;不同之处是本人额外自己添加的自动生成目标文件的依赖关系,保证依赖能覆盖所有源文件和头文件。gcc的-MM选项再结合sed命令可实现上述功能。
测试的工程目录结构如图2-1所示。
图2-1 测试的工程目录结构
该工程由3个目录构成:app、lib1、lib2。其中lib2目录下还有另一个源码子目录:sub-func/。
Makefile和工程源码可自行下载参考,在此不再敖述。编译执行效果如图2-2所示。
如2-2 编译执行结果
清除命令的执行效果如图2-3所示。
图2-3 清除命令的执行结果
2.3 使用简介
以一个例子说明。
在2.2中的工程即是一个最简单的使用场景;如果需要增加lib3目录,并添加func3.c源文件,则需要做如下改动。
(1)顶层makefile的改动
在顶层makefile中增加:
libs-y += lib3/
(2)子目录makefile的改动
新建lib3/Makefile,内容如下:
obj-y += func3.o
如此即能将lib3目录添加到构建过程中来。
3 结语
本文首先分析了uboot的makefile,并详细描述了主要目标u-boot的依赖关系;然后依照uboot的思路,重新设计了一个简洁的makefile体系。
本文分析的内容是u-boot的makefile的一个最重要、最基本的功能,除此之外,它还有很多高大上的功能未分析。