uboot源码makefile基础及启动流程梳理

时间:2024-10-28 14:27:34

学习正点原子教程个人记录

MakeFile

基本命令

  • make xxx_defconfig:用于配置 uboot,这个命令最主要的目的就是生成.config 文件。
  • make:用于编译 uboot,这个命令的主要工作就是生成二进制的u-boot.bin文件和其他的一
    些与 uboot 有关的文件,比如 u-boot.imx 等等。

子目录调用

主目录调用子目录的makefile

${MAKE} -C subdir 

subdir是子目录,内含有makefile文件

向子makefile传递变量

export VARIABLE .... //导出变量到子
unexport VARIABLE .... //不到出变量到子

特殊变量 SHELL ,MAKEFLAGS 默认已导出给子,除非使用 unexport声明,否则在整个make期间它的值始终传递给子

MAKEFLAGS += -rR --include-dir=$(CURDIR)
  • 使用“+=”来给变量 MAKEFLAGS 追加了一些值,
  • “-rR”表示禁止使用内置的隐含规则和变量定义,
  • “–include-dir”指明搜索路径,
  • ”$(CURDIR)”表示当前目录

默认uboot编译输出短命令。设置变量’V=1’实现所有命令输出

origin函数

返回紧紧跟着变量的来源

$(origin <variable>)

如果变量 variable 是在命令行定义的那么它的来源就是"command line"

完整命令输出

Makefile 中会用到变量 quiet 和 Q 来控制编译的时候是否在终端输出完整的命令,

make 在执行的时候默认会在终端输出命令,但是在命令前面加上“@”就不会在终端输出命令了。

假设有变量V=0 或者命令行不定义 V 的话:

KBUILD_VERBOSE=0
quiet= quiet_。
Q= @。

在顶层Makefile 中有很多如下所示的命令:

$(Q)$(MAKE) $(build)=tools

如果 V=0 的话上述命令展开就是@ make $(build)=tools

当 V=1 的时候 Q 就为空,
上述命令就是“make $(build)=tools”,因此在 make 执行的过程,命令会被完整的输出在终端上

静默输出

make -s

filter函数

$(filter <pattern...>,<text>)

filter 函数表示以 pattern 模式过滤 text 字符串中的单词,仅保留符合模式 pattern 的单词,
可以有多个模式。函数返回值就是符合 pattern 的字符串

因此$(filter 4.%,$(MAKE_VERSION))
的含义就是在字符串“MAKE_VERSION”中找出符合“ 4.%”的字符 (%为通配符),

firstword函数

$(firstword <text>)

firstword 函数用于取出 text 字符串中的第一个单词,函数的返回值就是获取到的单词.

当使用make -s时,-s会作为MAKEFLAGS变量 的一部分传递给MakeFile,实测 -s 会让 MAKEFLAGS值变为

请添加图片描述

请添加图片描述

rRs

设置目标文件输出到指定目录

在 make 的时候使用“O”来指定输出目录,比如“make O=out”就是设置目标文件输出到 out 目录中。

或者设置KBUILD_OUTPUT的值,指定输出目录

模块检查

uboot支持单独编译某个模块

make M=dir
或者
make SUBDIRS=dir

###架构获取

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/')
  • $(shell xxx) 执行shell命令

  • sed-e 替换命令 sed -e s/i.86/x86 表示将i.86替换为X86

  • tr ‘[:upper:]’ ‘[:lower:]’ 表示将所有大写字母得到小写字母

变量KCONFIG_CONFIG

变量KCONFIG_CONFIG ?= .config #默认是默认有,通过xxx_defconfiguboot进行配置,配置完成就有了.config,默认两者一样,defconfig是初始配置,.config是实时有效配置,图形配置修改的也是.config文件

ARCH\CPU等变量定义

在config.mk文件中

ARCH :=$(CONFIG_SYS_ARCH:"%"=%)

等号后的作用是提取变量CONFIG_SYS_ARCH中双引号的内容

sinlude

sinlude和include作用类似,makefile中是读取指定文件内容,区别是如果文件不存在sinlude不报错

架构指定

makefile中的 ARCH CPU -> config.mk中的CONFIG_SYS_ARCH -> .config文件中的CONFIG_SYS_ARCH

MAKECMDGOALS

MAKECMDGOALS 是 make 的一个环境变量,这个变量会保存你所指
定的终极目标列表,比如执行“make mx6ull_alientek_emmc_defconfig”,那么 MAKECMDGOALS
就为 mx6ull_alientek_emmc_defconfig

words函数

$(words<text>)

统计单词个数

变量build

build在scripts/Kbuild.include文件中定义

make xxx_deconfig

