u-boot编译 Makefile 简单分析

 

u-boot 版本: 2016.03,NXP更改过的版本,正点原子再次更改,详细可参见正点原子的教程。

1. make xxx_defconfig的过程

从顶层Makefile开始,前面的变量赋值分析,可以参考《正点原子 I.MX6U嵌入式Linux驱动开发指南》

我们直接从上述正点原子的文档变量分析后的结果开始,得到:

config-targets = 1 
mixed-targets = 0 
dot-config = 1

根据上述变量进行ifeq等条件选择后,make xxx_defconfig匹配的规则如下(顶层Makefile第480行):

ifeq ($(config-targets),1)
# ===========================================================================
# *config targets only - make sure prerequisites are updated, and descend
# in scripts/kconfig to make the *config target

KBUILD_DEFCONFIG := sandbox_defconfig
export KBUILD_DEFCONFIG KBUILD_KCONFIG

config: scripts_basic outputmakefile FORCE
	$(Q)$(MAKE) $(build)=scripts/kconfig $@

%config: scripts_basic outputmakefile FORCE
	$(Q)$(MAKE) $(build)=scripts/kconfig $@

else

第480行的             %config: scripts_basic outputmakefile FORCE  目标

outputmakefile目标为空,scripts_basic目标如下(顶层Makefile 400行)

scripts_basic:
	$(Q)$(MAKE) $(build)=scripts/basic
	$(Q)rm -f .tmp_quiet_recordmcount

其中 build值如下(scripts/Kbuild.include文件中):

build=-f ./scripts/Makefile.build obj

 所以scripts_basic目标对应的命令展开为:

@make -f ./scripts/Makefile.build obj=scripts/basic

%config目标对应的命令展开如下:

@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

2.Makefile.build分析

上述scripts_basic 目标 命令和%config目标命令都会调用Makefile.build文件作为Makefile文件,但是指定了不同的obj变量和不同的目标,两条命令除了obj变量是专门指定以外,其他变量都是顶层makefile中export的变量,和顶层Makefile保持一致。

2.1.scripts_basic目标对应的命令

先分析@make -f ./scripts/Makefile.build obj=scripts/basic命令, 经Makefile.build文件前面的变量赋值,得到src:=scripts/basic,即和obj变量一样,prefix:=.  ,然后命令使用默认目标__build如下(116行):

__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
	 $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
	 $(subdir-ym) $(always)
	@:

KBUILD_BUILT=1和KBUILD_MODULES=0和顶层Makefile一致,所以,依赖的目标为:builtin-target、lib-target、extra-y、subdir-ym和 always,其中除了always = scripts/basic/fixdep其他变量都是空,

always赋值的位置是scripts/basic/Makefile,此文件引用的位置在Makefile.build的57行:

kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)

经分析,kbuild-dir:= ./scripts/basic,kbuild-file:=./scripts/basic/Makefile,最后include ./scripts/basic/Makefile

scripts/basic/Makefile中赋值语句如下:

hostprogs-y	:= fixdep
always		:= $(hostprogs-y)

这里,always := fixdep,那前缀在哪里加的呢?在scripts/Makefile.lib 中的78行

always		:= $(addprefix $(obj)/,$(always))

至此,得到always的值,always = scripts/basic/fixdep。

scripts/basic/fixdep这个软件是主机上运行的软件,需要Makefile将它编译出来,怎么编译的呢?

在上述scripts/basic/Makefile文件中,有一个变量hostprogs-y值为fixdep,将会在scripts/Makefile.host中被引用,此文件也会被include到scripts/Makefile.build中(84行),下面是scripts/Makefile.host中几个相关变量分析(26行):

__hostprogs := $(sort $(hostprogs-y) $(hostprogs-m))

# C code
# Executables compiled from a single .c file
host-csingle	:= $(foreach m,$(__hostprogs), \
			$(if $($(m)-objs)$($(m)-cxxobjs),,$(m)))

得到__hostprogs :=fixdep,host-csingle := fixdep (注意,这里可能不止是有fixdep,未深究,可以echo出来看一下),然后Makefile.host(93行)有如下规则,$(always)正好匹配上这条规则:

$(host-csingle): $(obj)/%: $(src)/%.c FORCE
	$(call if_changed_dep,host-csingle)

 关于if_changed_dep函数的分析详见:https://blog.csdn.net/linuxweiyh/article/details/100179968,这里只做简要分析:

