本帖最后由 正点原子运营 于 2020-12-18 10:34 编辑
1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-301505-1-1.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:712557122
5)关注正点原子公众号,获取最新资料更新
第十章U-Boot顶层Makefile详解
上一章我们详细的讲解了uboot的使用方法,其实就是各种命令的使用,学会uboot使用以后就可以尝试移植uboot到自己的开发板上了。在移植之前我们先来分析uboot顶层Makefile文件,理清uboot的编译流程。本章我们使用正点原子提供的uboot源码,先了解uboot的目录结构,再来分析顶层Makefile文件。
1.1 U-Boot工程目录分析讲解顶层Makefile文件之前,我们需要先了解uboot工程目录。在22.2小节我们已经将uboot解压到了alientek_uboot下,如下图所示:
图23.1.1解压后的uboot 上图中除了正点原子提供的uboot源码压缩包u-boot-altk-xilinx-v2018.3.tar.gz文件外,其他的文件和文件夹都是解压出来的uboot源码。这些文件夹或文件的含义如下表所示: 表22.4.9.1 uboot目录列表 上表中的很多文件夹和文件我们都不需要去关心,我们要关注的文件夹或文件如下: 1. arch文件夹 这个文件夹里面存放着和架构有关的文件,如下图所示:
图23.1.2 arch文件夹 从上图可以看出有很多架构,比如arm、avr32、m68k等,我们现在用的是ARM芯片,所以只需要关心arm文件夹即可,进入arm文件夹里面内容如下图所示:
图 23.1.3 arm文件夹 mach开头的文件夹是跟具体的设备有关的,比如“mach-exynos”就是跟三星的exyons系列CPU有关的文件。我们使用的是ZYNQ,所以要关注“mach-zynq”这个文件夹。另外“cpu”这个文件夹也是和cpu架构有关的,文件夹内容如下图所示:
图 23.1.4 cpu文件夹 从上图可以看出有多种ARM架构相关的文件夹,ZYNQ使用的Cortex-A9内核,Cortex-A9属于armv7,所以我们要关心“armv7”这个文件夹。cpu文件夹里面有个名为“u-boot.lds”的链接脚本文件,这个就是ARM芯片所使用的u-boot链接脚本文件。armv7文件夹里面的文件都是跟ARMV7架构有关的,是我们分析uboot启动源码的时候需要重点关注的。 2. board文件夹 board文件夹就是和具体的开发板有关的,打开此文件夹,里面全是不同的板子,毫无疑问,正点原子的开发板肯定也在里面(正点原子添加的),borad文件夹里面有个名为“xilinx”的文件夹,如下图所示:
图 23.1.5 xilinx文件夹 所有使用xilinx芯片的板子都放到此文件夹中。该文件夹下有3个文件夹,这3个文件夹对应3种类型的开发板。“microblaze-generic”表示使用microblaze软核IP的FPGA开发板、“zynq”表示使用ZYNQ-7000系列芯片的开发板、“zynqmp”表示使用ZYNQ MP系列芯片的开发板。正点原子的领航者开发板是ZYNQ-7000系列的开发板,我们后面移植uboot的时候就是参考的Xilinx官方的开发板,也就是要参考“zynq”这个文件夹来定义我们的开发板。 3. configs文件夹 此文件夹为uboot配置文件,uboot是可配置的,但是你要是自己从头开始一个一个项目的配置,那就太麻烦了,因此一般半导体或者开发板厂商都会制作好一个配置文件。我们可以在这个做好的配置文件基础上来添加自己想要的功能,这些半导体厂商或者开发板厂商制作好的配置文件统一命名为“xxx_defconfig”,xxx表示开发板名字,这些defconfig文件都存放在configs文件夹,因此,Xilinx官方开发板和正点原子的开发板配置文件肯定也在这个文件夹中,如下图所示:
图 23.1.6 zynq系列开发板配置文件 上图中的以“zynq”开头的文件都是zynq系列的配置文件。正点原子领航者开发板所对应的uboot默认配置文件是zynq_altk_defconfig。 4. Makefile文件 这个是顶层Makefile文件,Makefile是支持嵌套的,也就是顶层Makefile可以调用子目录中的Makefile文件。Makefile嵌套在大项目中很常见,一般大项目里面所有的源代码都不会放到同一个目录中,各个功能模块的源代码都是分开的,各自存放在各自的目录中。每个功能模块目录下都有一个Makefile,这个Makefile只处理本模块的编译链接工作,这样所有的编译链接工作就不用全部放到一个Makefile中,可以使得Makefile变得简洁明了。 uboot源码根目录下的Makefile是顶层Makefile,它会调用其它的模块的Makefile文件,比如drivers/adc/Makefile。当然了,顶层Makefile要做的工作可远不止调用子目录Makefile这么简单,关于顶层Makefile的内容我们稍后会有详细的讲解。 5. README README文件描述了uboot的详细信息,包括uboot该如何编译、uboot中各文件夹的含义、相应的命令等等。建议大家详细的阅读此文件,可以进一步增加对uboot的认识。 关于uboot根目录中的文件和文件夹的含义就讲解到这里,接下来就要开始分析顶层Makefile了。 1.2 VScode创建uboot工程为了方便查看uboot源码,我们使用VScode创建uboot工程。打开VScode,选择:文件->打开文件夹…,选中uboot文件夹,如下图所示:
图 23.2.1选择uboot源码文件夹 打开uboot目录以后,VSCode界面如下图所示:
图 23.2.2 VScode界面 点击“文件->将工作区另存为…”,打开保存工作区对话框,将工作区保存到uboot源码根目录下,设置文件名为“uboot”,如下图所示:
图23.2.3保存工作区 保存成功以后就会在uboot源码根目录下存在一个名为uboot.code-workspace的文件。这样一个完整的VSCode工程就建立起来了。但是这个VSCode工程包含了uboot的所有文件,uboot中有些文件是不需要的,比如arch目录下是各种架构的文件夹,如下图所示:
图 23.2.4 arch目录 在arch目录下,我们只需要arm文件夹,所以需要将其它的目录从VSCode中给屏蔽掉,比如将arch/nios2这个目录给屏蔽掉。 在VSCode上建名为“.vscode”的文件夹,如图31.2.5所示:
图 23.2.5新建.vscode文件夹 输入新建文件夹的名字,完成以后如下图所示。
图 23.2.6新建的.vscode文件夹 在.vscode文件夹中新建一个名为“settings.json”的文件,然后在settings.json中输入如下内容: 示例代码settings.json文件代码 - {
- "search.exclude": {
- "**/node_modules": true,
- "**/bower_components": true,
- },
- "files.exclude": {
- "**/.git": true,
- "**/.svn": true,
- "**/.hg": true,
- "**/CVS": true,
- "**/.DS_Store": true,
- }
- }
复制代码结果如下图所示:
图 23.2.7 settings.json文件默认内容 其中"search.exclude"里面是需要在搜索结果中排除的文件或者文件夹,"files.exclude"是左侧工程目录中需要排除的文件或者文件夹。我们需要将arch/nios2文件夹下的所有文件从搜索结果和左侧的工程目录中都排除掉,因此在"search.exclude"和"files.exclude"中输入如下图所示内容:
图 23.2.8排除arch/nios2目录 此时再看一下左侧的工程目录,发现arch目录下没有nios2这个文件夹了,说明nios2这个文件夹被排除掉了,如下图所示:
图 23.2.9 arch/avr32目录排除 我们只是在"search.exclude"和"files.exclude"中加入了:rch/nios2":true,冒号前面的是要排除的文件或者文件夹,冒号后面为是否将文件排除,true表示排除,false表示不排除。用这种方法即可将不需要的文件,或者文件夹排除掉,对于本章我们分析uboot而言,在"search.exclude"和"files.exclude"中需要输入的完成的内容如下: 示例代码settings.json文件代码 - "**/*.o": true,
- "**/*.su": true,
- "**/*.cmd": true,
- "arch/arc": true,
- "arch/m68k": true,
- "arch/microblaze": true,
- "arch/mips": true,
- "arch/nds32": true,
- "arch/nios2": true,
- "arch/powerpc": true,
- "arch/sandbox": true,
- "arch/sh": true,
- "arch/xtensa": true,
- "arch/x86": true,
- "arch/arm/mach*": true,
- "arch/arm/mach-zynq": false,
- "arch/arm/cpu/arm11*": true,
- "arch/arm/cpu/arm720t": true,
- "arch/arm/cpu/arm9*": true,
- "arch/arm/cpu/armv7m": true,
- "arch/arm/cpu/armv8": true,
- "arch/arm/cpu/pxa": true,
- "arch/arm/cpu/sa1100": true,
- "board/[a-w]*": true,
- "board/[y-z]*": true,
- "board/[0-9]*": true,
- "board/[A-Z]*": true,
- "board/xe*": true,
- "board/xilinx/m*": true,
- "configs/[a-y]*": true,
- "configs/[A-Z]*": true,
- "configs/[0-9]*": true,
复制代码上述代码用到了通配符“*”,比如“**/*.o”表示所有.o结尾的文件。“configs/[a-y]*”表示configs目录下所有以‘a’~‘y’开头的文件或者文件夹。上述配置只是排除了一部分文件夹,大家在实际的使用中可以根据自己的实际需求来选择将哪些文件或者文件夹排除掉。排除以后我们的工程就会清爽很多,搜索的时候也不会跳出很多文件了。 1.3 U-Boot顶层Makefile分析在阅读uboot源码之前,肯定是要先看一下顶层Makefile,分析gcc版本代码的时候一定是先从顶层Makefile开始的,然后再是子Makefile,这样通过层层分析Makefile即可了解整个工程的组织结构。顶层Makefile也就是uboot根目录下的Makefile文件,由于顶层Makefile文件内容比较多,所以我们将其分开来看。 1.3.1 版本号顶层Makefile一开始是版本号,内容如下(为了方便分析,顶层Makefile代码段前段行号采用Makefile中的行号,因为uboot会更新,因此行号可能会与你所看的顶层Makefile有所不同): - VERSION = 2018
- PATCHLEVEL = 01
- SUBLEVEL =
- EXTRAVERSION =
- NAME =
复制代码VERSION是主版本号,PATCHLEVEL是补丁版本号,SUBLEVEL是次版本号,这三个一起构成了uboot的版本号,比如当前的uboot版本号就是“2018.01”。EXTRAVERSION是附加版本信息,NAME是和名字有关的,一般不使用这两个。 1.3.2 MAKEFLAGS变量make是支持递归调用的,也就是在Makefile中使用“make”命令来执行其他的Makefile文件,一般都是子目录中的Makefile文件。假如在当前目录下存在一个“subdir”子目录,这个子目录中又有其对应的Makefile文件,那么这个工程在编译的时候其主目录中的Makefile就可以调用子目录中的Makefile,以此来完成所有子目录的编译。 有时候我们需要向子make传递变量,这个时候使用“export”来导出要传递给子make的变量即可,如果不希望哪个变量传递给子make的话就使用“unexport”来声明不导出: export VARIABLE …… //导出变量给子make 。 unexport VARIABLE…… //不导出变量给子make。 有两个特殊的变量:“SHELL”和“MAKEFLAGS”,这两个变量除非使用“unexport”声明,否则的话在整个make的执行过程中,它们的值始终自动的传递给子make。在uboot的主Makefile中有如下代码: 20 MAKEFLAGS += -rR --include-dir=$(CURDIR) 上述代码使用“+=”来给变量MAKEFLAGS追加了一些值,“-rR”表示禁止使用内置的隐含规则和变量定义,“--include-dir”指明搜索路径,”$(CURDIR)”表示当前目录。 1.3.3 命令输出为了更好的体会下面讲解的内容,我们在讲解命令输出之前先介绍下不使用Petalinux时通常的编译uboot方式,这种方式了解就可以了,不推荐使用。在不使用Petalinux的情况下,可以使用如下的make命令来编译uboot(需要提醒的是在使用下面的命令前先建立Petalinux的环境变量,否则会提示arm-linux-gnueabihf-找不到等错误): - make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
- make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zynq_altk_defconfig
- make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8
复制代码这三条命令中“ARCH=arm”设置目标为arm架构,CROSS_COMPILE指定所使用的交叉编译器,只需要指明编译器前缀就行了,比如arm-linux-gnueabihf-gcc编译器的前缀就是“arm-linux-gnueabihf-”。第一条命令相当于“make distclean”,目的是清除工程,一般在第一次编译的时候最好清理一下工程。第二条指令相当于“make zynq_altk_defconfig”,用于配置uboot,配置文件为zynq_altk_defconfig。uboot支持其它的架构和外设,比如USB、网络、SD卡等。这些都是可以配置的,需要什么功能就使能什么功能。所以在编译uboot之前,一定要根据自己的需求配置uboot。zynq_altk_defconfig就是正点原子针对领航者开发板编写的配置文件,这个配置文件在alientek_uboot/configs目录中。最后一条指令相当于“make -j8”也就是使用8核来编译uboot。“-j”参数用于设置主机使用多少个核来编译uboot,设置的核越多,编译速度越快。-j8表示使用8个核编译uboot,具体设置多少个要根据自己的虚拟机或者电脑配置,如果你给VMware分配了4个核,那么最多只能使用-j4。 当这三条命令执行完以后uboot也就编译成功了,如下图所示:
图 23.3.1 编译完成 可以看到uboot默认编译是不会在终端中显示完整的命令,都是短命令,很简洁。 在终端中输出短命令虽然看起来很清爽,但是不利于分析uboot的编译过程。可以通过设置变量“V=1”来实现完整的命令输出,也就是使用命令“make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8”,这个在调试uboot的时候很有用,结果如下图所示:
图 23.3.2终端完整命令输出 顶层Makefile中控制命令输出的代码如下: 示例代码顶层Makefile代码 - <div style="text-align: left;"> ifeq ("$(origin V)","command line")</div><div style="text-align: left;">KBUILD_VERBOSE =$(V)</div><div style="text-align: left;">endif</div><div style="text-align: left;">ifndef KBUILD_VERBOSE</div><div style="text-align: left;">KBUILD_VERBOSE =0</div><div style="text-align: left;">endif</div><div style="text-align: left;">
- </div><div style="text-align: left;">ifeq ($(KBUILD_VERBOSE),1)</div><div style="text-align: left;">quiet =</div><div style="text-align: left;">Q =</div><div style="text-align: left;">else</div><div style="text-align: left;">quiet=quiet_</div><div style="text-align: left;">Q =@</div><div style="text-align: left;">endif</div>
复制代码上述代码中先使用ifeq来判断"$(origin V)"和"commandline"是否相等。这里用到了Makefile中的函数origin,origin和其他的函数不一样,它不操作变量的值,origin用于告诉你变量是哪来的,语法为: $(origin <variable>) variable是变量名,origin函数的返回值就是变量来源,因此$(origin V)就是变量V的来源。如果变量V是在命令行定义的那么它的来源就是"command line",这样"$(originV)"和"command line"就相等了。当这两个相等的时候变量KBUILD_VERBOSE就等于V的值,比如在命令行中输入“V=1“的话那么KBUILD_VERBOSE=1。如果没有在命令行输入V的话KBUILD_VERBOSE=0。 第80行判断KBUILD_VERBOSE是否为1,如果KBUILD_VERBOSE为1的话变量quiet和Q都为空,如果KBUILD_VERBOSE=0的话变量quiet为“quiet_“,变量Q为“@”,综上所述: V=1的话: KBUILD_VERBOSE=1 quiet= 空 Q= 空 V=0或者命令行不定义V的话: KBUILD_VERBOSE=0 quiet= quiet_ Q= @ Makefile中会用到变量quiet和Q来控制编译的时候是否在终端输出完整的命令,在顶层Makefile中有很多如下所示的命令: $(Q)$(MAKE) $(build)=tools 如果V=0的话上述命令展开就是“ @ make $(build)=tools”,make在执行的时候默认会在终端输出命令,但是在命令前面加上“@”就不会在终端输出命令了。当V=1的时候Q就为空,上述命令就是“make $(build)=tools”,因此在make执行的过程,命令会被完整的输出在终端上。 有些命令会有两个版本,比如: quiet_cmd_sym ?= SYM $@ cmd_sym ?= $(OBJDUMP) -t $< > $@ sym命令分为“quiet_cmd_sym”和“cmd_sym”两个版本,这两个命令的功能都是一样的,区别在于make执行的时候输出的命令不同。quiet_cmd_xxx命令输出信息少,也就是短命令,而cmd_xxx命令输出信息多,也就是完整的命令。 如果变量quiet为空的话,整个命令都会输出。 如果变量quiet为“quiet_”的话,仅输出短版本。 如果变量quiet为“silent_”的话,整个命令都不会输出。 1.3.4 静默输出上一小节讲了,设置V=0或者在命令行中不定义V的话,编译uboot的时候终端中显示的短命令,但是还是会有命令输出,有时候我们在编译uboot的时候不需要输出命令,这个时候就可以使用uboot的静默输出功能。编译的时候使用“make -s”即可实现静默输出,顶层Makefile中相应的代码如下: 示例代码顶层Makefile代码 - # 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
- export quiet Q KBUILD_VERBOSE
复制代码第91行判断当前正在使用的编译器版本号是否为4.x,判断$(filter 4.%,$(MAKE_VERSION))和“”(空)是否相等,如果不相等的话就成立,执行里面的语句。也就是说$(filter 4.%,$(MAKE_VERSION))不为空的话条件就成立,这里用到了Makefile中的filter函数,这是个过滤函数,函数格式如下: $(filter <pattern...>,<text>) filter函数表示以pattern模式过滤text字符串中的单词,仅保留符合模式pattern的单词,可以有多个模式。函数返回值就是符合pattern的字符串。因此$(filter 4.%,$(MAKE_VERSION))的含义就是在字符串“MAKE_VERSION”中找出符合“4.%”的字符(%为通配符),MAKE_VERSION是MAKE工具的版本号,我们当前使用的MAKE工具版本号为4.1(make-v命令查看),所以肯定可以找出“4.%”。因此$(filter4.%,$(MAKE_VERSION))不为空,条件成立,执行92~94行的语句。 第92行也是一个判断语句,如果$(filter %s ,$(firstword x$(MAKEFLAGS)))不为空的话条件成立,变量quiet等于“silent_”。这里也用到了函数filter,在$(firstword x$(MAKEFLAGS)))中过滤出符合“%s”的单词。到了函数firstword,函数firstword是获取首单词,函数格式如下: $(firstword <text>) firstword函数用于取出text字符串中的第一个单词,函数的返回值就是获取到的单词。当使用“make -s”编译的时候,“-s”会作为MAKEFLAGS变量的一部分传递给Makefile。在顶层Makfile中添加如下图所示的代码:
图 23.3.3顶层Makefile添加代码 上图中的两行代码用于输出$(firstwordx$(MAKEFLAGS))的结果,最后执行命令“make -s mytest”,结果如下图所示:
图 23.3.4修改顶层Makefile后的执行结果 从上图可以看出第一个单词是“xrRs”,将$(filter %s ,$(firstword x$(MAKEFLAGS)))展开就是$(filter%s, xrRs),而$(filter %s, xrRs)的返回值肯定不为空,条件成立,quiet=silent_。 第101行 使用export导出变量quiet、Q和KBUILD_VERBOSE。 1.3.5 设置编译结果输出目录uboot可以将编译出来的目标文件输出到单独的目录中,在make的时候使用“O”来指定输出目录,比如“make O=out”就是设置目标文件输出到out目录中。这么做是为了将源文件和编译产生的文件分开,当然也可以不指定O参数,不指定的话源文件和编译产生的文件都在同一个目录内,一般我们不指定O参数。顶层Makefile中相关的代码如下: 示例代码顶层Makefile代码 - # kbuild supports saving outputfiles in a separate directory.
- # To locate output files in aseparate directory two syntaxes are supported.
- # In both cases the workingdirectory must be the root of the kernel src.
- # 1) O=
- # Use "make O=dir/to/store/output/files/"
- #
- # 2) Set KBUILD_OUTPUT
- # Set the environment variableKBUILD_OUTPUT to point to the directory
- # where the output files shall beplaced.
- # export KBUILD_OUTPUT=dir/to/store/output/files/
- # make
- #
- # The O= assignment takes precedence over the KBUILD_OUTPUTenvironment
- # variable.
- # KBUILD_SRC is set on invocationof make in OBJ directory
- # KBUILD_SRC is not intended tobe used by the regular user (for now)
- ifeq ($(KBUILD_SRC),)
- # OK, Make called in directory where kernel src resides
- # Do we want to locate outputfiles in a separate directory?
- ifeq ("$(originO)", "command line")
- KBUILD_OUTPUT := $(O)
- endif
- # That's our default target whennone is given on the command line
- PHONY := _all
- _all:
- # Cancel implicit rules on topMakefile
- $(CURDIR)/Makefile Makefile: ;
- ifneq ($(KBUILD_OUTPUT),)
- # Invoke a second make in theoutput directory, passing relevantvariables
- # check that the output directoryactually exists
- saved-output := $(KBUILD_OUTPUT)
- KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) \
- && /bin/pwd)
- ......
- endif # ifneq ($(KBUILD_OUTPUT),)
- endif # ifeq ($(KBUILD_SRC),)
复制代码第124行判断“O”是否来自于命令行,如果来自命令行的话条件成立,KBUILD_OUTPUT就为$(O),因此变量KBUILD_OUTPUT就是输出目录。 第135行判断KBUILD_OUTPUT是否为空。 第139行调用mkdir命令,创建KBUILD_OUTPUT目录,并且将创建成功以后的绝对路径赋值给KBUILD_OUTPUT。至此,通过O指定的输出目录就存在了。 1.3.6 代码检查uboot支持代码检查,使用命令“make C=1”使能代码检查,检查那些需要重新编译的文件。“make C=2”用于检查所有的源码文件,顶层Makefile中的代码如下: 示例代码31.3.6.1 顶层Makefile代码 - # Call a source code checker (by default, "sparse") as part of the
- # C compilation.
- #
- # Use 'make C=1' to enable checking of only re-compiled files.
- # Use 'make C=2' to enable checking of *all* source files, regardless
- # of whether they are re-compiled or not.
- #
- # See the file "Documentation/sparse.txt" for more details, including
- # where to get the "sparse" utility.
- ifeq ("$(originC)", "command line")
- KBUILD_CHECKSRC = $(C)
- endif
- ifndef KBUILD_CHECKSRC
- KBUILD_CHECKSRC = 0
- endif
复制代码第176行判断C是否来源于命令行,如果C来源于命令行,那就将C赋值给变量KBUILD_CHECKSRC,如果命令行没有C的话KBUILD_CHECKSRC就为0。 1.3.7 模块编译在uboot中允许单独编译某个模块,使用命令“make M=dir”即可,旧语法“make SUBDIRS=dir”也是支持的。顶层Makefile中的代码如下: 示例代码顶层Makefile代码 - # Use make M=dir to specify directory of external module to build
- # Old syntax make ... SUBDIRS=$PWD is still supported
- # Setting the environmentvariable KBUILD_EXTMOD take precedence
- ifdef SUBDIRS
- KBUILD_EXTMOD ?= $(SUBDIRS)
- endif
- ifeq ("$(originM)", "command line")
- KBUILD_EXTMOD := $(M)
- endif
- # If building an external modulewe do not care about the all: rule
- # but instead _all depend onmodules
- PHONY += all
- ifeq ($(KBUILD_EXTMOD),)
- _all: all
- else
- _all: modules
- endif
- ifeq ($(KBUILD_SRC),)
- # building in the source tree
- srctree := .
- else
- ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR)))
- # building in a subdirectoryof the source tree
- srctree := ..
- else
- srctree := $(KBUILD_SRC)
- endif
- endif
- objtree := .
- src := $(srctree)
- obj := $(objtree)
- VPATH := $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD))
- export srctree objtree VPATH
复制代码第186行判断是否定义了SUBDIRS,如果定义了SUBDIRS,变量KBUILD_EXTMOD=SUBDIRS,这里是为了支持老语法“makeSUBIDRS=dir” 第190行判断是否在命令行定义了M,如果定义了的话KBUILD_EXTMOD=$(M)。 第197行判断KBUILD_EXTMOD时候为空,如果为空的话目标_all依赖all,因此要先编译出all。否则的话默认目标_all依赖modules,要先编译出modules,也就是编译模块。一般情况下我们不会在uboot中编译模块,所以此处会编译all这个目标。 第203行判断KBUILD_SRC是否为空,如果为空的话就设置变量srctree为当前目录,即srctree为“.”,一般不设置KBUILD_SRC。 第214行设置变量objtree为当前目录。 第215和216行分别设置变量src和obj,都为当前目录。 第218行设置VPATH。 第220行导出变量scrtree、objtree和VPATH。 1.3.8 获取主机架构和系统- 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 HOSTARCHHOSTOS
复制代码接下来顶层Makefile会获取主机架构和系统,也就是我们电脑的架构和系统,代码如下: 示例代码顶层Makefile代码 第227行定义了一个变量HOSTARCH,用于保存主机架构,这里调用shell命令“uname -m”获取主机CPU体系架构的名称,结果如下图所示: file:///C:/Users/WCY/AppData/Local/Temp/msohtmlclip1/01/clip_image036.jpg 图 23.3.5 uname -m命令 从上图可以看出当前电脑主机架构为“x86_64”,shell中的“|”表示管道,意思是将左边的输出作为右边的输入,sed -e是替换命令,“sed -e s/i.86/x86/”表示将管道输入的字符串中的“i.86”替换为“x86”,其他的“sed-s”命令同理。对于我笔者的电脑而言,HOSTARCH=x86_64。 第237行定义了变量HOSTOS,此变量用于保存主机操作系统OS的值,先使用shell命令“name -s”可以来获取主机OS,结果如下图所示: file:///C:/Users/WCY/AppData/Local/Temp/msohtmlclip1/01/clip_image038.jpg 图 23.3.6 uname -s命令 从上图可以看出此时笔者的主机的OS为“Linux”,使用管道将“Linux”作为后面“tr '[:upper:]' '[:lower:]'”的输入,“tr'[:upper:]' '[:lower:]'”表示将所有的大写字母替换为小写字母,因此得到“linux”。最后同样使用管道,将“linux”作为“sed -e 's/\(cygwin\).*/cygwin/'”的输入,用于将cygwin.*替换为cygwin。因此,HOSTOS=linux。 第240行导出HOSTARCH=x86_64,HOSTOS=linux。 1.3.9 设置目标架构、交叉编译器和配置文件编译uboot的时候需要设置目标板架构和交叉编译器,“make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-”就是用于设置ARCH和CROSS_COMPILE,在顶层Makefile中代码如下: 示例代码顶层Makefile代码 - # set default to nothing fornative builds
- ifeq ($(HOSTARCH),$(ARCH))
- CROSS_COMPILE ?=
- endif
- KCONFIG_CONFIG ?= .config
- export KCONFIG_CONFIG
复制代码第245行判断HOSTARCH和ARCH这两个变量是否相等,主机架构(变量HOSTARCH)是x86_64,而我们编译的是ARM版本uboot,肯定不相等,所以需要设置CROS_COMPILE= arm-linux-gnueabihf-。从上面的示例代码可以看出,手动编译的话每次编译uboot的时候都要在make命令后面设置ARCH和CROS_COMPILE,使用起来很麻烦,可以直接修改顶层Makefile,在里面加入ARCH和CROSS_COMPILE的定义,如下图所示:
图 23.3.7定义ARCH和CROSS_COMPILE 按照上图所示,直接在顶层Makefile里面定义ARCH和CROSS_COMPILE,这样就不用每次编译的时候都要在make命令后面定义ARCH和CROSS_COMPILE。因为我们使用Petalinux工具,使用Petalinux工具编译uboot很方便,所以就不需要在顶层Makefile里面定义ARCH和CROSS_COMPILE。 第249行定义变量KCONFIG_CONFIG,uboot是可以配置的,这里设置配置文件为.config。.config默认是没有的,需要使用命令“make xxx_defconfig”对uboot进行配置,配置完成以后就会在uboot根目录下生成.config。默认情况下.config和xxx_defconfig内容是一样的,因为.config就是从xxx_defconfig复制过来的。如果后续自行调整了uboot的一些配置参数,那么这些新的配置参数就添加到了.config中,而不是xxx_defconfig。相当于xxx_defconfig只是一些初始配置,而.config里面的才是实时有效的配置。 1.3.10 调用scripts/Kbuild.include主Makefile会调用文件scripts/Kbuild.include这个文件,顶层Makefile中代码如下: 示例代码顶层Makefile代码 - # We need some genericdefinitions (do not try to remake the file).
- scripts/Kbuild.include: ;
- include scripts/Kbuild.include
复制代码该段代码中使用“include”包含了文件scripts/Kbuild.include,此文件里面定义了很多变量,如下图所示:
图 23.3.8 Kbuild.include文件部分内容 在uboot的编译过程中会用到scripts/Kbuild.include中的这些变量,后面用到的时候再分析。 1.3.11 交叉编译工具变量设置上面我们只是设置了CROSS_COMPILE的名字,但是交叉编译器其他的工具还没有设置,顶层Makefile中相关代码如下: 示例代码顶层Makefile代码 - # Make variables (CC, etc...)
- AS = $(CROSS_COMPILE)as
- # Always use GNU ld
- ifneq ($(shell $(CROSS_COMPILE)ld.bfd -v 2> /dev/null),)
- LD = $(CROSS_COMPILE)ld.bfd
- else
- LD = $(CROSS_COMPILE)ld
- endif
- CC = $(CROSS_COMPILE)gcc
- CPP = $(CC) -E
- AR = $(CROSS_COMPILE)ar
- NM = $(CROSS_COMPILE)nm
- LDR = $(CROSS_COMPILE)ldr
- STRIP = $(CROSS_COMPILE)strip
- OBJCOPY = $(CROSS_COMPILE)objcopy
- OBJDUMP = $(CROSS_COMPILE)objdump
复制代码 1.3.12 导出其他变量接下来在顶层Makefile会导出很多变量,代码如下: 示例代码顶层Makefile代码 - export VERSION PATCHLEVEL SUBLEVEL UBOOTRELEASEUBOOTVERSION
- export ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR
- export CONFIG_SHELL HOSTCC HOSTCFLAGS HOSTLDFLAGSCROSS_COMPILE AS LD CC
- export CPP AR NM LDR STRIP OBJCOPY OBJDUMP
- export MAKE AWK PERL PYTHON
- export HOSTCXX HOSTCXXFLAGS DTC CHECK CHECKFLAGS
- export KBUILD_CPPFLAGS NOSTDINC_FLAGS UBOOTINCLUDEOBJCOPYFLAGS LDFLAGS
- export KBUILD_CFLAGS KBUILD_AFLAGS
复制代码这些变量中大部分都已经在前面定义了,我们重点来看一下下面这几个变量: ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR 这7个变量在顶层Makefile是找不到的,说明这7个变量是在其他文件里面定义的,先来看一下这7个变量都是什么内容,在顶层Makefile中输入如下图所示的内容:
图 23.3.9输出变量值 修改好顶层Makefile以后执行如下命令: make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mytest 结果如下图所示:
图 23.3.10变量结果 从上图可以看到这7个变量的值,这7个变量是从哪里来的呢?在uboot根目录下有个文件叫做config.mk,这7个变量就是在config.mk里面定义的,打开config.mk内容如下: 示例代码config.mk代码 - #
- # (C) Copyright 2000-2013
- # Wolfgang Denk, DENX SoftwareEngineering, wd@denx.de.
- #
- # SPDX-License-Identifier: GPL-2.0+
- #
- #########################################################################
- # This file is included from ./Makefile and spl/Makefile.
- # Clean the state to avoid thesame flags added twice.
- #
- # (Tegra needs different flags for SPL.
- # That's the reason why this file must be included from spl/Makefile too.
- # If we did not have Tegra SoCs, build system would be much simpler...)
- PLATFORM_RELFLAGS :=
- PLATFORM_CPPFLAGS :=
- PLATFORM_LDFLAGS :=
- LDFLAGS :=
- LDFLAGS_FINAL :=
- OBJCOPYFLAGS :=
- # clear VENDOR for tcsh
- VENDOR :=
- #########################################################################
- ARCH := $(CONFIG_SYS_ARCH:"%"=%)
- CPU := $(CONFIG_SYS_CPU:"%"=%)
- ifdef CONFIG_SPL_BUILD
- ifdef CONFIG_TEGRA
- CPU := arm720t
- endif
- endif
- BOARD := $(CONFIG_SYS_BOARD:"%"=%)
- ifneq ($(CONFIG_SYS_VENDOR),)
- VENDOR := $(CONFIG_SYS_VENDOR:"%"=%)
- endif
- ifneq ($(CONFIG_SYS_SOC),)
- SOC := $(CONFIG_SYS_SOC:"%"=%)
- endif
- # Some architecture config.mk files need to know what CPUDIR is set to,
- # so calculate CPUDIR beforeincluding ARCH/SOC/CPU config.mk files.
- # Check if arch/$ARCH/cpu/$CPU exists, otherwise assume arch/$ARCH/cpu contains
- # CPU-specific code.
- CPUDIR=arch/$(ARCH)/cpu$(if $(CPU),/$(CPU),)
- sinclude $(srctree)/arch/$(ARCH)/config.mk
- sinclude $(srctree)/$(CPUDIR)/config.mk
- ifdef SOC
- sinclude $(srctree)/$(CPUDIR)/$(SOC)/config.mk
- endif
- ifneq ($(BOARD),)
- ifdef VENDOR
- BOARDDIR = $(VENDOR)/$(BOARD)
- else
- BOARDDIR = $(BOARD)
- endif
- endif
- ifdef BOARD
- sinclude $(srctree)/board/$(BOARDDIR)/config.mk # include board specific rules
- endif
- ifdef FTRACE
- PLATFORM_CPPFLAGS += -finstrument-functions -DFTRACE
- endif
- # Allow use of stdint.h if available
- ifneq ($(USE_STDINT),)
- PLATFORM_CPPFLAGS += -DCONFIG_USE_STDINT
- endif
- #########################################################################
- RELFLAGS := $(PLATFORM_RELFLAGS)
- PLATFORM_CPPFLAGS += $(RELFLAGS)
- PLATFORM_CPPFLAGS += -pipe
- LDFLAGS += $(PLATFORM_LDFLAGS)
- LDFLAGS_FINAL += -Bstatic
- export PLATFORM_CPPFLAGS
- export RELFLAGS
- export LDFLAGS_FINAL
- export CONFIG_STANDALONE_LOAD_ADDR
复制代码第25行定义变量ARCH,值为$(CONFIG_SYS_ARCH:"%"=%),也就是提取CONFIG_SYS_ARCH里面双引号“”之间的内容。比如CONFIG_SYS_ARCH=“arm”的话,ARCH=arm。 第26行定义变量CPU,值为$(CONFIG_SYS_CPU:"%"=%)。 第32行定义变量BOARD,值为(CONFIG_SYS_BOARD:"%"=%)。 第34行定义变量VENDOR,值为$(CONFIG_SYS_VENDOR:"%"=%)。 第37行定义变量SOC,值为$(CONFIG_SYS_SOC:"%"=%)。 第44行定义变量CPUDIR,值为arch/$(ARCH)/cpu$(if$(CPU),/$(CPU),)。 第46行sinclude和include的功能类似,在Makefile中都是读取指定文件内容,这里读取文件$(srctree)/arch/$(ARCH)/config.mk的内容。sinclude读取的文件如果不存在的话不会报错。 第47行读取文件$(srctree)/$(CPUDIR)/config.mk的内容。 第50行读取文件$(srctree)/$(CPUDIR)/$(SOC)/config.mk的内容。 第54行定义变量BOARDDIR,如果定义了VENDOR那么BOARDDIR=$(VENDOR)/$(BOARD),否则的BOARDDIR=$(BOARD)。 第60行读取文件$(srctree)/board/$(BOARDDIR)/config.mk。 接下来需要找到CONFIG_SYS_ARCH、CONFIG_SYS_CPU、CONFIG_SYS_BOARD、CONFIG_SYS_VENDOR和CONFIG_SYS_SOC这5个变量的值。这5个变量在uboot根目录下的.config文件中有定义,定义如下: 示例代码.config文件代码 - CONFIG_SYS_ARCH="arm"
- CONFIG_SYS_CPU="armv7"
- CONFIG_SYS_SOC="zynq"
- CONFIG_SYS_VENDOR="xilinx"
- CONFIG_SYS_BOARD="zynq"
- CONFIG_SYS_CONFIG_NAME="zynq_altk"
复制代码根据这段代码可知: - ARCH = arm
- CPU = armv7
- BOARD = zynq
- VENDOR = xilinx
- SOC = zynq
- CPUDIR = arch/arm/cpu/armv7
- BOARDDIR = xilinx/zynq
- 在config.mk中读取的文件有:
- arch/arm/config.mk
- arch/arm/cpu/armv7/config.mk
- arch/arm/cpu/armv7/zynq/config.mk (此文件不存在)
- board/xilinx/zynq/config.mk (此文件不存在)
复制代码 1.3.13 make xxx_defconfig过程在编译uboot之前要使用“make xxx_defconfig”命令来配置uboot,那么这个配置过程是如何运行的呢?在顶层Makefile中有如下代码: 示例代码顶层Makefile代码段 - # To make sure we do not include .config forany of the *config targets
- # catch them early, and hand them over toscripts/kconfig/Makefile
- # It is allowed to specify more targets whencalling make, including
- # mixing *config targets and build targets.
- # For example 'make oldconfig all'.
- # Detect when mixed targets is specified, andmake a second invocation
- # of make so .config is not included in thiscase either (for *config).
-
- version_h :=include/generated/version_autogenerated.h
- timestamp_h :=include/generated/timestamp_autogenerated.h
-
- no-dot-config-targets := clean clobber mrproperdistclean \
- help %docs check% coccicheck \
- ubootversion backup tests
-
- config-targets := 0
- mixed-targets := 0
- dot-config := 1
-
- ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),)
- ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),)
- dot-config := 0
- endif
- endif
-
- ifeq ($(KBUILD_EXTMOD),)
- ifneq ($(filter config %config,$(MAKECMDGOALS)),)
- config-targets := 1
- ifneq ($(words $(MAKECMDGOALS)),1)
- mixed-targets := 1
- endif
- endif
- endif
-
- ifeq ($(mixed-targets),1)
- # ===========================================================================
- # We're called with mixed targets (*configand build targets).
- # Handle them one by one.
-
- 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)
- # ===========================================================================
- # *config targets only - make sureprerequisites are updated, and descend
- # in scripts/kconfig to make the *configtarget
-
- KBUILD_DEFCONFIG := sandbox_defconfig
- export KBUILD_DEFCONFIG KBUILD_KCONFIG
-
- config: scripts_basic outputmakefileFORCE
- $(Q)$(MAKE) $(build)=scripts/kconfig $@
-
- %config: scripts_basic outputmakefileFORCE
- $(Q)$(MAKE) $(build)=scripts/kconfig $@
-
- else
- # ===========================================================================
- # Build targets only - this includes vmlinux,arch specific targets, clean
- # targets and others. In general all targetsexcept *config targets.
-
- # Additional helpers built in scripts/
- # Carefully list dependencies so we do nottry to build scripts twice
- # in parallel
- PHONY += scripts
- scripts: scripts_basicinclude/config/auto.conf
- $(Q)$(MAKE) $(build)=$(@)
-
- ifeq ($(dot-config),1)
- # Read in config
- -include include/config/auto.conf
复制代码第424行定义了变量version_h,这变量保存版本号文件,此文件是自动生成的。文件include/generated/version_autogenerated.h内容如下图所示:
图 23.3.11版本号文件 第425行定义了变量timestamp_h,此变量保存时间戳文件,此文件也是自动生成的。文件include/generated/timestamp_autogenerated.h内容如下图所示:
图 23.3.12时间戳文件 第427行定义了变量no-dot-config-targets。 第431行定义了变量config-targets,初始值为0。 第432行定义了变量mixed-targets,初始值为0。 第433行定义了变量dot-config,初始值为1。 第435行将MAKECMDGOALS中不符合no-dot-config-targets的部分过滤掉,剩下的如果不为空的话条件就成立。MAKECMDGOALS是make的一个环境变量,这个变量会保存你所指定的终极目标列表,比如执行“make zynq_altk_defconfig”,那么MAKECMDGOALS就为zynq_altk_defconfig。很明显过滤后为空,所以条件不成立,变量dot-config依旧为1。 第441行判断KBUILD_EXTMOD是否为空,如果KBUILD_EXTMOD为空的话条件成立,经过前面的分析,我们知道KBUILD_EXTMOD为空,所以条件成立。 第442行将MAKECMDGOALS中不符合“config”和“%config”的部分过滤掉,如果剩下的部分不为空条件就成立,很明显此处条件成立,变量config-targets=1。 第444行统计MAKECMDGOALS中的单词个数,如果不为1的话条件成立。此处调用Makefile中的words函数来统计单词个数,words函数格式如下: $(words <text>) 很明显,MAKECMDGOALS的单词个数是1个,所以条件不成立,mixed-targets继续为0。综上所述,这些变量值如下: config-targets = 1 mixed-targets = 0 dot-config = 1 第450行如果变量mixed-targets为1的话条件成立,很明显,条件不成立。 第467行如果变量config-targets为1的话条件成立,很明显,条件成立,执行这个分支。 第475行,没有目标与之匹配,所以不执行。 第478行,有目标与之匹配,当输入“make xxx_defconfig”的时候就会匹配到%config目标,目标“%config”依赖于scripts_basic、outputmakefile和FORCE。FORCE在顶层Makefile的1703行有如下定义: 示例代码顶层Makefile代码段 1703 PHONY += FORCE 1704 FORCE: 可以看出FORCE是没有规则和依赖的,所以每次都会重新生成FORCE。当FORCE作为其他目标的依赖时,由于FORCE总是被更新过的,因此依赖所在的规则总是会执行的。 依赖scripts_basic和outputmakefile在顶层Makefile中的内容如下: 示例代码顶层Makefile代码段 - # Basic helpers built in scripts/
- PHONY += scripts_basic
- scripts_basic:
- $(Q)$(MAKE) $(build)=scripts/basic
- $(Q)rm -f.tmp_quiet_recordmcount
-
- # To avoid any implicit rule to kick in,define an empty command.
- scripts/basic/%: scripts_basic ;
-
- PHONY += outputmakefile
- # outputmakefile generates a Makefile in theoutput directory, if using a
- # separate output directory. This allowsconvenient use of make in the
- # output directory.
- outputmakefile:
- ifneq ($(KBUILD_SRC),)
- $(Q)ln -fsn $(srctree) source
- $(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
- $(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
- endif
复制代码第410行,判断KBUILD_SRC是否为空,只有变量KBUILD_SRC不为空的时候outputmakefile才有意义,经过我们前面的分析KBUILD_SRC为空,所以outputmakefile无效,只有scripts_basic是有效的。 第398~400行是scripts_basic的规则,其对应的命令用到了变量Q、MAKE和build,其中: Q=@或为空 MAKE=make 变量build是在scripts/Kbuild.include文件中有定义,定义如下: 示例代码Kbuild.include代码段 - ###
- # Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
- # Usage:
- # $(Q)$(MAKE) $(build)=dir
- build := -f $(srctree)/scripts/Makefile.build obj
复制代码从上面的示例代码可以看出build=-f$(srctree)/scripts/Makefile.build obj,经过前面的分析可知,变量srctree为”.”,因此: build=-f ./scripts/Makefile.build obj scripts_basic展开以后如下: scripts_basic: @make -f./scripts/Makefile.build obj=scripts/basic //也可以没有@,视配置而定 @rm -f .tmp_quiet_recordmcount //也可以没有@ scripts_basic会调用文件./scripts/Makefile.build,这个我们后面在分析。 接着回到Makefile第478行的%config处,内容如下: %config: scripts_basicoutputmakefile FORCE $(Q)$(MAKE) $(build)=scripts/kconfig $@ 将命令展开就是: @make -f ./scripts/Makefile.build obj=scripts/kconfigxxx_defconfig //也可以没有@ 同样也跟文件./scripts/Makefile.build有关,我们后面再分析此文件。使用如下命令配置uboot,并观察其配置过程: make zynq_altk_defconfig V=1 配置过程如下图所示:
图 23.3.13 uboot配置过程 从上图可以看出,我们的分析是正确的,接下来就要结合下面两行命令重点分析一下文件scripts/Makefile.build。 ①、scripts_basic目标对应的命令 @make -f ./scripts/Makefile.build obj=scripts/basic ②、%config目标对应的命令 @make -f ./scripts/Makefile.build obj=scripts/kconfigxxx_defconfig 1.3.14 Makefile.build脚本分析从上一小节可知,“make xxx_defconfig”配置uboot的时候如下两行命令会执行脚本scripts/Makefile.build: @make -f ./scripts/Makefile.build obj=scripts/basic @make -f ./scripts/Makefile.build obj=scripts/kconfigxxx_defconfig 依次来分析一下: 1、scripts_basic目标对应的命令 scripts_basic目标对应的命令为:@make -f ./scripts/Makefile.build obj=scripts/basic。打开文件scripts/Makefile.build,有如下代码: 示例代码Makefile.build代码段 - # Modified for U-Boot
- prefix := tpl
- src := $(patsubst $(prefix)/%,%,$(obj))
- ifeq ($(obj),$(src))
- prefix := spl
- src := $(patsubst $(prefix)/%,%,$(obj))
- ifeq ($(obj),$(src))
- prefix := .
- endif
- endif
复制代码第9行定义了变量prefix值为tpl。 第10行定义了变量src,这里用到了函数patsubst,此行代码展开后为: $(patsubst tpl/%,%, scripts/basic) patsubst是替换函数,格式如下: $(patsubst<pattern>,<replacement>,<text>) 此函数用于在text中查找符合pattern的部分,如果匹配的话就用replacement替换掉。pattern是可以包含通配符“%”,如果replacement中也包含通配符“%”,那么replacement中的这个“%”将是pattern中的那个“%”所代表的字符串。函数的返回值为替换后的字符串。因此,第10行就是在“scripts/basic”中查找符合“tpl/%”的部分,然后将“tpl/”取消掉,但是“scripts/basic”没有“tpl/”,所以src= scripts/basic。 第11行判断变量obj和src是否相等,相等的话条件成立,很明显,此处条件成立。 第12行和第9行一样,只是这里处理的是“spl”,“scripts/basic”里面也没有“spl/”,所以src继续为scripts/basic。 第15行因为变量obj和src相等,所以prefix=.。 继续分析scripts/Makefile.build,有如下代码: 示例代码Makefile.build代码段 - <div style="text-align: left;"> # The filename Kbuild hasprecedence over Makefile</div><div style="text-align: left;">kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))</div><div style="text-align: left;">kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)</div><div style="text-align: left;">include $(kbuild-file)</div>
复制代码将kbuild-dir展开后为: $(if $(filter /%, scripts/basic), scripts/basic, ./scripts/basic), 因为没有以“/”为开头的单词,所以$(filter /%, scripts/basic)的结果为空,kbuild-dir=./scripts/basic。 将kbuild-file展开后为: $(if $(wildcard ./scripts/basic/Kbuild), ./scripts/basic/Kbuild,./scripts/basic/Makefile) 因为scrpts/basic目录中没有Kbuild这个文件,所以kbuild-file= ./scripts/basic/Makefile。最后将59行展开,即: include ./scripts/basic/Makefile 也就是读取scripts/basic下面的Makefile文件。 继续分析scripts/Makefile.build,如下代码: 示例代码 Makefile.build代码段 - __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
- $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
- $(subdir-ym) $(always)
- @:
复制代码__build是默认目标,因为命令“@make -f ./scripts/Makefile.build obj=scripts/basic”没有指定目标,所以会使用到默认目标:__build。在顶层Makefile中,KBUILD_BUILTIN为1,KBUILD_MODULES为0,因此展开后目标__build为: __build  (builtin-target) $(lib-target) $(extra-y))$(subdir-ym) $(always) @: 可以看出目标__build有5个依赖:builtin-target、lib-target、extra-y、subdir-ym和always。这5个依赖的具体内容我们就不通过源码来分析了,直接在scripts/Makefile.build中输入下图所示内容,将这5个变量的值打印出来:
图 23.3.14输出变量 执行如下命令: make zynq_altk_defconfig V=1 结果如下图所示:
图 23.3.15输出结果 从上图可以看出,只有always有效,因此__build最终为: __build: scripts/basic/fixdep @: __build依赖于scripts/basic/fixdep,所以要先编译scripts/basic/fixdep.c,生成fixdep,前面已经读取了scripts/basic/Makefile文件。 综上所述,scripts_basic目标的作用就是编译出scripts/basic/fixdep这个软件。 2、%config目标对应的命令 %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
复制代码可以看出,Makefilke.build会读取scripts/kconfig/Makefile中的内容,此文件有如下所示内容: 示例代码scripts/kconfig/Makefile代码段 - %_defconfig: $(obj)/conf
- $(Q)[ DISCUZ_CODE_1670 ]lt; $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)
- # Added for U-Boot (backward compatibility)
- %_config: %_defconfig
- @:
复制代码目标%_defconfig刚好和我们输入的xxx_defconfig匹配,所以会执行这条规则。依赖为$(obj)/conf,展开后就是scripts/kconfig/conf。接下来就是检查并生成依赖scripts/kconfig/conf。conf是主机软件,到这里我们就打住,不要纠结conf是怎么编译出来的,否则就越陷越深,像conf这种主机所使用的工具类软件我们一般不关心它是如何编译产生的。如果一定要看是conf是怎么生成的,可以输入如下命令重新配置uboot,在重新配置uboot的过程中就会输出conf编译信息。 make distclean make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zynq_altk_defconfigV=1 结果如下图所示:
图 23.3.16编译过程 得到scripts/kconfig/conf以后就要执行目标%_defconfig的命令: $(Q)$< $(silent)--defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig) 相关的变量值如下: silent=-s或为空 SRCARCH=.. Kconfig=Kconfig 将其展开就是: @ scripts/kconfig/conf --defconfig=arch/../configs/xxx_defconfigKconfig 上述命令用到了xxx_defconfig文件,比如zynq_altk_defconfig。这里会将zynq_altk_defconfig中的配置输出到.config文件中,最终生成uboot根目录下的.config文件。 这个就是命令make xxx_defconfig执行流程,总结一下如下图所示:
图 23.3.17 make xxx_defconfig执行流程图 至此,make xxx_defconfig就分析完了,接下来就要分析一下u-boot.bin是怎么生成的了。 1.3.15 make过程配置好uboot以后就可以直接make编译了,因为没有指明目标,所以会使用默认目标,主Makefile中的默认目标如下: 示例代码顶层Makefile代码段 128 # That's our default target when none isgiven on the command line 129 PHONY := _all 130 _all: 目标_all又依赖于all,如下所示: 示例代码顶层Makefile代码段 - # If building an external module we do notcare about the all: rule
- # but instead _all depend on modules
- PHONY += all
- ifeq ($(KBUILD_EXTMOD),)
- _all: all
- else
- _all: modules
- endif
复制代码如果KBUILD_EXTMOD为空的话_all依赖与all。这里不编译模块,所以KBUILD_EXTMOD肯定为空,_all的依赖就是all。在主Makefile中all目标规则如下: 示例代码顶层Makefile代码段 - all: $(ALL-y) cfg
- ifeq ($(CONFIG_DM_I2C_COMPAT)$(CONFIG_SANDBOX),y)
- @echo "===================== WARNING======================"
- @echo "This board uses CONFIG_DM_I2C_COMPAT.Please remove"
- @echo "(possibly in a subsequentpatch in your series)"
- @echo "before sending patches to themailing list."
- @echo "===================================================="
- endif
- @# Check that this build does not useCONFIG options that we do not
- @# know about unless they are in Kconfig.All the existing CONFIG
- @# options are whitelisted, so new onesshould not be added.
- $(callcmd,cfgcheck,u-boot.cfg)
复制代码从859行可以看出,all目标依赖$(ALL-y)和cfg,而在顶层Makefile中,ALL-y如下: 示例代码顶层Makefile代码段 - # Always append ALL so that arch config.mk'scan add custom ones
- ALL-y += u-boot.srec u-boot.bin u-boot.symSystem.map 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 besigned and Header must also
- # be included. So The image has to be builtexplicitly
- ALL-$(CONFIG_RAMBOOT_PBL) += u-boot.pbl
- endif
- endif
- ALL-$(CONFIG_SPL) +=spl/u-boot-spl.bin
- ifeq ($(CONFIG_MX6)$(CONFIG_SECURE_BOOT), yy)
- ALL-$(CONFIG_SPL_FRAMEWORK) += u-boot-ivt.img
- else
- ALL-$(CONFIG_SPL_FRAMEWORK) += u-boot.img
- endif
- ALL-$(CONFIG_TPL) +=tpl/u-boot-tpl.bin
- ALL-$(CONFIG_OF_SEPARATE) += u-boot.dtb
- ifeq ($(CONFIG_SPL_FRAMEWORK),y)
- ALL-$(CONFIG_OF_SEPARATE) += u-boot-dtb.img
- endif
- ALL-$(CONFIG_OF_HOSTFILE) += u-boot.dtb
- ifneq ($(CONFIG_SPL_TARGET),)
- ALL-$(CONFIG_SPL) +=$(CONFIG_SPL_TARGET:"%"=%)
- endif
- ALL-$(CONFIG_REMAKE_ELF) += u-boot.elf
- ALL-$(CONFIG_EFI_APP) += u-boot-app.efi
- ALL-$(CONFIG_EFI_STUB) += u-boot-payload.efi
-
- ifneq ($(BUILD_ROM)$(CONFIG_BUILD_ROM),)
- ALL-$(CONFIG_X86_RESET_VECTOR) += u-boot.rom
- endif
-
- # enable combined SPL/u-boot/dtb rules fortegra
- ifeq ($(CONFIG_TEGRA)$(CONFIG_SPL),yy)
- ALL-y += u-boot-tegra.binu-boot-nodtb-tegra.bin
- ALL-$(CONFIG_OF_SEPARATE) += u-boot-dtb-tegra.bin
- endif
-
- # Add optional build target if defined inboard/cpu/soc headers
- ifneq ($(CONFIG_BUILD_TARGET),)
- ALL-y += $(CONFIG_BUILD_TARGET:"%"=%)
- endif
复制代码从上面的示例代码代码可以看出,ALL-y包含u-boot.srec、u-boot.bin、u-boot.sym、System.map和binary_size_check这几个文件。根据uboot的配置情况也可能包含其他的文件,比如: ALL-$(CONFIG_ONENAND_U_BOOT) += u-boot-onenand.bin CONFIG_ONENAND_U_BOOT就是uboot中跟ONENAND配置有关的,如果我们使能了ONENAND,那么在.config配置文件中就会有“CONFIG_ONENAND_U_BOOT=y”这一句。相当于CONFIG_ONENAND_U_BOOT是个变量,这个变量的值为“y”,所以展开以后就是: ALL-y += u-boot-onenand.bin 这个就是.config里面的配置参数的含义,这些参数其实都是变量,后面跟着变量值,会在顶层Makefile或者其他Makefile中调用这些变量。 ALL-y里面有个u-boot.bin,这个就是我们最终需要的uboot二进制可执行文件,所作的所有工作就是为了它。在顶层Makefile中找到u-boot.bin目标对应的规则,如下所示: 示例代码顶层Makefile代码段 - ifeq ($(CONFIG_MULTI_DTB_FIT),y)
-
- fit-dtb.blob:dts/dt.dtb FORCE
- $(call if_changed,mkimage)
-
- MKIMAGEFLAGS_fit-dtb.blob = -f auto -A$(ARCH) -T firmware -C none -O u-boot \
- -a 0 -e 0 -E \
- $(patsubst %,-barch/$(ARCH)/dts/%.dtb,$(subst ",,$(CONFIG_OF_LIST))) -d /dev/null
-
- u-boot-fit-dtb.bin:u-boot-nodtb.bin fit-dtb.blob
- $(call if_changed,cat)
-
- u-boot.bin: u-boot-fit-dtb.bin FORCE
- $(callif_changed,copy)
- else ifeq ($(CONFIG_OF_SEPARATE),y)
- u-boot-dtb.bin: u-boot-nodtb.bin dts/dt.dtb FORCE
- $(callif_changed,cat)
-
- u-boot.bin: u-boot-dtb.bin FORCE
- $(callif_changed,copy)
- else
- u-boot.bin: u-boot-nodtb.bin FORCE
- $(callif_changed,copy)
- endif
复制代码第881行判断CONFIG_MULTI_DTB_FIT是否等于y,如果相等,那条件就成立,在.config中搜索“CONFIG_MULTI_DTB_FIT”,未设置,说明条件不成立。 第895行判断CONFIG_OF_SEPARATE是否等于y,如果相等,那条件就成立,在.config中搜索“CONFIG_OF_SEPARATE”,未设置,说明条件不成立。 第902行就是目标u-boot.bin的规则,目标u-boot.bin依赖于u-boot-nodtb.bin,命令为$(call if_changed,copy),这里调用了if_changed,if_changed是一个函数,这个函数在scripts/Kbuild.include中有定义,而顶层Makefile中会包含scripts/Kbuild.include文件,这个前面已经说过了。 if_changed在Kbuild.include中的定义如下: 示例代码Kbuild.include代码段 - ###
- # if_changed - execute command if any prerequisite isnewer than
- # target, or command line haschanged
- # if_changed_dep - as if_changed, but uses fixdep to revealdependencies
- # including used config symbols
- # if_changed_rule - as if_changed but executerule instead
- # See Documentation/kbuild/makefiles.txt formore info
- ifneq ($(KBUILD_NOCMDDEP),1)
- # Check if both arguments has same arguments.Result is empty string if equal.
- # User may override thischeck using make KBUILD_NOCMDDEP=1
- arg-check = $(strip $(filter-out $(cmd_$(1)), $(cmd_$@)) \
- $(filter-out $(cmd_$@), $(cmd_$(1))) )
- else
- arg-check = $(if $(strip $(cmd_$@)),,1)
- endif
- # Replace >[ DISCUZ_CODE_1675 ]lt; with >[ DISCUZ_CODE_29 ]lt; topreserve $ when reloading the .cmd file
- # (needed for make)
- # Replace >#< with >\#< to avoidstarting a comment in the .cmd file
- # (needed for make)
- # Replace >'< with >'\''< to beable to enclose the whole string in '...'
- # (needed for the shell)
- make-cmd = $(call escsq,$(subst \#,\\\#,$(subst$,$,$(cmd_$(1)))))
- # Find any prerequisites that is newer thantarget or that does not exist.
- # PHONY targets skipped in both cases.
- any-prereq = $(filter-out $(PHONY),$?) $(filter-out $(PHONY) $(wildcard $^),$^)
- # Execute command if command has changed orprerequisite(s) are updated.
- #
- if_changed = $(if $(strip $(any-prereq) $(arg-check)), \
- @set -e; \
- $(echo-cmd) $(cmd_$(1)); \
- printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd)
复制代码第227行为if_changed的描述,根据描述,在一些先决条件比目标新的时候,或者命令行有改变的时候,if_changed就会执行一些命令。 第257行就是函数if_changed,if_changed函数引用的变量比较多,也比较绕,我们只需要知道它可以从u-boot-nodtb.bin生成u-boot.bin就行了。 既然u-boot.bin依赖于u-boot-nodtb.bin,那么肯定要先生成u-boot-nodtb.bin文件,顶层Makefile中相关代码如下: 示例代码顶层Makefile代码段 - u-boot-nodtb.bin:u-boot FORCE
- $(callif_changed,objcopy)
- $(callDO_STATIC_RELA,[ DISCUZ_CODE_1676 ]lt;,$@,$(CONFIG_SYS_TEXT_BASE))
- $(BOARD_SIZE_CHECK)
复制代码目标u-boot-nodtb.bin又依赖于u-boot,顶层Makefile中u-boot相关规则如下: 示例代码顶层Makefile代码段 - 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)
u-boot-main := $(libs-y)
$(head-y)跟CPU架构有关,我们使用的是ARM芯片,所以head-y在arch/arm/Makefile中被指定为: head-y := arch/arm/cpu/$(CPU)/start.o 根据23.3.12小节的分析,我们知道CPU=armv7,因此head-y展开以后就是: head-y := arch/arm/cpu/armv7/start.o 因此: u-boot-init= arch/arm/cpu/armv7/start.o $(libs-y)在顶层Makefile中被定义为uboot所有子目录下build-in.o的集合,代码如下: 示例代码顶层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 += cmd/
- libs-y += common/
- libs-y += env/
- libs-$(CONFIG_API) += api/
- libs-$(CONFIG_HAS_POST) += post/
- libs-y += test/
- libs-y += test/dm/
- libs-$(CONFIG_UT_ENV) += test/env/
- libs-$(CONFIG_UT_OVERLAY) += test/overlay/
-
- libs-y += $(if $(BOARDDIR),board/$(BOARDDIR)/)
-
- libs-y := $(sort $(libs-y))
-
- u-boot-dirs := $(patsubst%/,%,$(filter %/, $(libs-y))) tools examples
-
- u-boot-alldirs := $(sort $(u-boot-dirs) $(patsubst %/,%,$(filter%/, $(libs-))))
-
- libs-y := $(patsubst %/, %/built-in.o, $(libs-y))
复制代码从上面的代码可以看出,libs-y都是uboot各子目录的集合,最后: libs-y := $(patsubst %/, %/built-in.o, $(libs-y)) 这里调用了函数patsubst,将libs-y中的“/”替换为”/built-in.o”,比如“drivers/dma/”就变为了“drivers/dma/built-in.o”,相当于将libs-y改为所有子目录中built-in.o文件的集合。那么u-boot-main就等于所有子目录中built-in.o的集合。 这个规则就相当于将以u-boot.lds为链接脚本,将arch/arm/cpu/armv7/start.o和各个子目录下的built-in.o链接在一起生成u-boot。 u-boot.lds的规则如下: 示例代码顶层Makefile代码段 1392 u-boot.lds: $(LDSCRIPT) prepare FORCE 1393 $(callif_changed_dep,cpp_lds) 接下来的重点就是各子目录下的built-in.o是怎么生成的,以drivers/gpio/built-in.o为例,在drivers/gpio/目录下会有个名为.built-in.o.cmd的文件,此文件内容如下: 示例代码drivers/gpio/.built-in.o.cmd代码 cmd_drivers/gpio/built-in.o := arm-linux-gnueabihf-ld.bfd -r-o drivers/gpio/built-in.o drivers/gpio/gpio-uclass.o drivers/gpio/zynq_gpio.o 从命令“cmd_drivers/gpio/built-in.o”可以看出,drivers/gpio/built-in.o这个文件是使用ld命令由文件drivers/gpio/gpio-uclass.o和drivers/gpio/zynq_gpio.o生成而来的,其中zynq_gpio.o是zynq_gpio.c编译生成的.o文件,这个是Xilinx的ZYNQ系列的GPIO驱动文件。这里用到了ld的“-r”参数,参数含义如下: -r –relocateable: 产生可重定向的输出,比如,产生一个输出文件它可再次作为‘ld’的输入,这经常被叫做“部分链接”,当我们需要将几个小的.o文件链接成为一个.o文件的时候,需要使用此选项。 最终将各个子目录中的built-in.o文件链接在一起就形成了u-boot,使用如下命令编译uboot就可以看到链接的过程: make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zynq_altk_defconfigV=1 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- V=1 编译的时候会有如下图所示内容输出:
图 23.3.18编译内容输出 将其整理一下,内容如下: - arm-linux-gnueabihf-ld.bfd -pie --gc-sections -Bstatic --no-dynamic-linker -Ttext 0x4000000
- -o u-boot -T u-boot.lds
- arch/arm/cpu/armv7/start.o
- --start-group arch/arm/cpu/built-in.o
- arch/arm/cpu/armv7/built-in.o
- arch/arm/lib/built-in.o
- arch/arm/mach-zynq/built-in.o
- board/xilinx/zynq/built-in.o
- cmd/built-in.o
- common/built-in.o
- disk/built-in.o
- drivers/built-in.o
- drivers/dma/built-in.o
- drivers/gpio/built-in.o
- ……
- drivers/usb/phy/built-in.o
- drivers/usb/ulpi/built-in.o
- dts/built-in.o
- env/built-in.o
- fs/built-in.o
- lib/built-in.o
- net/built-in.o
- test/built-in.o
- test/dm/built-in.o
- --end-group arch/arm/lib/eabi_compat.o
- arch/arm/lib/lib.a -Map u-boot.map; true
复制代码可以看出最终是用arm-linux-gnueabihf-ld.bfd命令将arch/arm/cpu/armv7/start.o和其他众多的built_in.o链接在一起,形成u-boot。 目标all除了u-boot.bin以外还有其他的依赖,比如u-boot.srec 、u-boot.sym 、System.map、u-boot.cfg和binary_size_check等等,这些依赖的生成方法和u-boot.bin很类似,大家自行查看一下顶层Makefile,我们就不详细的讲解了。 总结一下“make”命令的流程,如下图所示:
图 23.3.19 make命令流程 上图就是“make”命令的执行流程,关于uboot的顶层Makefile就分析到这里,重点是“make xxx_defconfig”和“make”这两个命令的执行流程: make xxx_defconfig:用于配置uboot,这个命令最主要的目的就是生成.config文件。 make:用于编译uboot,这个命令的主要工作就是生成二进制的u-boot.bin文件和其他的一些与uboot有关的文件,如u-boot.elf等等。 关于uboot的顶层Makefile就分析到这里,有些内容我们没有详细、深入的去研究,因为我们的重点是使用uboot,而不是uboot的研究者,我们要做的是缕清uboot的流程。至于更具体的实现,有兴趣的可以参考一下其他资料。
|