入口为顶层MakeFile的如下:

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

  • 有目标与之匹配,当输入“make xxx_defconfig”的时候就会匹配到%config 目标,目标“%config”依赖于 scripts_basic、outputmakefile 和 FORCE
  • 展开 #@make -f ./scripts/Makefile.build obj=scripts/kconfig xxxx_deconfig

uboot根目录执行

make mx6ull_14x14_ddr512_emmc_defconfig V=1

执行结果如下:

请添加图片描述

scripts_basic 展开:

@make -f ./scripts/Makefile.build obj=scripts/basic
//也可以没有@,视配置而定
@rm -f . tmp_quiet_recordmcount
//也可以没有@

/scripts/Makefile.build 这个文件非常重要

①、scripts_basic 目标对应的命令
@make -f ./scripts/Makefile.build obj=scripts/basic
②、%config 目标对应的命令
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

scripts/Makefile.build文件中

对于scripts_basic ->@make -f ./scripts/Makefile.build obj=scripts/basic 来说

kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
->
kbuild-dir := $(if $(filter /%,scripts/basic),scripts/basic,./scripts/basic
->
因为 scripts/basic没有 /开头的字符串,所以filter返回空,if条件不成立,
->
kbuild-dir :=./scripts/basic

继续向下推
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
->
kbuild-file :=$(if $(wildcard ./scripts/basic/Kbuild), ./scripts/basic/Kbuild, ./scripts/basic/Makefile)
—>
因为scripts/basic/下没有Kbuild文件,所以if条件不成立
kbuild-file :=./scripts/basic/Makefile
include ./scripts/basic/Makefile

__build是默认目标,如果make命令没有指定目标,那么就使用默认目标

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

疑问:为什么$(modorder-target)丢失了?
->

__build:$(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)
	@:
->

所以__build依赖有:builtin-target、lib-target、extra-y、subdir-ym 和 always
通过在make中打印这几个值得到只有always有效
always = scripts/basic/fixdep

->
__build: scripts/basic/fixdep
@:

scripts_basic 目标的作用就是编译出 scripts/basic/fixdep 这个软件.

同理,对于%config ->@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig来说

src= scripts/kconfig
kbuild-dir = ./scripts/kconfig
kbuild-file = ./scripts/kconfig/Makefile
include ./scripts/kconfig/Makefile

在上述/kconfig/Makefile文件中有目标如下

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

# Added for U-Boot (backward compatibility)
%_config: %_defconfig
	@:
	
->
$(obj)/conf = scripts/kconfig/conf

目标%_defconfig 刚好和我们输入的 xxx_defconfig 匹配,所以会执行这条规则。依赖为
$(obj)/conf,展开后就是 scripts/kconfig/conf。接下来就是检查并生成依赖 scripts/kconfig/conf。
conf 是主机软件,到这里我们就打住,不要纠结 conf 是怎么编译出来的,否则就越陷越深,太
绕了,像 conf 这种主机所使用的工具类软件我们一般不关心它是如何编译产生的。

依赖完成后就执行目标%_deconfig的命令

$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)

->
 $< 代表第一个依赖文件
 $@ 代表的是规则的目标文件
silent=-s 或为空
SRCARCH=..
Kconfig=Kconfig
->
@ scripts/kconfig/conf --defconfig=arch/../configs/xxx_defconfig Kconfig
  • xxx_defconfig是在编译时输入的文件,如mx6ull_alientek_emmc_defconfig包含一组预定义的配置选项,用

  • scripts/kconfig/conf: 处理配置任务的工具,读取 defconfig 文件并生成 .config 文件。

  • .config: 最终生成的配置文件,包含所有的配置选项及其值。

  • Kconfig: 定义了所有可能的配置选项及其依赖关系。

如上描述 scripts/kconfig/conf 是一个编译机的工具,这个工具实现了从xxx_defconfig到.config的工作。具体的工作流程为:
请添加图片描述

请添加图片描述

总而言之,xxx_defconfig->.config是利用 scripts/kconfig/conf 工具和 xxxx_defconfig文件实现的

make流程分析

PHONY += all
ifeq ($(KBUILD_EXTMOD),)#如果KBUILD_EXTEOD为空,目标_all依赖all,否则依赖modules
_all: all
else
_all: modules
endif

->
整体编译时 KBUILD_EXTMOD 为空 所以
->
_all: all
->
all: $(ALL-y)
all依赖 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
....

ALL-y 包含 u-boot.srec、u-boot.bin、u-boot.sym、
System.map、u-boot.cfg 和 binary_size_check 这几个文件。根据 uboot 的配置情况也可能包含其他的文件 比如 当CONFIG_ONENAND_U_BOOT在menuconfig中选中ONENAND时,则会有“CONFIG_ONENAND_U_BOOT=y”,相当于
ALL-y += u-boot-onenand.bin

这个就是.config 里面的配置参数的含义,这些参数其实都是变量,后面跟着变量值,会在
顶层 Makefile 或者其他 Makefile 中调用这些变量