位置在scripts/Kbuild.include文件(263行):

if_changed_dep = $(if $(strip $(any-prereq) $(arg-check) ),                  \
	@set -e;                                                             \
	$(echo-cmd) $(cmd_$(1));                                             \
	scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).tmp;\
	rm -f $(depfile);                                                    \
	mv -f $(dot-target).tmp $(dot-target).cmd)

这个函数只能在规则中调用,用于编译,并打印编译命令,以及将编译命令记录到.cmd文件(可以供开发者查看,另外里面有大量的dep规则,二次编译中将发挥作用)中,其中any-prereq变量和arg-check变量中用到了很多自动化变量,例如$@,$^,这里就不详细展开了,这里arg-check应该不为空,所以运行接下来的shell命令。其中set -e表示 接下来任何一条命令执行失败就退出;$(echo-cmd)用于输出命令到屏幕,$(cmd_$(1))才是真正发挥作用的,表示执行命令,这里我们传入的参数是host-csingle,所以$(cmd_$(1))展开后就是,$(cmd_host-csingle),这个变量定义在Makefile.host(91行):

quiet_cmd_host-csingle 	= HOSTCC  $@
      cmd_host-csingle	= $(HOSTCC) $(hostc_flags) -o $@ $< \
	  	$(HOST_LOADLIBES) $(HOSTLOADLIBES_$(@F))

这才是真正的编译生成scripts/basic/fixdep的命令,接着if_changed_dep函数中

scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).tmp;

表示fixdep软件刚生成就被调用,而且是用来记录自己的编译过程中用到的命令,记录到$(dot-target).tmp文件中,接下来的shell语句我就不分析了,都是很简单的命令,至此fixdep软件编译完成。

这里我们需要留意$(cmd_$(1))这个语句,因为它是真正用于执行编译过程的语句,在很多其他地方都有用到。

2.2 %config目标对应的命令

@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

指定obj变量为scripts/kconfig,且目标为xxx_defconfig,经分析,几个关键的变量如下:

src= scripts/kconfig

kbuild-dir = ./scripts/kconfig

kbuild-file = ./scripts/kconfig/Makefile

include $(kbuild-file)

可以看出./scripts/kconfig/Makefile将会被include到Makefile.build,./scripts/kconfig/Makefile(113行)中有如下目标:

%_defconfig: $(obj)/conf
	$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)

此目标依赖于$(obj)/conf 展开为 scripts/kconfig/conf ,这也是一个主机上运行的软件,需要先编译,这里不再分析,跟fixdep的编译过程类似,也是使用hostprogs-y变量,以及Makefile.host中的同一个规则进行编译,hostprogs-y变量的赋值在./scripts/kconfig/Makefile(190行):

hostprogs-y := conf nconf mconf kxgettext qconf gconf

接下来看$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig),展开如下:

@ scripts/kconfig/conf --defconfig=arch/../configs/xxx_defconfig Kconfig

这时候软件conf已经编译好了,直接调用即可,作用是生成顶层目录下的.config文件,详细的情况就不说了。

3.make过程

3.1最终的链接过程分析

.config文件生成完成后,就可以进行编译了,执行命令make V=1即可,在顶层Makefile文件中,默认目标为_all,

_all依赖于all,顶层Makefile(197行):

PHONY += all
ifeq ($(KBUILD_EXTMOD),)
_all: all
else
_all: modules
endif

 all依赖于$(ALL-y),顶层Makefile(802行)

all:		$(ALL-y)

ALL-y变量的具体值是可以根据.config中的具体变量定义进行配置的 (CONFIG_XXX形式的变量),这里摘取顶层Makefile中的部分ALL-y赋值相关的内容:

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)
# For Secure Boot The Image needs to be signed and Header must also
# be included. So The image has to be built explicitly
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中包含一个u-boot.bin,我们先从u-boot.bin目标入手,顶层Makefile(829行):

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

CONFIG_OF_SEPARATE在.config中不存在,所以else后面的目标有效,u-boot.bin依赖于u-boot-nodtb.bin,我们先看一下u-boot.bin的命令,if_changed和生成主机软件fixdep是用到的if_changed_dep类似,定义在scripts/Kbuild.include(257行):

if_changed = $(if $(strip $(any-prereq) $(arg-check)),                       \
	@set -e;                                                             \
	$(echo-cmd) $(cmd_$(1));                                             \
	printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd)

