学习正点原子教程个人记录
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_defconfig
对uboot
进行配置,配置完成就有了.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 总是执行这些目标,而不管是否存在同名文件
-
声明
.PHONY
目标.PHONY: clean all
这行代码声明了
clean
和all
是伪目标。这样,Make 会忽略当前目录下是否存在名为clean
或all
的文件,总是执行这些目标。
优点
-
避免文件名冲突:
.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
中,