【转载】u-boot的makefile体系分析

 原文:http://www.wowotech.net/u-boot/375.html

时间 2017-02-16 16:42:49  蜗窝科技


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内核,主要分为以下几个部分:

【转载】u-boot的makefile体系分析_第1张图片

图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。

【转载】u-boot的makefile体系分析_第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"。部分截图如下:

【转载】u-boot的makefile体系分析_第3张图片

图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依赖关系图。

【转载】u-boot的makefile体系分析_第4张图片

图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所示。

【转载】u-boot的makefile体系分析_第5张图片

图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所示。

【转载】u-boot的makefile体系分析_第6张图片

图1-7 编译目录

【转载】u-boot的makefile体系分析_第7张图片

图1-8 通用规则编译示例

图1-8中的绿色框即是源文件了,通过源文件一步步即可构建出顶层目标。

图1-8看起来有点冗繁,图1-9看起来更简洁。

【转载】u-boot的makefile体系分析_第8张图片

图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所示。

【转载】u-boot的makefile体系分析_第9张图片

图2-1 测试的工程目录结构

该工程由3个目录构成:app、lib1、lib2。其中lib2目录下还有另一个源码子目录:sub-func/。

Makefile和工程源码可自行下载参考,在此不再敖述。编译执行效果如图2-2所示。

【转载】u-boot的makefile体系分析_第10张图片

如2-2 编译执行结果

清除命令的执行效果如图2-3所示。

【转载】u-boot的makefile体系分析_第11张图片

图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的一个最重要、最基本的功能,除此之外,它还有很多高大上的功能未分析。

你可能感兴趣的:(转载)