直接看$(cmd_$(1)),其他部分其实我们已经分析过一遍了,由于我们传入的参数是copy,所以展开之后是$(cmd_copy),cmd_copy变量定义在顶层Makefile(827行)如下,就是简单的cp命令:

quiet_cmd_copy = COPY    $@
      cmd_copy = cp $< $@

接下来分析u-boot-nodtb.bin目标,顶层Makefile(870行):

u-boot-nodtb.bin: u-boot FORCE
	$(call if_changed,objcopy)
	$(call DO_STATIC_RELA,$<,$@,$(CONFIG_SYS_TEXT_BASE))
	$(BOARD_SIZE_CHECK)

此规则具体的语法我就不详细展开了,看到objcopy,推测,应该是有一个cmd_objcopy的目标,里面调用了objcopy命令,我们直接看它依赖的u-boot目标。

u-boot目标在顶层Makefile(1170行):

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

依赖于    $(u-boot-init) $(u-boot-main) u-boot.lds三个目标。

我们先看u-boot自己的规则,我们只看第一句,后面ifeq里面的就不分析了,CONFIG_KALLSYMS好像没有定义。

$(call if_changed,u-boot__),cmd_u-boot__变量定义在顶层Makefile(1162行):

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.lds连接脚本,将u-boot-main 和u-boot-init变量(其实是大量的built-in.o文件)链接到一起生成u-boot文件,这应该是最后一步,由于展开过长,且命令很简单,我就不展开了。下面我们分析这些built-in.o文件是如何生成的,以及u-boot.lds是怎么生成的。

3.2 u-boot.lds链接脚本生成过程分析

u-boot.lds目标定义在顶层Makefile(1311行):

u-boot.lds: $(LDSCRIPT) prepare FORCE
	$(call if_changed_dep,cpp_lds)

这里的依赖文件就不分析了,直接看规则,$(call if_changed_dep,cpp_lds),所以对应的变量为cmd_cpp_lds,定义在顶层Makefile(1308行):

cmd_cpp_lds = $(CPP) -Wp,-MD,$(depfile) $(cpp_flags) $(LDPPFLAGS) -ansi \
		-D__ASSEMBLY__ -x assembler-with-cpp -P -o $@ $<

展开的结果,我们可以直接参考编译完成后生成的.u-boot.lds.cmd文件,这些.cmd文件对编译命令都有记载,如下:

cmd_u-boot.lds := arm-linux-gnueabihf-gcc -E -Wp,-MD,./.u-boot.lds.d -D__KERNEL__ -D__UBOOT__   -D__ARM__ -marm -mno-thumb-interwork  -mabi=aapcs-linux  -mword-relocations  -fno-pic  -mno-unaligned-access  -ffunction-sections -fdata-sections -fno-common -ffixed-r9  -msoft-float  -pipe  -march=armv7-a   -Iinclude   -I./arch/arm/include -include ./include/linux/kconfig.h  -nostdinc -isystem /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/../lib/gcc/arm-linux-gnueabihf/4.9.4/include -include ./include/u-boot/u-boot.lds.h -DCPUDIR=arch/arm/cpu/armv7  -ansi -D__ASSEMBLY__ -x assembler-with-cpp -P -o u-boot.lds arch/arm/cpu/u-boot.lds

其实这里是使用arm-linux-gnueabihf-gcc交叉编译器以arch/arm/cpu/u-boot.lds 为模板生成顶层目录下的u-boot.lds ,我也是第一次看到gcc编译器的这个功能。

3.3 所有的built-in.o等.o文件生成分析

这里可以参考另一篇文章,虽然是介绍linux内核的编译,但是u-boot和linux内核用的同一套Makefile,可以借鉴:https://www.cnblogs.com/andyfly/p/9401647.html

在3.1章节最后我们提到了两个变量,u-boot-main 和u-boot-init,定义在顶层Makefile(682行):

u-boot-init := $(head-y)
u-boot-main := $(libs-y)

head-y在arch/arm/Makefile(70行)定义如下:

head-y := arch/arm/cpu/$(CPU)/start.o

这个是汇编文件编译而来的,应该放在整个u-boot的开头位置,至于此.o怎么编译生成的,这里不深究,这里只深究built-in.o文件是如何生成的,因为其他文件夹下的.c代码全部都会编译成buit-in.o,所以更有通用性。

libs-y在顶层Makefile(624行)中有大量的赋值,赋的值都是源码目录,如下:

libs-y += lib/
libs-$(HAVE_VENDOR_COMMON_LIB) += board/$(VENDOR)/common/
libs-$(CONFIG_OF_EMBED) += dts/
libs-y += fs/
libs-y += net/
libs-y += disk/
libs-y += drivers/
libs-y += drivers/dma/
libs-y += drivers/gpio/
libs-y += drivers/i2c/
libs-y += drivers/mmc/
libs-y += drivers/mtd/
libs-$(CONFIG_CMD_NAND) += drivers/mtd/nand/
libs-y += drivers/mtd/onenand/
libs-$(CONFIG_CMD_UBI) += drivers/mtd/ubi/
libs-y += drivers/mtd/spi/
libs-y += drivers/net/
libs-y += drivers/net/phy/
libs-y += drivers/pci/
libs-y += drivers/power/ \
	drivers/power/fuel_gauge/ \
	drivers/power/mfd/ \
	drivers/power/pmic/ \
	drivers/power/battery/ \
	drivers/power/regulator/
libs-y += drivers/spi/
libs-$(CONFIG_FMAN_ENET) += drivers/net/fm/
libs-$(CONFIG_SYS_FSL_DDR) += drivers/ddr/fsl/
libs-$(CONFIG_ALTERA_SDRAM) += drivers/ddr/altera/
libs-y += drivers/serial/
libs-y += drivers/usb/dwc3/
libs-y += drivers/usb/emul/
libs-y += drivers/usb/eth/
libs-y += drivers/usb/gadget/
libs-y += drivers/usb/gadget/udc/
libs-y += drivers/usb/host/
libs-y += drivers/usb/musb/
libs-y += drivers/usb/musb-new/
libs-y += drivers/usb/phy/
libs-y += drivers/usb/ulpi/
libs-y += cmd/
libs-y += common/
libs-$(CONFIG_API) += api/
libs-$(CONFIG_HAS_POST) += post/
libs-y += test/
libs-y += test/dm/
libs-$(CONFIG_UT_ENV) += test/env/

libs-y += $(if $(BOARDDIR),board/$(BOARDDIR)/)

libs-y := $(sort $(libs-y))

u-boot-dirs	:= $(patsubst %/,%,$(filter %/, $(libs-y))) tools examples

u-boot-alldirs	:= $(sort $(u-boot-dirs) $(patsubst %/,%,$(filter %/, $(libs-))))

libs-y		:= $(patsubst %/, %/built-in.o, $(libs-y))

u-boot-init := $(head-y)
u-boot-main := $(libs-y)

在上述赋值的后面,将libs-y中所有目录加上built-in.o最为结尾,所以此时libs-y变成了大量的built-in.o文件,展开为:

u-boot-dirs=arch/arm/cpu arch/arm/cpu/armv7 arch/arm/imx-common arch/arm/lib board/freescale/common board/freescale/mx6ullevk cmd common disk drivers drivers/dma drivers/gpio drivers/i2c drivers/mmc drivers/mtd drivers/mtd/onenand drivers/mtd/spi drivers/net drivers/net/phy drivers/pci drivers/power drivers/power/battery drivers/power/fuel_gauge drivers/power/mfd drivers/power/pmic drivers/power/regulator drivers/serial drivers/spi drivers/usb/dwc3 drivers/usb/emul drivers/usb/eth drivers/usb/gadget drivers/usb/gadget/udc drivers/usb/host drivers/usb/musb-new drivers/usb/musb drivers/usb/phy drivers/usb/ulpi fs lib net test test/dm tools examples

u-boot-dirs变量记录了这些built-in.o文件的所在目录。

顶层Makefile(1188行):

$(sort $(u-boot-init) $(u-boot-main)): $(u-boot-dirs) ;

意思是所有的built-in.o文件编译依赖于built-in.o的目录对应的目标,其实这些built-in.o就是在目录为目标的规则中完成编译的。$(u-boot-dirs)变量的规则在顶层Makefile(1197行):

PHONY += $(u-boot-dirs)
$(u-boot-dirs): prepare scripts
	$(Q)$(MAKE) $(build)=$@

虽然这是一个规则,但是由于u-boot-dirs变量有很多值,所以这条规则会被分成很多条规则,依次调用。

我们挑选任意一个built-in.o的目录作为目标,这里就挑选drivers/gpio作为目标,依赖目标prepare scripts就不详解了。