u-boot.bin是二进制文件

->

u-boot.bin: u-boot-nodtb.bin FORCE
	$(call if_changed,copy)
	
if_changed是在scripts/Kbuild.include 中定义的一个函数

if_changed = $(if $(strip $(any-prereq) $(arg-check)),                       \
	@set -e;                                                             \
	$(echo-cmd) $(cmd_$(1));                                             \
	printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd)
	
教程中提到if_changed可以从 u-boot-nodtb.bin 生成 u-boot.bin 

->
u-boot-nodtb.bin: u-boot FORCE
	$(call if_changed,objcopy)
	$(call DO_STATIC_RELA,$<,$@,$(CONFIG_SYS_TEXT_BASE))
	$(BOARD_SIZE_CHECK)
依赖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-init u-boot-main是变量,定义如下

-> 

u-boot-init := $(head-y) =》 u-boot-init= arch/arm/cpu/armv7/start.o
u-boot-main := $(libs-y)

其中
$(head-y)跟 CPU 架构有关,我们使用的是 ARM 芯片,所以 head-y 在 arch/arm/Makefile 中
被指定为:
head-y := arch/arm/cpu/$(CPU)/start.o
CPU=armv7,因此 head-y 展开以后就是:
head-y := arch/arm/cpu/armv7/start.o

libs-y += cmd/
...
libs-y		:= $(patsubst %/, %/built-in.o, $(libs-y))  利用patsubst函数将libs-y改为所有子目录中build-in.o的集合
$(libs-y)在顶层 Makefile 中被定义为 uboot 所有子目录下 build-in.o 的集合

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

这里不太确定

整体依赖作用为:相当于将以 u-boot.lds 为链接脚本,将 arch/arm/cpu/armv7/start.o 和各个子目录下的 built-in.o 链接在一起生成 u-boot

->

各子目录下的 built-in.o 是怎么生成的
每个子目录下有一个名为.built-in.o.cmd 的文件,以 drivers/gpio/built-in.o 为例,内容如下:

cmd_drivers/gpio/built-in.o := arm-linux-gnueabihf-ld.bfd -r -o
drivers/gpio/built-in.o drivers/gpio/mxc_gpio.o


使用 ld 命令由文件 drivers/gpio/mxc_gpio.o 生成built-in.o,mxc_gpio.o 是 mxc_gpio.c 编译生成的.o 文件,这个是 NXP 的 I.MX 系列的 GPIO 驱动文件。

这里用到了 ld 的“-r”参数,参数含义如下:
-r –relocateable: 产生可重定向的输出,比如,产生一个输出文件它可再次作为‘ld’的输
入,这经常被叫做“部分链接”,当我们需要将几个小的.o 文件链接成为一个.o 文件的时候,需
要使用此选项。

最终将各个子目录中的 built-in.o 文件链接在一起就形成了 u-boot


请添加图片描述

patsubst函数

patsubst替换函数

$(patsubst <pattern>,<replacement>,<text>)

text中查找符合pattern的部分,如果找到,那么用replacement替换。

pattern可以包含通配符%,如果replacement也包含%,那么replacement中%就是pattern%代表的字符串,函数返回替换后的字符串。

wildcard函数

$(wildcard <pattern>)

返回当前目录满足pattern的文件或目录名列表

如:

SRC = $(wildcard *.c)

返回 main.c fun.c

.PHONY

.PHONY 是一个特殊的目标,用于声明一些目标是“伪目标”。伪目标不对应于实际存在的文件,而是用来执行某些命令或任务。这种声明可以避免文件名与目标名冲突,并确保这些目标总是被执行。

为什么需要 .PHONY

如果一个目标名称和某个实际存在的文件或目录名称相同,Make 可能会认为这个文件已经是最新的,从而跳过该目标的执行。通过标记目标为 .PHONY,可以确保 Make 总是执行这些目标,而不管是否存在同名文件

  1. 声明 .PHONY 目标

    .PHONY: clean all
    

    这行代码声明了 cleanall 是伪目标。这样,Make 会忽略当前目录下是否存在名为 cleanall 的文件,总是执行这些目标。

优点
  • 避免文件名冲突: .PHONY 可以防止目标名与实际存在的文件名冲突。
  • 确保目标执行: 标记为 .PHONY 的目标每次都会被执行,无论是否存在同名文件。
  • 提高可读性和可维护性: 使用 .PHONY 可以让 Makefile 更加清晰,明确哪些目标是伪目标,哪些是实际文件。

编译脚本

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12

uboot启动流程

编译后根据arch/arm/cpu/u-boot.lds生成根目录下的连接脚本u-boot.lds文件,第三行 的 ENTRY(_start)为代码入口点,定义在 arch/arm/lib/vectors.S 中,

请添加图片描述