嵌入式Linux(十八)Uboot顶层MakeFile

一.Uboot工程目录分析

  主要关心几个文件夹:

1)arch文件夹:
  存放架构相关的文件,我们用的ARM,只需要关心其ARM子文件夹即可。我们使用的是I.MX6ULL,所以需要关注imx-common文件夹,CPU文件夹下需要关注armv7子文件夹,因为6ull使用的是Cortex-A7内核属于armv7系列。cpu文件夹下的u-boot.lds链接脚本文件是ARM芯片使用的uboot链接脚本文件。
嵌入式Linux(十八)Uboot顶层MakeFile_第1张图片
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文件:
嵌入式Linux(十八)Uboot顶层MakeFile_第2张图片
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 脚本分析

嵌入式Linux(十八)Uboot顶层MakeFile_第3张图片
①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 执行流程
嵌入式Linux(十八)Uboot顶层MakeFile_第4张图片

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。
嵌入式Linux(十八)Uboot顶层MakeFile_第5张图片

你可能感兴趣的:(嵌入式,linux,arm开发,运维)