$(Q)$(MAKE) $(build)=$@展开就是:make -f ./scripts/Makefile.build obj=drivers/gpio,这个Makefile.build文件我们已经分析多次,此时在Makefile.build中kbuild-dir = drivers/gpio,kbuild-file =  drivers/gpio/Makefile,所以它会将driver/gpio/Makefile文件include进去,此Makefile如下:

ifndef CONFIG_SPL_BUILD
obj-$(CONFIG_DWAPB_GPIO)	+= dwapb_gpio.o
obj-$(CONFIG_AXP_GPIO)		+= axp_gpio.o
endif
obj-$(CONFIG_DM_GPIO)		+= gpio-uclass.o

obj-$(CONFIG_AT91_GPIO)	+= at91_gpio.o
obj-$(CONFIG_ATMEL_PIO4)	+= atmel_pio4.o
obj-$(CONFIG_INTEL_ICH6_GPIO)	+= intel_ich6_gpio.o
obj-$(CONFIG_KIRKWOOD_GPIO)	+= kw_gpio.o
obj-$(CONFIG_KONA_GPIO)	+= kona_gpio.o
obj-$(CONFIG_MARVELL_GPIO)	+= mvgpio.o
obj-$(CONFIG_MARVELL_MFP)	+= mvmfp.o
obj-$(CONFIG_MXC_GPIO)	+= mxc_gpio.o
obj-$(CONFIG_MXS_GPIO)	+= mxs_gpio.o
obj-$(CONFIG_PCA953X)		+= pca953x.o
obj-$(CONFIG_PCA9698)		+= pca9698.o
obj-$(CONFIG_ROCKCHIP_GPIO)	+= rk_gpio.o
obj-$(CONFIG_S5P)		+= s5p_gpio.o
obj-$(CONFIG_SANDBOX_GPIO)	+= sandbox.o
obj-$(CONFIG_SPEAR_GPIO)	+= spear_gpio.o
obj-$(CONFIG_TEGRA_GPIO)	+= tegra_gpio.o
obj-$(CONFIG_DA8XX_GPIO)	+= da8xx_gpio.o
obj-$(CONFIG_DM644X_GPIO)	+= da8xx_gpio.o
obj-$(CONFIG_ALTERA_PIO)	+= altera_pio.o
obj-$(CONFIG_MPC83XX_GPIO)	+= mpc83xx_gpio.o
obj-$(CONFIG_SH_GPIO_PFC)	+= sh_pfc.o
obj-$(CONFIG_OMAP_GPIO)	+= omap_gpio.o
obj-$(CONFIG_DB8500_GPIO)	+= db8500_gpio.o
obj-$(CONFIG_BCM2835_GPIO)	+= bcm2835_gpio.o
obj-$(CONFIG_S3C2440_GPIO)	+= s3c2440_gpio.o
obj-$(CONFIG_XILINX_GPIO)	+= xilinx_gpio.o
obj-$(CONFIG_ADI_GPIO2)	+= adi_gpio2.o
obj-$(CONFIG_TCA642X)		+= tca642x.o
oby-$(CONFIG_SX151X)		+= sx151x.o
obj-$(CONFIG_SUNXI_GPIO)	+= sunxi_gpio.o
obj-$(CONFIG_LPC32XX_GPIO)	+= lpc32xx_gpio.o
obj-$(CONFIG_STM32_GPIO)	+= stm32_gpio.o
obj-$(CONFIG_GPIO_UNIPHIER)	+= gpio-uniphier.o
obj-$(CONFIG_ZYNQ_GPIO)		+= zynq_gpio.o
obj-$(CONFIG_VYBRID_GPIO)	+= vybrid_gpio.o
obj-$(CONFIG_HIKEY_GPIO)	+= hi6220_gpio.o
obj-$(CONFIG_PIC32_GPIO)	+= pic32_gpio.o

都是关于obj-y变量的配置,也是根据宏定义进行配置的,这里我们只需要编译一个文件,每个厂商对应一个文件,我们的是NXP的mxc_gpio.o,所以此时obj-y = mxc_gpio.o ,scripts/Makefile.build中(第108行):

ifneq ($(strip $(obj-y) $(obj-m) $(obj-) $(subdir-m) $(lib-target)),)
builtin-target := $(obj)/built-in.o
endif

此时builtin-target :=drivers/gpio/built-in.o ,默认目标为__build,依赖于builtin-target,所以先去执行built-target目标的规则,位于scripts/Makefile.build(358行):

