1、选项和参数配置
1.1、版本号
VERSION = 2016 // 主版本号
PATCHLEVEL = 03 // 补丁版本号
SUBLEVEL = // 次版本号
EXTRAVERSION = // 附加信息
NAME = // uboot名称
1.2、MAKEFLAGS
MAKEFLAGS在make执行的过程中传递给子make,用于make命令执行时指定选项和标志,-r
选项用于禁止使用内置的隐含规则,-R
选项用于禁止使用内置的变量定义,--include-dir选项制定搜索路径,$(CURDIR)表示当前目录
MAKEFLAGS += -rR --include-dir=$(CURDIR)
1.3、显示详细编译信息
执行make的时候添加V=1选项可以显示更详细的编译信息。
ifeq ("$(origin V)", "command line")
KBUILD_VERBOSE = $(V)
endif
ifndef KBUILD_VERBOSE
KBUILD_VERBOSE = 0
endif
ifeq ($(KBUILD_VERBOSE),1)
quiet =
Q =
else
quiet=quiet_
Q = @
endif
添加该选项后 quiet 和Q均为空,下图所示命令执行的时候就可以输出信息,否则Q值为@就不会输出编译信息:
1.4、静默编译
编译的时候使用make -s可以实现静默编译,本质是quiet=silent_:
# If the user is running make -s (silent mode), suppress echoing of
# commands
ifneq ($(filter 4.%,$(MAKE_VERSION)),) # make-4
ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),)
quiet=silent_
endif
else # make-3.8x
ifneq ($(filter s% -s%,$(MAKEFLAGS)),)
quiet=silent_
endif
endif
1.5、设置编译结果输出路径
该选项很少用到,编译的时候make O=filepath,可以指定输出路径KBUILD_OUTPUT 变量保存filepath并供makefile使用
ifeq ($(KBUILD_SRC),)
# OK, Make called in directory where kernel src resides
# Do we want to locate output files in a separate directory?
ifeq ("$(origin O)", "command line")
KBUILD_OUTPUT := $(O)
endif
# That's our default target when none is given on the command line
PHONY := _all
_all:
# Cancel implicit rules on top Makefile
$(CURDIR)/Makefile Makefile: ;
ifneq ($(KBUILD_OUTPUT),)
# Invoke a second make in the output directory, passing relevant variables
# check that the output directory actually exists
saved-output := $(KBUILD_OUTPUT)
KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) \
&& /bin/pwd)
$(if $(KBUILD_OUTPUT),, \
$(error failed to create output directory "$(saved-output)"))
PHONY += $(MAKECMDGOALS) sub-make
$(filter-out _all sub-make $(CURDIR)/Makefile, $(MAKECMDGOALS)) _all: sub-make
@:
sub-make: FORCE
$(Q)$(MAKE) -C $(KBUILD_OUTPUT) KBUILD_SRC=$(CURDIR) \
-f $(CURDIR)/Makefile $(filter-out _all sub-make,$(MAKECMDGOALS))
# Leave processing to above invocation of make
skip-makefile := 1
endif # ifneq ($(KBUILD_OUTPUT),)
endif # ifeq ($(KBUILD_SRC),)
1.6、源文件检查
该选项很少用到,编译的时候make C=1,或者C=2,1表示检查需要重新编译的文件,2表示检查所有文件,该值会赋值给变量KBUILD_CHECKSRC,变量在顶层makefile中export KBUILD_CHECKSRC导出给其他文件使用
ifeq ("$(origin C)", "command line")
KBUILD_CHECKSRC = $(C)
endif
ifndef KBUILD_CHECKSRC
KBUILD_CHECKSRC = 0
endif
1.7、模块编译
该选项很少用到,编译的时候make M=modle;KBUILD_EXTMOD变量赋值为modle,_all依赖于modules,makefile会执行相应命令生成模块:
ifdef SUBDIRS
KBUILD_EXTMOD ?= $(SUBDIRS)
endif
ifeq ("$(origin M)", "command line")
KBUILD_EXTMOD := $(M)
endif
# If building an external module we do not care about the all: rule
# but instead _all depend on modules
PHONY += all
ifeq ($(KBUILD_EXTMOD),)
_all: all
else
_all: modules
endif
1.8、获取编译平台信息
通过执行shell命令并通过管道进行处理得到HOSTARCH HOSTOS导出:
HOSTARCH := $(shell uname -m | \
sed -e s/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 HOSTARCH HOSTOS
可以打印出信息查看:
1.9、设置编译工具等
HOSTARCH和ARCH一致的话为将CROSS_COMPILE 变量设置为空,KCONFIG_CONFIG 变量未指定的话指定为.config,判断是否存在bash或者/bin/bash,存在的话赋值给变量CONFIG_SHELL 。
# set default to nothing for native builds
ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE ?=
endif
KCONFIG_CONFIG ?= .config
export KCONFIG_CONFIG
# SHELL used by kbuild
CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
else if [ -x /bin/bash ]; then echo /bin/bash; \
else echo sh; fi ; fi)
HOSTCC = cc
HOSTCXX = c++
HOSTCFLAGS = -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer
HOSTCXXFLAGS = -O2
ifeq ($(HOSTOS),cygwin)
HOSTCFLAGS += -ansi
endif
在执行make的时候通常需要指定ARCH和CROSS_COMPILE,可以在此处直接赋值,后续只需要执行make即可:
ARCH ?= arm
CROSS_COMPILE ?= arm-linux-gnueabihf-
2、make xxx_defconfig执行过程
2.1、目的
执行命令查看详细信息,make xxx_defconfig的最终目的是将配置写入.config中:
2.2 、规则分析
使用以下命令将make规则信息输出到makedata.log中:
make mx6ull_14x14_ddr256_nand_defconfig V=1 -p > makedata.log
查找mx6ull_14x14_ddr256_nand_defconfig,其依赖于scripts/kconfig/conf,规则来源于cripts/kconfig/Makefile的114行:
mx6ull_14x14_ddr256_nand_defconfig: scripts/kconfig/conf
# recipe to execute (from 'scripts/kconfig/Makefile', line 114):
$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)
继续查找scripts/kconfig/conf工具的依赖:
scripts/kconfig/conf: FORCE scripts/kconfig/conf.o scripts/kconfig/zconf.tab.o
进一步查找可以发现这些.o文件来源于一些.c文件。
scripts/kconfig/conf.o: scripts/kconfig/conf.c FORCE
scripts/kconfig/zconf.tab.o: scripts/kconfig/zconf.tab.c FORCE scripts/kconfig/zconf.lex.c scripts/kconfig/zconf.hash.c
因此整个过程是通过一些.c文件生成.o最终.o文件被链接成scripts/kconfig/conf,使用该工具执行$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)命令即为make xxxdefconfig的作用,在命令中$(Q)为是否编译输出,无需关注;$<表示第一个依赖,即scripts/kconfig/conf;silent没有设置,无需关注;SRCARCH为“..”;$@为第一个目标,即mx6ull_14x14_ddr256_nand_defconfig;$(Kconfig)为Kconfig,最终执行的命令为:
scripts/kconfig/conf --defconfig=configs/mx6ull_14x14_ddr256_nand_defconfig Kconfig
2.3、工具代码分析
要分析上述命令具体的实现细节则需要查看conf这个工具的源码,即2.2中分析出依赖的几个.c文件,工具的主函数位于scripts/kconfig/conf.c中。
首先通过getopt_long函数处理入参,实现input_mode和defconfig_file的赋值,其中input_mode赋值为defconfig,defconfig_file赋值为configs/mx6ull_14x14_ddr256_nand_defconfig。
while ((opt = getopt_long(ac, av, "s", long_opts, NULL)) != -1) {
if (opt == 's') {
conf_set_message_callback(NULL);
continue;
}
input_mode = (enum input_mode)opt;
switch (opt) {
case silentoldconfig:
sync_kconfig = 1;
break;
case defconfig:
case savedefconfig:
defconfig_file = optarg;
break;
……
……
……
case '?':
conf_usage(progname);
exit(1);
break;
}
}
在源码中添加了几行打印信息方便查看赋值,执行到if (ac == optind)时,我们后续还有一个参数Kconfig所以判断不成立不会执行,如果我们调用工具时不添加kconfig参数就会打印错误信息:
printf("defconfig_file : %s\n",defconfig_file);
if (ac == optind) {
printf(_("%s: Kconfig file missing\n"), av[0]);
conf_usage(progname);
exit(1);
}
name = av[optind];
printf("name = %s\n", name);
conf_parse(name);
后续name被赋值为Kconfig,然后调佣conf_parse进行解析处理。处理完Kconfig后调用conf_read处理我们指定的默认配置文件。
处理完Kconfiug和xxx_defconfig后调用conf_write将处理结果写入.config文件:
执行完整过程可以查看添加的打印信息:
3、make执行过程
在顶层目录直接执行make,在Makefile中会查找第一个目标去生成,第一个目标为_all,依赖于all:
ifeq ($(KBUILD_EXTMOD),)
_all: all
else
_all: modules
all依赖于$(ALL-y):
all: $(ALL-y)
$(ALL-y)的值如下,对于其中的ALL-$(CONFIG_ONENAND_U_BOOT) += u-boot-onenand.bin这样的语句,如果.config中对CONFIG_ONENAND_U_BOOT进行配置的话会有CONFIG_ONENAND_U_BOOT=y的设置,此时就是ALL-y += u-boot-onenand.bin。
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
…………
…………
…………
# Add optional build target if defined in board/cpu/soc headers
ifneq ($(CONFIG_BUILD_TARGET),)
ALL-y += $(CONFIG_BUILD_TARGET:"%"=%)
endif
u-boot.bin为例,在第一行有给$(ALL-y)累加u-boot.bin,查找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
u-boot.bin依赖于u-boot-nodtb.bin,继续查找:
u-boot-nodtb.bin: u-boot FORCE
u-boot-nodtb.bin依赖于u-boot,继续查找:
u-boot: $(u-boot-init) $(u-boot-main) u-boot.lds FORCE
其中三项依赖如下,大概意思是通过连接脚本将头部和多个库链接生成u-boot-nodtb.bin:
u-boot-init := $(head-y)
u-boot-main := $(libs-y)
u-boot.lds: $(LDSCRIPT) prepare FORCE
//对于$(head-y)在arch/arm/Makefile指定:
head-y := arch/arm/cpu/$(CPU)/start.o
//替换后:
head-y := arch/arm/cpu/armv7/start.o
//对于$(libs-y)在顶层Makefile中指定:
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 := $(patsubst %/, %/built-in.o, $(libs-y))
对于ALL-y其他依赖可以依次分析。
4、总结
简单介绍了uboot顶层makefile,给初学者提供一些参考。
文章参考:
正点原子左盟主:《I.MX6U嵌入式Linux驱动开发指南》
百文网韦东山老师课程:《u-boot完全分析与移植》