一.Uboot工程目录分析
主要关心几个文件夹:
1)arch文件夹:
存放架构相关的文件,我们用的ARM,只需要关心其ARM子文件夹即可。我们使用的是I.MX6ULL,所以需要关注imx-common文件夹,CPU文件夹下需要关注armv7子文件夹,因为6ull使用的是Cortex-A7内核属于armv7系列。cpu文件夹下的u-boot.lds链接脚本文件是ARM芯片使用的uboot链接脚本文件。
2)board文件夹:
和具体的板子相关。I.MX系列以前属于freescale,所以打开该文件夹,正点原子的开发板在NXP基础上开发,对应的是mx6ullevk文件夹。
3)configs文件夹:
uboot配置文件。我的板子对应的是mx6ull_14x14_ddr512_emmc_defconfig这个文件,make该文件即可配置uboot。之前在mx6ull_alientek_emmc.sh文件里已经写好了这句。
4).u-boot.xxx_cmd文件:
生成对应的xxx文件。u-boot.cmd用于生成uboot。u-boot.cmd中使用了ld.bfd链接工具,将各个.c文件编译的.o文件链接在一起得到built-in.o文件,形成uboot文件。
前面说到使用MFGTools向开发板烧写的是u-boot.imx文件而不是bin文件,就是由.u-boot.imx.cmd文件实现向bin文件头部添加IVT信息从而生成imx文件。
**5)Makefile文件:**略
6)u-boot.xxx文件:
7).config文件:
make deconfig文件后得到。在.config 中会有大量的变量值为‘y’,这些为‘y’的变量一般用于控制某项功能是否使能,为‘y’的话就表示功能使能。
比如:
CONFIG_CMD_BOOTM=y使能了bootm命令,在cmd/Makefile中有一行obj-$(CONFIG_CMD_BOOTM) += bootm.o,展开后得到obj-y += bootm.o。obj-y包含所有要编译的文件对应的.o文件,所以相当于给obj-y追加了一个bootm.o,从而编译了bootm.c文件。
二.Uboot顶层Makefile分析
2.1 版本号
VERSION = 2016 //主版本号
PATCHLEVEL = 03 //补丁版本号
SUBLEVEL = //次版本号
EXTRAVERSION = //附加版本信息
NAME =
2.2 MAKEFLAGS 变量
①主目录Makefile可以通过以下命令编译子目录Makefile:$(MAKE) -C subdir。-C指定子目录。
②向子Makefile传递变量:
export VARIABLE …… //导出变量给子 make 。
unexport VARIABLE…… //不导出变量给子 make。
③特殊变量SHELL和MAKEFLAGS:
默认自动传递给子make,除非加unexport限制。
MAKEFLAGS += -rR --include-dir=$(CURDIR) //+=给变量追加值,其中-rR表示禁止使用内置隐含规则和变量定义,--include-dir表示搜索路径,$(CURDIR)表示当前目录
2.3 命令输出
编译uboot的时候控制台会有输出,在顶层make中控制输出的代码如下:
ifeq ("$(origin V)", "command line") //判断$(origin V)是否等于command line。origin是makefile中的函数,用于返回变量的来源。如果V是在命令行定义的来源就是command line.
KBUILD_VERBOSE = $(V) //命令行中V的值赋给KBUILD_VERBOSE
endif
ifndef KBUILD_VERBOSE
KBUILD_VERBOSE = 0 //否则=0
endif
//makefile中会用quiet和Q控制编译的时候是否在终端输出完整命令
ifeq ($(KBUILD_VERBOSE),1)
quiet =
Q =
else
quiet=quiet_
Q = @
endif
在顶层make中有很多这种命令:
$(Q)$(MAKE) $(build)=tools
//V=0展开:@ make $(build)=tools。这个@会禁止在终端输出命令
//V=1展开:make $(build)=tools.命令完整输出
//如果变量 quiet 为空的话,整个命令都会输出。
//如果变量 quiet 为“quiet_”的话,仅输出短版本。
//如果变量 quiet 为“silent_”的话,整个命令都不会输出。
2.4 静默输出
即使V=0,编译uboot依然有输出,如果想不要的话使用make -s就可以静默输出,其内部实现代码如下:
ifneq ($(filter 4.%,$(MAKE_VERSION)),) # make-4 //判断版本号是否为4.x,与NULL比较,不等就成立
ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),)
quiet=silent_
endif
else # make-3.8x
ifneq ($(filter s% -s%,$(MAKEFLAGS)),)
quiet=silent_
endif
filter是Makefile的过滤函数,表示以 pattern 模式过滤 text 字符串中的单词,仅保留符合模式 pattern 的单词,可以有多个模式。函数返回值就是符合 pattern 的字符串。
$(filter <pattern...>,<text>)
firstword获取首单词,用于取出 text 字符串中的第一个单词,函数的返回值就是获取到的单词。
$(firstword <text>)
2.5 设置编译结果输出目录
uboot将编译出来的目标文件输出到单独的目录中,在make时使用O来指定输出目录即可。
//判断O是否来源于命令行,KBUILD_OUTPUT为输出目录
ifeq ("$(origin O)", "command line")
KBUILD_OUTPUT := $(O)
endif
...
ifneq ($(KBUILD_OUTPUT),) //判断KBUILD_OUTPUT是否为空
...
//创建KBUILD_OUTPUT目录,路径赋值给KBUILD_OUTPUT,至此实现O指定的输出目录。
KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) \
&& /bin/pwd)
2.6 代码检查
uboot中使用“make C=1”使能代码检查,检查需要重新编译的文件;使用“make C=2”检查所有源码文件。顶层make中实现如下:
ifeq ("$(origin C)", "command line")
KBUILD_CHECKSRC = $(C)
endif
ifndef KBUILD_CHECKSRC
KBUILD_CHECKSRC = 0
endif
2.7 模块编译
uboot中可以单独编译某个模块,使用“make M=dir”即可,顶层make中实现如下:
ifdef SUBDIRS //判断是否定义了SUBDIRS,为了支持老语法“make SUBIDRS=dir”
KBUILD_EXTMOD ?= $(SUBDIRS)
endif
//判断是否在命令行定义了 M,如果定义了的话 KBUILD_EXTMOD=$(M)。
ifeq ("$(origin M)", "command line")
KBUILD_EXTMOD := $(M)
endif
//判断 KBUILD_EXTMOD 时为空,如果为空的话目标_all 依赖 all
//因此要先编译出 all。否则的话默认目标_all 依赖 modules,要先编译出 modules,也就是编译模块。
PHONY += all
ifeq ($(KBUILD_EXTMOD),)
_all: all
else
_all: modules
endif
//行判断 KBUILD_SRC 是否为空,如果为空的话就设置变量 srctree 为当前目录,即srctree 为“.”
//一般不设置 KBUILD_SRC。
ifeq ($(KBUILD_SRC),)
srctree := .
else
ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR)))
srctree := ..
else
srctree := $(KBUILD_SRC)
endif
endif
objtree := . //设置变量 objtree 为当前目录
//设置变量 src 和 obj,都为当前目录
src := $(srctree)
obj := $(objtree)
VPATH := $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD))
export srctree objtree VPATH
2.8 获取主机架构和系统
顶层make会获取主机架构和系统。
//HOSTARCH变量用于保存主机架构,这里调用shell命令“uname -m”得到架构名
HOSTARCH := $(shell uname -m | \ //“|”是管道,将左边的输出作为右边的输入
sed -e s/i.86/x86/ \ //sed -e 是替换命令,表示将管道输入的字符串中的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变量用于保存主机系统的值
HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \ //转换小写
sed -e 's/\(cygwin\).*/cygwin/')
export HOSTARCH HOSTOS //导出 HOSTARCH=x86_64,HOSTOS=linux
2.9 设置目标架构、交叉编译器和配置文件
编译uboot需要设置目标板架构和交叉编译器,“ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-”就是用于设置 ARCH 和 CROSS_COMPILE。顶层make中代码如下:
//判断主机架构和目标板架构是否相等
ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE ?=
endif
//肯定不相等,所以设置
ARCH ?= arm
CROSS_COMPILE ?= arm-linux-gnueabihf-
//设置配置文件为.config
KCONFIG_CONFIG ?= .config
export KCONFIG_CONFIG
2.10 调用 scripts/Kbuild.include
顶层make会调用该文件,该文件包含了很多变量的定义。编译过程中需要使用。
2.11 交叉编译工具设置
设置交叉编译器其他的工具。
2.12 导出其它变量
重点关注以下变量:ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR。这七个变量不在顶层make中定义,而是在uboot根目录下的config.mk文件定义的。
然后是CONFIG_SYS_ARCH、CONFIG_SYS_CPU、CONFIG_SYS_BOARD、 CONFIG_SYS_VENDOR 和 CONFIG_SYS_SOC。这几个变量在uboot根目录下的**.config**文件中定义。
ARCH = arm
CPU = armv7
BOARD = mx6ullevk
VENDOR = freescale
SOC = mx6
CPUDIR = arch/arm/cpu/armv7
BOARDDIR = freescale/mx6ullevk
CONFIG_SYS_ARCH="arm"
CONFIG_SYS_CPU="armv7"
CONFIG_SYS_SOC="mx6"
CONFIG_SYS_VENDOR="freescale"
CONFIG_SYS_BOARD="mx6ullevk "
CONFIG_SYS_CONFIG_NAME="mx6ullevk"
2.13 make xxx_defconfig 过程
version_h := include/generated/version_autogenerated.h //变量version_h保存版本号文件
timestamp_h := include/generated/timestamp_autogenerated.h //timestamp_h保存时间戳文件
no-dot-config-targets := clean clobber mrproper distclean \
help %docs check% coccicheck \
ubootversion backup
config-targets := 0
mixed-targets := 0
dot-config := 1
//将MAKECMDGOALS中不符合 no-dot-config-targets 的部分过滤掉
//MAKECMDGOALS是make的一个环境变量,这个变量会保存你所指定的终极目标列表
//我们执行“make mx6ull_alientek_emmc_defconfig”,那 MAKECMDGOALS=mx6ull_alientek_emmc_defconfig
//所以这里过滤后为空,条件不成立,所以dot-config=1
ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),)
ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),)
dot-config := 0
endif
endif
//KBUILD_EXTMOD 为空,所以条件成立
ifeq ($(KBUILD_EXTMOD),)
ifneq ($(filter config %config,$(MAKECMDGOALS)),) //成立,config-targets=1
config-targets := 1
ifneq ($(words $(MAKECMDGOALS)),1) //word函数:$(words <text>)。统计单词个数
mixed-targets := 1
endif
endif
endif
ifeq ($(mixed-targets),1)
PHONY += $(MAKECMDGOALS) __build_one_by_one
$(filter-out __build_one_by_one, $(MAKECMDGOALS)): __build_one_by_one
@:
__build_one_by_one:
$(Q)set -e; \
for i in $(MAKECMDGOALS); do \
$(MAKE) -f $(srctree)/Makefile $$i; \
done
else
ifeq ($(config-targets),1)
KBUILD_DEFCONFIG := sandbox_defconfig
export KBUILD_DEFCONFIG KBUILD_KCONFIG
//没有匹配目标,不执行
config: scripts_basic outputmakefile FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@
//输入“make xxx_defconfig”的时候就会匹配到%config目标,“%config”依赖于scripts_basic、outputmakefile和FORCE
//其中FORCE没有规则和依赖,所以每次都会重新生成,所以依赖于FORCE的也都会重新生成。
//------------------------------------
scripts_basic展开为:
scripts_basic:
@make -f ./scripts/Makefile.build obj=scripts/basic //也可以没有@,视配置而定
@rm -f . tmp_quiet_recordmcount //也可以没有@
//------------------------------------
%config: scripts_basic outputmakefile FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@ //展开:@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
else
ifeq ($(dot-config),1)
-include include/config/auto.conf
2.14 Makefile.build 脚本分析
①scripts_basic目标对应的命令
打开scripts/Makefile.build文件:
prefix := tpl
//patsubst为替换函数:$(patsubst <pattern>,<replacement>,<text>)
//在text中查找符合pattern得部分,使用replacement替换掉
src := $(patsubst $(prefix)/%,%,$(obj)) //展开:$(patsubst tpl/%,%, scripts/basic),意思是在scripts/basic中查找符合“tpl/%”的部分然后消掉tpl/。
ifeq ($(obj),$(src))
prefix := spl
src := $(patsubst $(prefix)/%,%,$(obj))
ifeq ($(obj),$(src))
prefix := .
endif
endif
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src)) //展开:$(if $(filter /%, scripts/basic), scripts/basic, ./scripts/basic),没有/开头的,所以kbuild-dir=./scripts/basic
//kbuild-file= ./scripts/basic/Makefile
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file) //include ./scripts/basic/Makefile 读取scripts/basic下的Makefile
//__build是默认目标,顶层make中KBUILD_BUILTIN=1,KBUILD_MODULES=0
//展开:__build:$(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)
//这里有5个依赖项,其中只有always有效,所以__build=scripts/basic/fixdep
__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
$(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
$(subdir-ym) $(always)
@:
综上所述,scripts_basic目标的作用就是编译出scripts/basic/fixdep这个软件。
②%config目标对应的命令
打开scripts/kconfig/Makefile:
%_defconfig: $(obj)/conf //展开:scripts/kconfig/conf
//展开:@ scripts/kconfig/conf --defconfig=arch/../configs/xxx_defconfig Kconfig
$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)
总结make xxx_defconfig 执行流程:
2.15 make过程分析
配置好uboot之后进行make编译,默认目标为_all,其依赖为all。顶层make中,all依赖ALL-y,ALL-y 包含u-boot.srec、u-boot.bin、u-boot.sym、System.map、u-boot.cfg 和 binary_size_check这几个文件。
all: $(ALL-y)
ALL-y 里面有个 u-boot.bin,这个就是我们最终需要的 uboot 二进制可执行文件。其在顶层make中规则如下:
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 文件,顶层Makefile 中相关代码如下:
u-boot-nodtb.bin: u-boot FORCE
$(call if_changed,objcopy)
$(call DO_STATIC_RELA,$<,$@,$(CONFIG_SYS_TEXT_BASE))
$(BOARD_SIZE_CHECK)
目标u-boot-nodtb.bin又依赖于u-boot,顶层 Makefile中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
目标u-boot依赖于u-boot_init、u-boot-main 和 u-boot.lds。u-boot_init 和 u-boot-main是两个变量,在顶层 Makefile 中有定义,值如下:
u-boot-init := $(head-y) //$(head-y)跟CPU架构有关,ARM的head-y := arch/arm/cpu/$(CPU)/start.o CPU=armv7
u-boot-main := $(libs-y) //$(libs-y)在顶层Makefile中被定义为uboot所有子目录下build-in.o的集合
以u-boot.lds为链接脚本,将arch/arm/cpu/armv7/start.o和各个子目录下的built-in.o链接在一起生成u-boot。