$(builtin-target): $(obj-y) FORCE
	$(call if_changed,link_o_target)

cmd_link_o_target变量定义在scripts/Makefile.build(第353行)

quiet_cmd_link_o_target = LD      $@
# If the list of objects to link is empty, just create an empty built-in.o
cmd_link_o_target = $(if $(strip $(obj-y)),\
		      $(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^) \
		      $(cmd_secanalysis),\
		      rm -f $@; $(AR) rcs$(KBUILD_ARFLAGS) $@)

大概意思是将obj-y变量中的所有.o文件链接成一个built-in.o文件。【题外话:所以在顶层Makefile(1197行)执行make -f ./scripts/Makefile.build obj=xxx 命令的时候,每次执行这条命令的过程中obj-y变量都不相同,互不影响。】

到这里,还是有疑问,obj-y变量中的.o文件又是根据那条规则编译生成的呢?我们注意到$(builtin-target)目标依赖于$(obj-y),所以在将obj-y中所有.o文件链接成built-in.o文件之前,肯定要先生成obj-y中的.o文件,其实是根据默认规则生成的,scripts/Makefile.build(279行)

# Built-in and composite module parts
$(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE
	$(call cmd,force_checksrc)
	$(call if_changed_rule,cc_o_c)

这里调用了函数if_changed_rule,和if_changed函数位于同一文件,位于Kbuild.include(273行)

# Usage: $(call if_changed_rule,foo)
# Will check if $(cmd_foo) or any of the prerequisites changed,
# and if so will execute $(rule_foo).
if_changed_rule = $(if $(strip $(any-prereq) $(arg-check) ),                 \
	@set -e;                                                             \
	$(rule_$(1)))

我们传进去的参数为cc_o_c,所以为$(rule_cc_o_c),位于scripts/Makefile.build(266行)

define rule_cc_o_c
	$(call echo-cmd,checksrc) $(cmd_checksrc)			  \
	$(call echo-cmd,cc_o_c) $(cmd_cc_o_c);				  \
	$(cmd_modversions)						  \
	$(call echo-cmd,record_mcount)					  \
	$(cmd_record_mcount)						  \
	scripts/basic/fixdep $(depfile) $@ '$(call make-cmd,cc_o_c)' >    \
	                                              $(dot-target).tmp;  \
	rm -f $(depfile);						  \
	mv -f $(dot-target).tmp $(dot-target).cmd
endef

我们看到rule_cc_o_c展开了一个叫cmd_cc_o_c的变量位于scripts/Makefile.build(205行)

ifndef CONFIG_MODVERSIONS
cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<

else

可以看到这才是真正的将.c文件编译成.o文件的命令。

这里我们经常调用的三个函数,if_changed, if_changed_dep, if_changed_rule,其实是分工明确的,可以参考https://blog.csdn.net/linuxweiyh/article/details/100179968,和参考Kbuild.include文件中对它们的说明。

3.4 其他的漏掉的环节

我们大体知道了u-boot是如何生成的,但是还有大量的环节我们没有分析,例如上述很多目标都有一个叫做prepare的依赖,它是如何做编译前的准备的,我们生成的.config文件是如何发挥作用对u-boot进行裁剪的,我也并未深究,在顶层Makefile中(490行和494行)

ifeq ($(dot-config),1)
# Read in config
-include include/config/auto.conf

# Read in dependencies to all Kconfig* files, make sure to run
# oldconfig if changes are detected.
-include include/config/auto.conf.cmd

这里-include了两个文件,它们应该是根据.config生成的,所以第一次include的时候,并没有成功,整个Makefile解析一遍之后,make会寻找匹配的规则(如下)来生成它们,顶层Makefile(503行)

include/config/%.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd
	$(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig
	@# If the following part fails, include/config/auto.conf should be
	@# deleted so "make silentoldconfig" will be re-run on the next build.
	$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.autoconf || \
		{ rm -f include/config/auto.conf; false; }
	@# include/config.h has been updated after "make silentoldconfig".
	@# We need to touch include/config/auto.conf so it gets newer
	@# than include/config.h.
	@# Otherwise, 'make silentoldconfig' would be invoked twice.
	$(Q)touch include/config/auto.conf

当生成它们之后,会被-include进来,然后才开始_all目标,即正式编译,这里只是提一个思路,就不深究了。另外还有很多自动生成的文件,例如include/generated目录下的头文件是如何生成的,等等大量的环节没有分析,感兴趣的可以自行分析

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值