搜索
bottom↓
回复: 1

【正点原子FPGA连载】第十二章U-Boot顶层Makefile详解--摘自【正点原子】领航者 ZYNQ 之linux驱动开发指南

[复制链接]

出0入234汤圆

发表于 2020-8-29 11:05:23 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2020-8-29 11:08 编辑

1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:876744900 点击加入:
QQ群头像.png       
5)关注正点原子公众号,获取最新资料


100846rel79a9p4uelap24.jpg

100846f1ce1fg14zbg0va4.png

第十二章U-Boot顶层Makefile详解



上一章我们详细的讲解了uboot的使用方法,其实就是各种命令的使用,学会uboot使用以后就可以尝试移植uboot到自己的开发板上了。在移植之前我们先来分析uboot顶层Makefile文件,理清uboot的编译流程。本章我们使用正点原子提供的uboot源码,先了解uboot的目录结构,再来分析顶层Makefile文件。



12.1U-Boot工程目录分析
讲解顶层Makefile文件之前,我们需要先了解uboot工程目录。在22.2小节我们已经将uboot解压到了alientek_uboot下,如下图所示:
第12章302.png

图 23.1.1解压后的uboot

上图中除了正点原子提供的uboot源码压缩包u-boot-altk-xilinx-v2018.3.tar.gz文件外,其他的文件和文件夹都是解压出来的uboot源码。这些文件夹或文件的含义如下表所示:
表 22.4.9.1 uboot目录列表
22491.png
224911.png

上表中的很多文件夹和文件我们都不需要去关心,我们要关注的文件夹或文件如下:
1.arch文件夹
这个文件夹里面存放着和架构有关的文件,如下图所示:
第12章1211.png

图 23.1.2 arch文件夹

从上图可以看出有很多架构,比如arm、avr32、m68k等,我们现在用的是ARM芯片,所以只需要关心arm文件夹即可,进入arm文件夹里面内容如下图所示:
第12章1352.png

图 23.1.3 arm文件夹

mach开头的文件夹是跟具体的设备有关的,比如“mach-exynos”就是跟三星的exyons系列CPU有关的文件。我们使用的是ZYNQ,所以要关注“mach-zynq”这个文件夹。另外“cpu”这个文件夹也是和cpu架构有关的,文件夹内容如下图所示:
第12章1541.png

图 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”的文件夹,如下图所示:
第12章1901.png

图 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官方开发板和正点原子的开发板配置文件肯定也在这个文件夹中,如下图所示:
第12章2455.png

[size=13.3333px]
图 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了。
12.2VScode创建uboot工程
为了方便查看uboot源码,我们使用VScode创建uboot工程。打开VScode,选择:文件->打开文件夹…,选中uboot文件夹,如下图所示:
第12章3224.png

图 23.2.1选择uboot源码文件夹

打开uboot目录以后,VSCode界面如下图所示:
第12章3317.png

图 23.2.2 VScode界面

点击“文件->将工作区另存为…”,打开保存工作区对话框,将工作区保存到uboot源码根目录下,设置文件名为“uboot”,如下图所示:
第12章3448.png

图 23.2.3保存工作区

保存成功以后就会在uboot源码根目录下存在一个名为uboot.code-workspace的文件。这样一个完整的VSCode工程就建立起来了。但是这个VSCode工程包含了uboot的所有文件,uboot中有些文件是不需要的,比如arch目录下是各种架构的文件夹,如下图所示:
第12章3648.png

图 23.2.4 arch目录

在arch目录下,我们只需要arm文件夹,所以需要将其它的目录从VSCode中给屏蔽掉,比如将arch/nios2这个目录给屏蔽掉。
在VSCode上建名为“.vscode”的文件夹,如图31.2.5所示:
第12章3813.png

图 23.2.5新建.vscode文件夹

输入新建文件夹的名字,完成以后如下图所示。
第12章3902.png

图 23.2.6新建的.vscode文件夹

在.vscode文件夹中新建一个名为“settings.json”的文件,然后在settings.json中输入如下内容:
示例代码settings.json文件代码
  1. 1  {
  2. 2      "search.exclude": {
  3. 3          "**/node_modules": true,
  4. 4          "**/bower_components": true,
  5. 5      },
  6. 6      "files.exclude": {
  7. 7          "**/.git": true,
  8. 8          "**/.svn": true,
  9. 9          "**/.hg": true,
  10. 10         "**/CVS": true,
  11. 11         "**/.DS_Store": true,      
  12. 12     }
  13. 13 }
复制代码

结果如下图所示:
第12章4369.png

图 23.2.7 settings.json文件默认内容

其中"search.exclude"里面是需要在搜索结果中排除的文件或者文件夹,"files.exclude"是左侧工程目录中需要排除的文件或者文件夹。我们需要将arch/nios2文件夹下的所有文件从搜索结果和左侧的工程目录中都排除掉,因此在"search.exclude"和"files.exclude"中输入如下图所示内容:
第12章4609.png

图 23.2.8排除arch/nios2目录

此时再看一下左侧的工程目录,发现arch目录下没有nios2这个文件夹了,说明nios2这个文件夹被排除掉了,如下图所示:
第12章4739.png

图 23.2.9 arch/avr32目录排除

我们只是在"search.exclude"和"files.exclude"中加入了:rch/nios2": true,冒号前面的是要排除的文件或者文件夹,冒号后面为是否将文件排除,true表示排除,false表示不排除。用这种方法即可将不需要的文件,或者文件夹排除掉,对于本章我们分析uboot而言,在"search.exclude"和"files.exclude"中需要输入的完成的内容如下:
示例代码settings.json文件代码
  1. 1  "**/*.o": true,
  2. 2  "**/*.su": true,
  3. 3  "**/*.cmd": true,
  4. 4  "arch/arc": true,
  5. 5  "arch/m68k": true,
  6. 6  "arch/microblaze": true,
  7. 7  "arch/mips": true,
  8. 8  "arch/nds32": true,
  9. 9  "arch/nios2": true,
  10. 10 "arch/powerpc": true,
  11. 11 "arch/sandbox": true,
  12. 12 "arch/sh": true,
  13. 13 "arch/xtensa": true,
  14. 14 "arch/x86": true,
  15. 15 "arch/arm/mach*": true,
  16. 16 "arch/arm/mach-zynq": false,
  17. 17 "arch/arm/cpu/arm11*": true,
  18. 18 "arch/arm/cpu/arm720t": true,
  19. 19 "arch/arm/cpu/arm9*": true,
  20. 20 "arch/arm/cpu/armv7m": true,
  21. 21 "arch/arm/cpu/armv8": true,
  22. 22 "arch/arm/cpu/pxa": true,
  23. 23 "arch/arm/cpu/sa1100": true,
  24. 24 "board/[a-w]*": true,
  25. 25 "board/[y-z]*": true,
  26. 26 "board/[0-9]*": true,
  27. 27 "board/[A-Z]*": true,
  28. 28 "board/xe*": true,
  29. 29 "board/xilinx/m*": true,
  30. 30 "configs/[a-y]*": true,
  31. 31 "configs/[A-Z]*": true,
  32. 32 "configs/[0-9]*": true,
复制代码

上述代码用到了通配符“*”,比如“**/*.o”表示所有.o结尾的文件。“configs/[a-y]*”表示configs目录下所有以‘a’~‘y’开头的文件或者文件夹。上述配置只是排除了一部分文件夹,大家在实际的使用中可以根据自己的实际需求来选择将哪些文件或者文件夹排除掉。排除以后我们的工程就会清爽很多,搜索的时候也不会跳出很多文件了。
12.3U-Boot顶层Makefile分析
在阅读uboot源码之前,肯定是要先看一下顶层Makefile,分析gcc版本代码的时候一定是先从顶层Makefile开始的,然后再是子Makefile,这样通过层层分析Makefile即可了解整个工程的组织结构。顶层Makefile也就是uboot根目录下的Makefile文件,由于顶层Makefile文件内容比较多,所以我们将其分开来看。
12.3.1版本号
顶层Makefile一开始是版本号,内容如下(为了方便分析,顶层Makefile代码段前段行号采用Makefile中的行号,因为uboot会更新,因此行号可能会与你所看的顶层Makefile有所不同):
  1. 5 VERSION = 2018
  2. 6 PATCHLEVEL = 01
  3. 7 SUBLEVEL =
  4. 8 EXTRAVERSION =
  5. 9 NAME =
复制代码

VERSION是主版本号,PATCHLEVEL是补丁版本号,SUBLEVEL是次版本号,这三个一起构成了uboot的版本号,比如当前的uboot版本号就是“2018.01”。EXTRAVERSION是附加版本信息,NAME是和名字有关的,一般不使用这两个。
12.3.2MAKEFLAGS变量
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)”表示当前目录。
12.3.3命令输出
为了更好的体会下面讲解的内容,我们在讲解命令输出之前先介绍下不使用Petalinux时通常的编译uboot方式,这种方式了解就可以了,不推荐使用。在不使用Petalinux的情况下,可以使用如下的make命令来编译uboot(需要提醒的是在使用下面的命令前先建立Petalinux的环境变量,否则会提示arm-linux-gnueabihf-找不到等错误):
  1. make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
  2. make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zynq_altk_defconfig
  3. 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也就编译成功了,如下图所示:
第12章8075.png

图 23.3.1 编译完成

可以看到uboot默认编译是不会在终端中显示完整的命令,都是短命令,很简洁。
在终端中输出短命令虽然看起来很清爽,但是不利于分析uboot的编译过程。可以通过设置变量“V=1”来实现完整的命令输出,也就是使用命令“make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8”,这个在调试uboot的时候很有用,结果如下图所示:
第12章8325.png

图 23.3.2终端完整命令输出

顶层Makefile中控制命令输出的代码如下:
示例代码 顶层Makefile代码
  1. 73 ifeq ("$(origin V)", "command line")
  2. 74   KBUILD_VERBOSE = $(V)
  3. 75 endif
  4. 76 ifndef KBUILD_VERBOSE
  5. 77   KBUILD_VERBOSE = 0
  6. 78 endif
  7. 79
  8. 80 ifeq ($(KBUILD_VERBOSE),1)
  9. 81   quiet =
  10. 82   Q =
  11. 83 else
  12. 84   quiet=quiet_
  13. 85   Q = @
  14. 86 endif
复制代码

上述代码中先使用ifeq来判断"$(origin V)"和"command line"是否相等。这里用到了Makefile中的函数origin,origin和其他的函数不一样,它不操作变量的值,origin用于告诉你变量是哪来的,语法为:
  1. $(origin <variable>)
复制代码

variable是变量名,origin函数的返回值就是变量来源,因此$(origin V)就是变量V的来源。如果变量V是在命令行定义的那么它的来源就是"command line",这样"$(origin V)"和"command line"就相等了。当这两个相等的时候变量KBUILD_VERBOSE就等于V的值,比如在命令行中输入“V=1“的话那么KBUILD_VERBOSE=1。如果没有在命令行输入V的话
  1. KBUILD_VERBOSE=0。
复制代码

第80行判断KBUILD_VERBOSE是否为1,如果KBUILD_VERBOSE为1的话变量quiet和Q都为空,如果KBUILD_VERBOSE=0的话变量quiet为“quiet_“,变量Q为“@”,综上所述:
V=1的话:
  1. KBUILD_VERBOSE=1
  2. quiet= 空
  3. Q=        空
复制代码

V=0或者命令行不定义V的话:
  1. KBUILD_VERBOSE=0
  2. quiet= quiet_
  3. Q=        @
复制代码

Makefile中会用到变量quiet和Q来控制编译的时候是否在终端输出完整的命令,在顶层Makefile中有很多如下所示的命令:
  1. $(Q)$(MAKE) $(build)=tools
复制代码

如果V=0的话上述命令展开就是“@ make $(build)=tools”,make在执行的时候默认会在终端输出命令,但是在命令前面加上“@”就不会在终端输出命令了。当V=1的时候Q就为空,上述命令就是“make $(build)=tools”,因此在make执行的过程,命令会被完整的输出在终端上。
有些命令会有两个版本,比如:
  1. quiet_cmd_sym ?= SYM     $@
  2. cmd_sym ?= $(OBJDUMP) -t [        DISCUZ_CODE_1574        ]lt; > $@
复制代码

sym命令分为“quiet_cmd_sym”和“cmd_sym”两个版本,这两个命令的功能都是一样的,区别在于make执行的时候输出的命令不同。quiet_cmd_xxx命令输出信息少,也就是短命令,而cmd_xxx命令输出信息多,也就是完整的命令。
如果变量quiet为空的话,整个命令都会输出。
如果变量quiet为“quiet_”的话,仅输出短版本。
如果变量quiet为“silent_”的话,整个命令都不会输出。
12.3.4静默输出
上一小节讲了,设置V=0或者在命令行中不定义V的话,编译uboot的时候终端中显示的短命令,但是还是会有命令输出,有时候我们在编译uboot的时候不需要输出命令,这个时候就可以使用uboot的静默输出功能。编译的时候使用“make -s”即可实现静默输出,顶层Makefile中相应的代码如下:
示例代码 顶层Makefile代码
  1. 88  # If the user is running make -s (silent mode), suppress echoing of
  2. 89  # commands
  3. 90  
  4. 91  ifneq ($(filter 4.%,$(MAKE_VERSION)),)  # make-4
  5. 92  ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),)
  6. 93    quiet=silent_
  7. 94  endif
  8. 95  else                    # make-3.8x
  9. 96  ifneq ($(filter s% -s%,$(MAKEFLAGS)),)
  10. 97    quiet=silent_
  11. 98  endif
  12. 99  endif
  13. 100
  14. 101 export quiet Q KBUILD_VERBOSE
复制代码

第91行判断当前正在使用的编译器版本号是否为4.x,判断$(filter 4.%,$(MAKE_VERSION))和“ ”(空)是否相等,如果不相等的话就成立,执行里面的语句。也就是说$(filter 4.%,$(MAKE_VERSION))不为空的话条件就成立,这里用到了Makefile中的filter函数,这是个过滤函数,函数格式如下:
  1. $(filter <pattern...>,<text>)
复制代码

filter函数表示以pattern模式过滤text字符串中的单词,仅保留符合模式pattern的单词,可以有多个模式。函数返回值就是符合pattern的字符串。因此$(filter 4.%,$(MAKE_VERSION))的含义就是在字符串“MAKE_VERSION”中找出符合“4.%”的字符(%为通配符),MAKE_VERSION是MAKE工具的版本号,我们当前使用的MAKE工具版本号为4.1(make -v命令查看),所以肯定可以找出“4.%”。因此$(filter 4.%,$(MAKE_VERSION))不为空,条件成立,执行92~94行的语句。
第92行也是一个判断语句,如果$(filter %s ,$(firstword x$(MAKEFLAGS)))不为空的话条件成立,变量quiet等于“silent_”。这里也用到了函数filter,在$(firstword x$(MAKEFLAGS)))中过滤出符合“%s”的单词。到了函数firstword,函数firstword是获取首单词,函数格式如下:
  1. $(firstword <text>)
复制代码

firstword函数用于取出text字符串中的第一个单词,函数的返回值就是获取到的单词。当使用“make -s”编译的时候,“-s”会作为MAKEFLAGS变量的一部分传递给Makefile。在顶层Makfile中添加如下图所示的代码:
第12章11128.png

图 23.3.3顶层Makefile添加代码

上图中的两行代码用于输出$(firstword x$(MAKEFLAGS))的结果,最后执行命令“make -s mytest”,结果如下图所示:
第12章11270.png

图 23.3.4修改顶层Makefile后的执行结果

从上图可以看出第一个单词是“xrRs”,将$(filter %s ,$(firstword x$(MAKEFLAGS)))展开就是$(filter %s, xrRs),而$(filter %s, xrRs)的返回值肯定不为空,条件成立,quiet=silent_。
第101行 使用export导出变量quiet、Q和KBUILD_VERBOSE。
12.3.5设置编译结果输出目录
uboot可以将编译出来的目标文件输出到单独的目录中,在make的时候使用“O”来指定输出目录,比如“make O=out”就是设置目标文件输出到out目录中。这么做是为了将源文件和编译产生的文件分开,当然也可以不指定O参数,不指定的话源文件和编译产生的文件都在同一个目录内,一般我们不指定O参数。顶层Makefile中相关的代码如下:
示例代码 顶层Makefile代码
  1. 103 # kbuild supports saving output files in a separate directory.
  2. 104 # To locate output files in a separate directory two syntaxes are supported.
  3. 105 # In both cases the working directory must be the root of the kernel src.
  4. 106 # 1) O=
  5. 107 # Use "make O=dir/to/store/output/files/"
  6. 108 #
  7. 109 # 2) Set KBUILD_OUTPUT
  8. 110 # Set the environment variable KBUILD_OUTPUT to point to the directory
  9. 111 # where the output files shall be placed.
  10. 112 # export KBUILD_OUTPUT=dir/to/store/output/files/
  11. 113 # make
  12. 114 #
  13. 115 # The O= assignment takes precedence over the KBUILD_OUTPUT environment
  14. 116 # variable.
  15. 117
  16. 118 # KBUILD_SRC is set on invocation of make in OBJ directory
  17. 119 # KBUILD_SRC is not intended to be used by the regular user (for now)
  18. 120 ifeq ($(KBUILD_SRC),)
  19. 121
  20. 122 # OK, Make called in directory where kernel src resides
  21. 123 # Do we want to locate output files in a separate directory?
  22. 124 ifeq ("$(origin O)", "command line")
  23. 125   KBUILD_OUTPUT := $(O)
  24. 126 endif
  25. 127
  26. 128 # That's our default target when none is given on the command line
  27. 129 PHONY := _all
  28. 130 _all:
  29. 131
  30. 132 # Cancel implicit rules on top Makefile
  31. 133 $(CURDIR)/Makefile Makefile: ;
  32. 134
  33. 135 ifneq ($(KBUILD_OUTPUT),)
  34. 136 # Invoke a second make in the output directory, passing relevant variables
  35. 137 # check that the output directory actually exists
  36. 138 saved-output := $(KBUILD_OUTPUT)
  37. 139 KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) \
  38. 140                                 && /bin/pwd)
  39. ......
  40. 155 endif # ifneq ($(KBUILD_OUTPUT),)
  41. 156 endif # ifeq ($(KBUILD_SRC),)
复制代码

第124行判断“O”是否来自于命令行,如果来自命令行的话条件成立,KBUILD_OUTPUT就为$(O),因此变量KBUILD_OUTPUT就是输出目录。
第135行判断KBUILD_OUTPUT是否为空。
第139行调用mkdir命令,创建KBUILD_OUTPUT目录,并且将创建成功以后的绝对路径赋值给KBUILD_OUTPUT。至此,通过O指定的输出目录就存在了。
12.3.6代码检查
uboot支持代码检查,使用命令“make C=1”使能代码检查,检查那些需要重新编译的文件。“make C=2”用于检查所有的源码文件,顶层Makefile中的代码如下:
示例代码31.3.6.1  顶层Makefile代码
  1. 166 # Call a source code checker (by default, "sparse") as part of the
  2. 167 # C compilation.
  3. 168 #
  4. 169 # Use 'make C=1' to enable checking of only re-compiled files.
  5. 170 # Use 'make C=2' to enable checking of *all* source files, regardless
  6. 171 # of whether they are re-compiled or not.
  7. 172 #
  8. 173 # See the file "Documentation/sparse.txt" for more details, including
  9. 174 # where to get the "sparse" utility.
  10. 175
  11. 176 ifeq ("$(origin C)", "command line")
  12. 177   KBUILD_CHECKSRC = $(C)
  13. 178 endif
  14. 179 ifndef KBUILD_CHECKSRC
  15. 180   KBUILD_CHECKSRC = 0
  16. 181 endif
复制代码

第176行判断C是否来源于命令行,如果C来源于命令行,那就将C赋值给变量KBUILD_CHECKSRC,如果命令行没有C的话KBUILD_CHECKSRC就为0。
12.3.7模块编译
在uboot中允许单独编译某个模块,使用命令“make M=dir”即可,旧语法“make SUBDIRS=dir”也是支持的。顶层Makefile中的代码如下:
示例代码 顶层Makefile代码
  1. 183 # Use make M=dir to specify directory of external module to build
  2. 184 # Old syntax make ... SUBDIRS=$PWD is still supported
  3. 185 # Setting the environment variable KBUILD_EXTMOD take precedence
  4. 186 ifdef SUBDIRS
  5. 187   KBUILD_EXTMOD ?= $(SUBDIRS)
  6. 188 endif
  7. 189
  8. 190 ifeq ("$(origin M)", "command line")
  9. 191   KBUILD_EXTMOD := $(M)
  10. 192 endif
  11. 193
  12. 194 # If building an external module we do not care about the all: rule
  13. 195 # but instead _all depend on modules
  14. 196 PHONY += all
  15. 197 ifeq ($(KBUILD_EXTMOD),)
  16. 198 _all: all
  17. 199 else
  18. 200 _all: modules
  19. 201 endif
  20. 202
  21. 203 ifeq ($(KBUILD_SRC),)
  22. 204         # building in the source tree
  23. 205         srctree := .
  24. 206 else
  25. 207         ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR)))
  26. 208                 # building in a subdirectory of the source tree
  27. 209                 srctree := ..
  28. 210         else
  29. 211                 srctree := $(KBUILD_SRC)
  30. 212         endif
  31. 213 endif
  32. 214 objtree     := .
  33. 215 src     := $(srctree)
  34. 216 obj     := $(objtree)
  35. 217
  36. 218 VPATH       := $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD))
  37. 219
  38. 220 export srctree objtree VPATH
复制代码

第186行判断是否定义了SUBDIRS,如果定义了SUBDIRS,变量KBUILD_EXTMOD=SUBDIRS,这里是为了支持老语法“make SUBIDRS=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。
12.3.8获取主机架构和系统
接下来顶层Makefile会获取主机架构和系统,也就是我们电脑的架构和系统,代码如下:
示例代码 顶层Makefile代码
  1. 227 HOSTARCH := $(shell uname -m | \
  2. 228     sed -e s/i.86/x86/ \
  3. 229         -e s/sun4u/sparc64/ \
  4. 230         -e s/arm.*/arm/ \
  5. 231         -e s/sa110/arm/ \
  6. 232         -e s/ppc64/powerpc/ \
  7. 233         -e s/ppc/powerpc/ \
  8. 234         -e s/macppc/powerpc/\
  9. 235         -e s/sh.*/sh/)
  10. 236
  11. 237 HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
  12. 238         sed -e 's/\(cygwin\).*/cygwin/')
  13. 239
  14. 240 export  HOSTARCH HOSTOS
复制代码

第227行定义了一个变量HOSTARCH,用于保存主机架构,这里调用shell命令“uname -m”获取主机CPU体系架构的名称,结果如下图所示:
第12章16457.png

图 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,结果如下图所示:
第12章16757.png

图 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。
12.3.9设置目标架构、交叉编译器和配置文件
编译uboot的时候需要设置目标板架构和交叉编译器,“make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-”就是用于设置ARCH和CROSS_COMPILE,在顶层Makefile中代码如下:
示例代码 顶层Makefile代码
  1. 244 # set default to nothing for native builds
  2. 245 ifeq ($(HOSTARCH),$(ARCH))
  3. 246 CROSS_COMPILE ?=
  4. 247 endif
  5. 248
  6. 249 KCONFIG_CONFIG  ?= .config
  7. 250 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的定义,如下图所示:
第12章17650.png

图 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里面的才是实时有效的配置。
12.3.10调用scripts/Kbuild.include
主Makefile会调用文件scripts/Kbuild.include这个文件,顶层Makefile中代码如下:
示例代码 顶层Makefile代码
  1. 328 # We need some generic definitions (do not try to remake the file).
  2. 329 scripts/Kbuild.include: ;
  3. 330 include scripts/Kbuild.include
复制代码

该段代码中使用“include”包含了文件scripts/Kbuild.include,此文件里面定义了很多变量,如下图所示:
第12章18502.png

图 23.3.8 Kbuild.include文件部分内容

在uboot的编译过程中会用到scripts/Kbuild.include中的这些变量,后面用到的时候再分析。
12.3.11交叉编译工具变量设置
上面我们只是设置了CROSS_COMPILE的名字,但是交叉编译器其他的工具还没有设置,顶层Makefile中相关代码如下:
示例代码 顶层Makefile代码
  1. 332 # Make variables (CC, etc...)
  2. 333
  3. 334 AS              = $(CROSS_COMPILE)as
  4. 335 # Always use GNU ld
  5. 336 ifneq ($(shell $(CROSS_COMPILE)ld.bfd -v 2> /dev/null),)
  6. 337 LD              = $(CROSS_COMPILE)ld.bfd
  7. 338 else
  8. 339 LD              = $(CROSS_COMPILE)ld
  9. 340 endif
  10. 341 CC              = $(CROSS_COMPILE)gcc
  11. 342 CPP             = $(CC) -E
  12. 343 AR              = $(CROSS_COMPILE)ar
  13. 344 NM              = $(CROSS_COMPILE)nm
  14. 345 LDR             = $(CROSS_COMPILE)ldr
  15. 346 STRIP             = $(CROSS_COMPILE)strip
  16. 347 OBJCOPY           = $(CROSS_COMPILE)objcopy
  17. 348 OBJDUMP          = $(CROSS_COMPILE)objdump
复制代码

12.3.12导出其他变量
接下来在顶层Makefile会导出很多变量,代码如下:
示例代码 顶层Makefile代码
  1. 370 export VERSION PATCHLEVEL SUBLEVEL UBOOTRELEASE UBOOTVERSION
  2. 371 export ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR
  3. 372 export CONFIG_SHELL HOSTCC HOSTCFLAGS HOSTLDFLAGS CROSS_COMPILE AS LD CC
  4. 373 export CPP AR NM LDR STRIP OBJCOPY OBJDUMP
  5. 374 export MAKE AWK PERL PYTHON
  6. 375 export HOSTCXX HOSTCXXFLAGS DTC CHECK CHECKFLAGS
  7. 376
  8. 377 export KBUILD_CPPFLAGS NOSTDINC_FLAGS UBOOTINCLUDE OBJCOPYFLAGS LDFLAGS
  9. 378 export KBUILD_CFLAGS KBUILD_AFLAGS
复制代码

这些变量中大部分都已经在前面定义了,我们重点来看一下下面这几个变量:
  1. ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR
复制代码

这7个变量在顶层Makefile是找不到的,说明这7个变量是在其他文件里面定义的,先来看一下这7个变量都是什么内容,在顶层Makefile中输入如下图所示的内容:
第12章19915.png

图 23.3.9输出变量值

修改好顶层Makefile以后执行如下命令:
  1. make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-  mytest
复制代码

结果如下图所示:
第12章20063.png

图 23.3.10变量结果

从上图可以看到这7个变量的值,这7个变量是从哪里来的呢?在uboot根目录下有个文件叫做config.mk,这7个变量就是在config.mk里面定义的,打开config.mk内容如下:
示例代码 config.mk代码
  1. 1  #
  2. 2  # (C) Copyright 2000-2013
  3. 3  # Wolfgang Denk, DENX Software Engineering, <a href="mailto:wd@denx.de">wd@denx.de</a>.
  4. 4  #
  5. 5  # SPDX-License-Identifier:   GPL-2.0+
  6. 6  #
  7. 7  #########################################################################
  8. 8  
  9. 9  # This file is included from ./Makefile and spl/Makefile.
  10. 10 # Clean the state to avoid the same flags added twice.
  11. 11 #
  12. 12 # (Tegra needs different flags for SPL.
  13. 13 #  That's the reason why this file must be included from spl/Makefile too.
  14. 14 #  If we did not have Tegra SoCs, build system would be much simpler...)
  15. 15 PLATFORM_RELFLAGS :=
  16. 16 PLATFORM_CPPFLAGS :=
  17. 17 PLATFORM_LDFLAGS :=
  18. 18 LDFLAGS :=
  19. 19 LDFLAGS_FINAL :=
  20. 20 OBJCOPYFLAGS :=
  21. 21 # clear VENDOR for tcsh
  22. 22 VENDOR :=
  23. 23 #########################################################################
  24. 24
  25. 25 ARCH := $(CONFIG_SYS_ARCH:"%"=%)
  26. 26 CPU := $(CONFIG_SYS_CPU:"%"=%)
  27. 27 ifdef CONFIG_SPL_BUILD
  28. 28 ifdef CONFIG_TEGRA
  29. 29 CPU := arm720t
  30. 30 endif
  31. 31 endif
  32. 32 BOARD := $(CONFIG_SYS_BOARD:"%"=%)
  33. 33 ifneq ($(CONFIG_SYS_VENDOR),)
  34. 34 VENDOR := $(CONFIG_SYS_VENDOR:"%"=%)
  35. 35 endif
  36. 36 ifneq ($(CONFIG_SYS_SOC),)
  37. 37 SOC := $(CONFIG_SYS_SOC:"%"=%)
  38. 38 endif
  39. 39
  40. 40 # Some architecture config.mk files need to know what CPUDIR is set to,
  41. 41 # so calculate CPUDIR before including ARCH/SOC/CPU config.mk files.
  42. 42 # Check if arch/$ARCH/cpu/$CPU exists, otherwise assume arch/$ARCH/cpu contains
  43. 43 # CPU-specific code.
  44. 44 CPUDIR=arch/$(ARCH)/cpu$(if $(CPU),/$(CPU),)
  45. 45
  46. 46 sinclude $(srctree)/arch/$(ARCH)/config.mk   
  47. 47 sinclude $(srctree)/$(CPUDIR)/config.mk     
  48. 48
  49. 49 ifdef    SOC
  50. 50 sinclude $(srctree)/$(CPUDIR)/$(SOC)/config.mk  
  51. 51 endif
  52. 52 ifneq ($(BOARD),)
  53. 53 ifdef    VENDOR
  54. 54 BOARDDIR = $(VENDOR)/$(BOARD)
  55. 55 else
  56. 56 BOARDDIR = $(BOARD)
  57. 57 endif
  58. 58 endif
  59. 59 ifdef    BOARD
  60. 60 sinclude $(srctree)/board/$(BOARDDIR)/config.mk  # include board specific rules
  61. 61 endif
  62. 62
  63. 63 ifdef FTRACE
  64. 64 PLATFORM_CPPFLAGS += -finstrument-functions -DFTRACE
  65. 65 endif
  66. 66
  67. 67 # Allow use of stdint.h if available
  68. 68 ifneq ($(USE_STDINT),)
  69. 69 PLATFORM_CPPFLAGS += -DCONFIG_USE_STDINT
  70. 70 endif
  71. 71
  72. 72 #########################################################################
  73. 73
  74. 74 RELFLAGS := $(PLATFORM_RELFLAGS)
  75. 75
  76. 76 PLATFORM_CPPFLAGS += $(RELFLAGS)
  77. 77 PLATFORM_CPPFLAGS += -pipe
  78. 78
  79. 79 LDFLAGS += $(PLATFORM_LDFLAGS)
  80. 80 LDFLAGS_FINAL += -Bstatic
  81. 81
  82. 82 export PLATFORM_CPPFLAGS
  83. 83 export RELFLAGS
  84. 84 export LDFLAGS_FINAL
  85. 85 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文件代码
  1. 18 CONFIG_SYS_ARCH="arm"
  2. 19 CONFIG_SYS_CPU="armv7"
  3. 20 CONFIG_SYS_SOC="zynq"
  4. 21 CONFIG_SYS_VENDOR="xilinx"
  5. 22 CONFIG_SYS_BOARD="zynq"
  6. 23 CONFIG_SYS_CONFIG_NAME="zynq_altk"
复制代码

根据这段代码可知:
  1. ARCH = arm
  2. CPU = armv7
  3. BOARD = zynq
  4. VENDOR = xilinx
  5. SOC = zynq
  6. CPUDIR = arch/arm/cpu/armv7
  7. BOARDDIR = xilinx/zynq
复制代码

在config.mk中读取的文件有:
  1. arch/arm/config.mk
  2. arch/arm/cpu/armv7/config.mk
  3. arch/arm/cpu/armv7/zynq/config.mk (此文件不存在)
  4. board/xilinx/zynq/config.mk (此文件不存在)
复制代码

12.3.13make xxx_defconfig过程
在编译uboot之前要使用“make xxx_defconfig”命令来配置uboot,那么这个配置过程是如何运行的呢?在顶层Makefile中有如下代码:
示例代码 顶层Makefile代码段
  1. 416  # To make sure we do not include .config for any of the *config targets
  2. 417  # catch them early, and hand them over to scripts/kconfig/Makefile
  3. 418  # It is allowed to specify more targets when calling make, including
  4. 419  # mixing *config targets and build targets.
  5. 420  # For example 'make oldconfig all'.
  6. 421  # Detect when mixed targets is specified, and make a second invocation
  7. 422  # of make so .config is not included in this case either (for *config).
  8. 423  
  9. 424  version_h := include/generated/version_autogenerated.h
  10. 425  timestamp_h := include/generated/timestamp_autogenerated.h
  11. 426  
  12. 427  no-dot-config-targets := clean clobber mrproper distclean \
  13. 428              help %docs check% coccicheck \
  14. 429              ubootversion backup tests
  15. 430  
  16. 431  config-targets := 0
  17. 432  mixed-targets  := 0
  18. 433  dot-config     := 1
  19. 434  
  20. 435  ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),)
  21. 436     ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),)
  22. 437         dot-config := 0
  23. 438     endif
  24. 439  endif
  25. 440  
  26. 441  ifeq ($(KBUILD_EXTMOD),)
  27. 442          ifneq ($(filter config %config,$(MAKECMDGOALS)),)
  28. 443                  config-targets := 1
  29. 444                  ifneq ($(words $(MAKECMDGOALS)),1)
  30. 445                          mixed-targets := 1
  31. 446                  endif
  32. 447          endif
  33. 448  endif
  34. 449  
  35. 450  ifeq ($(mixed-targets),1)
  36. 451  # ===========================================================================
  37. 452  # We're called with mixed targets (*config and build targets).
  38. 453  # Handle them one by one.
  39. 454  
  40. 455  PHONY += $(MAKECMDGOALS) __build_one_by_one
  41. 456  
  42. 457  $(filter-out __build_one_by_one, $(MAKECMDGOALS)): __build_one_by_one
  43. 458     @:
  44. 459  
  45. 460  __build_one_by_one:
  46. 461     $(Q)set -e; \
  47. 462     for i in $(MAKECMDGOALS); do \
  48. 463         $(MAKE) -f $(srctree)/Makefile $i; \
  49. 464     done
  50. 465  
  51. 466  else
  52. 467  ifeq ($(config-targets),1)
  53. 468  # ===========================================================================
  54. 469  # *config targets only - make sure prerequisites are updated, and descend
  55. 470  # in scripts/kconfig to make the *config target
  56. 471  
  57. 472  KBUILD_DEFCONFIG := sandbox_defconfig
  58. 473  export KBUILD_DEFCONFIG KBUILD_KCONFIG
  59. 474  
  60. 475  config: scripts_basic outputmakefile FORCE
  61. 476     $(Q)$(MAKE) $(build)=scripts/kconfig $@
  62. 477  
  63. 478  %config: scripts_basic outputmakefile FORCE
  64. 479     $(Q)$(MAKE) $(build)=scripts/kconfig $@
  65. 480  
  66. 481  else
  67. 482  # ===========================================================================
  68. 483  # Build targets only - this includes vmlinux, arch specific targets, clean
  69. 484  # targets and others. In general all targets except *config targets.
  70. 485  
  71. 486  # Additional helpers built in scripts/
  72. 487  # Carefully list dependencies so we do not try to build scripts twice
  73. 488  # in parallel
  74. 489  PHONY += scripts
  75. 490  scripts: scripts_basic include/config/auto.conf
  76. 491     $(Q)$(MAKE) $(build)=$(@)
  77. 492  
  78. 493  ifeq ($(dot-config),1)
  79. 494  # Read in config
  80. 495  -include include/config/auto.conf
复制代码

第424行定义了变量version_h,这变量保存版本号文件,此文件是自动生成的。文件include/generated/version_autogenerated.h内容如下图所示:
第12章27159.png

图 23.3.11版本号文件

第425行定义了变量timestamp_h,此变量保存时间戳文件,此文件也是自动生成的。文件include/generated/timestamp_autogenerated.h内容如下图所示:
第12章27317.png

图 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函数格式如下:
  1. $(words <text>)
复制代码

很明显,MAKECMDGOALS的单词个数是1个,所以条件不成立,mixed-targets继续为0。综上所述,这些变量值如下:
  1. config-targets = 1
  2. mixed-targets = 0
  3. 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代码段
  1. 1703 PHONY += FORCE
  2. 1704 FORCE:
复制代码

可以看出FORCE是没有规则和依赖的,所以每次都会重新生成FORCE。当FORCE作为其他目标的依赖时,由于FORCE总是被更新过的,因此依赖所在的规则总是会执行的。
依赖scripts_basic和outputmakefile在顶层Makefile中的内容如下:
示例代码 顶层Makefile代码段
  1. 396  # Basic helpers built in scripts/
  2. 397  PHONY += scripts_basic
  3. 398  scripts_basic:
  4. 399     $(Q)$(MAKE) $(build)=scripts/basic
  5. 400     $(Q)rm -f .tmp_quiet_recordmcount
  6. 401  
  7. 402  # To avoid any implicit rule to kick in, define an empty command.
  8. 403  scripts/basic/%: scripts_basic ;
  9. 404  
  10. 405  PHONY += outputmakefile
  11. 406  # outputmakefile generates a Makefile in the output directory, if using a
  12. 407  # separate output directory. This allows convenient use of make in the
  13. 408  # output directory.
  14. 409  outputmakefile:
  15. 410  ifneq ($(KBUILD_SRC),)
  16. 411     $(Q)ln -fsn $(srctree) source
  17. 412     $(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
  18. 413         $(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
  19. 414  endif
复制代码

第410行,判断KBUILD_SRC是否为空,只有变量KBUILD_SRC不为空的时候outputmakefile才有意义,经过我们前面的分析KBUILD_SRC为空,所以outputmakefile无效,只有scripts_basic是有效的。
第398~400行是scripts_basic的规则,其对应的命令用到了变量Q、MAKE和build,其中:
  1. Q=@或为空
  2. MAKE=make
复制代码

变量build是在scripts/Kbuild.include文件中有定义,定义如下:
示例代码 Kbuild.include代码段
  1. 177 ###
  2. 178 # Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
  3. 179 # Usage:
  4. 180 # $(Q)$(MAKE) $(build)=dir
  5. 181 build := -f $(srctree)/scripts/Makefile.build obj
复制代码

从上面的示例代码可以看出build=-f $(srctree)/scripts/Makefile.build obj,经过前面的分析可知,变量srctree为”.”,因此:
  1. build=-f ./scripts/Makefile.build obj
复制代码

scripts_basic展开以后如下:
  1. scripts_basic:
  2.         @make -f ./scripts/Makefile.build obj=scripts/basic                //也可以没有@,视配置而定
  3.         @rm -f .tmp_quiet_recordmcount                                             //也可以没有@
复制代码

scripts_basic会调用文件./scripts/Makefile.build,这个我们后面在分析。
接着回到Makefile第478行的%config处,内容如下:
  1. %config: scripts_basic outputmakefile FORCE
  2.     $(Q)$(MAKE) $(build)=scripts/kconfig $@
复制代码

将命令展开就是:
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig    //也可以没有@
同样也跟文件./scripts/Makefile.build有关,我们后面再分析此文件。使用如下命令配置uboot,并观察其配置过程:
  1. make zynq_altk_defconfig V=1
复制代码

配置过程如下图所示:
第12章30356.png

图 23.3.13 uboot配置过程

从上图可以看出,我们的分析是正确的,接下来就要结合下面两行命令重点分析一下文件scripts/Makefile.build。
①、scripts_basic目标对应的命令
  1. @make -f ./scripts/Makefile.build obj=scripts/basic
复制代码

②、%config目标对应的命令
  1. @make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
复制代码

12.3.14Makefile.build脚本分析
从上一小节可知,“make xxx_defconfig”配置uboot的时候如下两行命令会执行脚本scripts/Makefile.build:
  1. @make -f ./scripts/Makefile.build obj=scripts/basic
  2. @make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
复制代码

依次来分析一下:
1、scripts_basic目标对应的命令
scripts_basic目标对应的命令为:@make -f ./scripts/Makefile.build obj=scripts/basic。打开文件scripts/Makefile.build,有如下代码:
示例代码 Makefile.build代码段
  1. 8  # Modified for U-Boot
  2. 9  prefix := tpl
  3. 10 src := $(patsubst $(prefix)/%,%,$(obj))
  4. 11 ifeq ($(obj),$(src))
  5. 12 prefix := spl
  6. 13 src := $(patsubst $(prefix)/%,%,$(obj))
  7. 14 ifeq ($(obj),$(src))
  8. 15 prefix := .
  9. 16 endif
  10. 17 endif
复制代码

第9行定义了变量prefix值为tpl。
第10行定义了变量src,这里用到了函数patsubst,此行代码展开后为:
  1. $(patsubst tpl/%,%, scripts/basic)
复制代码

patsubst是替换函数,格式如下:
  1. $(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代码段
  1. 56 # The filename Kbuild has precedence over Makefile
  2. 57 kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
  3. 58 kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
  4. 59 include $(kbuild-file)
复制代码

将kbuild-dir展开后为:
  1. $(if $(filter /%, scripts/basic),  scripts/basic, ./scripts/basic),
复制代码

因为没有以“/”为开头的单词,所以$(filter /%, scripts/basic)的结果为空,kbuild-dir=./scripts/basic。
将kbuild-file展开后为:
  1. $(if $(wildcard ./scripts/basic/Kbuild), ./scripts/basic/Kbuild, ./scripts/basic/Makefile)
复制代码

因为scrpts/basic目录中没有Kbuild这个文件,所以kbuild-file= ./scripts/basic/Makefile。最后将59行展开,即:
  1. include ./scripts/basic/Makefile
复制代码

也就是读取scripts/basic下面的Makefile文件。
继续分析scripts/Makefile.build,如下代码:
示例代码 Makefile.build代码段
  1. 116 __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
  2. 117      $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
  3. 118      $(subdir-ym) $(always)
  4. 119     @:
复制代码

__build是默认目标,因为命令“@make -f ./scripts/Makefile.build obj=scripts/basic”没有指定目标,所以会使用到默认目标:__build。在顶层Makefile中,KBUILD_BUILTIN为1,KBUILD_MODULES为0,因此展开后目标__build为:
  1. __build:$(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)
  2.         @:
复制代码

可以看出目标__build有5个依赖:builtin-target、lib-target、extra-y、subdir-ym和always。这5个依赖的具体内容我们就不通过源码来分析了,直接在scripts/Makefile.build中输入下图所示内容,将这5个变量的值打印出来:
第12章33153.png

图 23.3.14输出变量

执行如下命令:
  1. make zynq_altk_defconfig V=1
复制代码

结果如下图所示:
第12章33258.png

图 23.3.15输出结果

从上图可以看出,只有always有效,因此__build最终为:
  1. __build: scripts/basic/fixdep
  2.         @:
复制代码

__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,各个变量值如下:
  1. src= scripts/kconfig
  2. kbuild-dir = ./scripts/kconfig
  3. kbuild-file = ./scripts/kconfig/Makefile
  4. include ./scripts/kconfig/Makefile
复制代码

可以看出,Makefilke.build会读取scripts/kconfig/Makefile中的内容,此文件有如下所示内容:
示例代码 scripts/kconfig/Makefile代码段
  1. 120 %_defconfig: $(obj)/conf
  2. 121     $(Q)[        DISCUZ_CODE_1618        ]lt; $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)
  3. 122
  4. 123 # Added for U-Boot (backward compatibility)
  5. 124 %_config: %_defconfig
  6. 125     @:
复制代码

目标%_defconfig刚好和我们输入的xxx_defconfig匹配,所以会执行这条规则。依赖为$(obj)/conf,展开后就是scripts/kconfig/conf。接下来就是检查并生成依赖scripts/kconfig/conf。conf是主机软件,到这里我们就打住,不要纠结conf是怎么编译出来的,否则就越陷越深,像conf这种主机所使用的工具类软件我们一般不关心它是如何编译产生的。如果一定要看是conf是怎么生成的,可以输入如下命令重新配置uboot,在重新配置uboot的过程中就会输出conf编译信息。
  1. make distclean
  2. make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zynq_altk_defconfig V=1
复制代码

结果如下图所示:
第12章34431.png

图 23.3.16编译过程

得到scripts/kconfig/conf以后就要执行目标%_defconfig的命令:
  1. $(Q)[        DISCUZ_CODE_1620        ]lt; $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)
复制代码

相关的变量值如下:
  1. silent=-s或为空
  2. SRCARCH=..
  3. Kconfig=Kconfig
复制代码

将其展开就是:
  1. @ scripts/kconfig/conf --defconfig=arch/../configs/xxx_defconfig Kconfig
复制代码

上述命令用到了xxx_defconfig文件,比如zynq_altk_defconfig。这里会将zynq_altk_defconfig中的配置输出到.config文件中,最终生成uboot根目录下的.config文件。
这个就是命令make xxx_defconfig执行流程,总结一下如下图所示:
第12章34885.png

图 23.3.17 make xxx_defconfig执行流程图

至此,make xxx_defconfig就分析完了,接下来就要分析一下u-boot.bin是怎么生成的了。
12.3.15make过程
配置好uboot以后就可以直接make编译了,因为没有指明目标,所以会使用默认目标,主Makefile中的默认目标如下:
示例代码 顶层Makefile代码段
  1. 128  # That's our default target when none is given on the command line
  2. 129  PHONY := _all
  3. 130  _all:
复制代码

目标_all又依赖于all,如下所示:
示例代码 顶层Makefile代码段
  1. 194  # If building an external module we do not care about the all: rule
  2. 195  # but instead _all depend on modules
  3. 196  PHONY += all
  4. 197  ifeq ($(KBUILD_EXTMOD),)
  5. 198  _all: all
  6. 199  else
  7. 200  _all: modules
  8. 201  endif
复制代码

如果KBUILD_EXTMOD为空的话_all依赖与all。这里不编译模块,所以KBUILD_EXTMOD肯定为空,_all的依赖就是all。在主Makefile中all目标规则如下:
示例代码 顶层Makefile代码段
  1. 859  all:       $(ALL-y) cfg
  2. 860  ifeq ($(CONFIG_DM_I2C_COMPAT)$(CONFIG_SANDBOX),y)
  3. 861     @echo "===================== WARNING ======================"
  4. 862     @echo "This board uses CONFIG_DM_I2C_COMPAT. Please remove"
  5. 863     @echo "(possibly in a subsequent patch in your series)"
  6. 864     @echo "before sending patches to the mailing list."
  7. 865     @echo "===================================================="
  8. 866  endif
  9. 867     @# Check that this build does not use CONFIG options that we do not
  10. 868     @# know about unless they are in Kconfig. All the existing CONFIG
  11. 869     @# options are whitelisted, so new ones should not be added.
  12. 870     $(call cmd,cfgcheck,u-boot.cfg)
复制代码

从859行可以看出,all目标依赖$(ALL-y)和cfg,而在顶层Makefile中,ALL-y如下:
示例代码 顶层Makefile代码段
  1. 767  # Always append ALL so that arch config.mk's can add custom ones
  2. 768  ALL-y += u-boot.srec u-boot.bin u-boot.sym System.map binary_size_check
  3. 769  
  4. 770  ALL-$(CONFIG_ONENAND_U_BOOT) += u-boot-onenand.bin
  5. 771  ifeq ($(CONFIG_SPL_FSL_PBL),y)
  6. 772  ALL-$(CONFIG_RAMBOOT_PBL) += u-boot-with-spl-pbl.bin
  7. 773  else
  8. 774  ifneq ($(CONFIG_SECURE_BOOT), y)
  9. 775  # For Secure Boot The Image needs to be signed and Header must also
  10. 776  # be included. So The image has to be built explicitly
  11. 777  ALL-$(CONFIG_RAMBOOT_PBL) += u-boot.pbl
  12. 778  endif
  13. 779  endif
  14. 780  ALL-$(CONFIG_SPL) += spl/u-boot-spl.bin
  15. 781  ifeq ($(CONFIG_MX6)$(CONFIG_SECURE_BOOT), yy)
  16. 782  ALL-$(CONFIG_SPL_FRAMEWORK) += u-boot-ivt.img
  17. 783  else
  18. 784  ALL-$(CONFIG_SPL_FRAMEWORK) += u-boot.img
  19. 785  endif
  20. 786  ALL-$(CONFIG_TPL) += tpl/u-boot-tpl.bin
  21. 787  ALL-$(CONFIG_OF_SEPARATE) += u-boot.dtb
  22. 788  ifeq ($(CONFIG_SPL_FRAMEWORK),y)
  23. 789  ALL-$(CONFIG_OF_SEPARATE) += u-boot-dtb.img
  24. 790  endif
  25. 791  ALL-$(CONFIG_OF_HOSTFILE) += u-boot.dtb
  26. 792  ifneq ($(CONFIG_SPL_TARGET),)
  27. 793  ALL-$(CONFIG_SPL) += $(CONFIG_SPL_TARGET:"%"=%)
  28. 794  endif
  29. 795  ALL-$(CONFIG_REMAKE_ELF) += u-boot.elf
  30. 796  ALL-$(CONFIG_EFI_APP) += u-boot-app.efi
  31. 797  ALL-$(CONFIG_EFI_STUB) += u-boot-payload.efi
  32. 798  
  33. 799  ifneq ($(BUILD_ROM)$(CONFIG_BUILD_ROM),)
  34. 800  ALL-$(CONFIG_X86_RESET_VECTOR) += u-boot.rom
  35. 801  endif
  36. 802  
  37. 803  # enable combined SPL/u-boot/dtb rules for tegra
  38. 804  ifeq ($(CONFIG_TEGRA)$(CONFIG_SPL),yy)
  39. 805  ALL-y += u-boot-tegra.bin u-boot-nodtb-tegra.bin
  40. 806  ALL-$(CONFIG_OF_SEPARATE) += u-boot-dtb-tegra.bin
  41. 807  endif
  42. 808  
  43. 809  # Add optional build target if defined in board/cpu/soc headers
  44. 810  ifneq ($(CONFIG_BUILD_TARGET),)
  45. 811  ALL-y += $(CONFIG_BUILD_TARGET:"%"=%)
  46. 812  endif
复制代码

从上面的示例代码代码可以看出,ALL-y包含u-boot.srec、u-boot.bin、u-boot.sym、System.map和binary_size_check这几个文件。根据uboot的配置情况也可能包含其他的文件,比如:
  1. 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”,所以展开以后就是:
  1. ALL-y += u-boot-onenand.bin
复制代码

这个就是.config里面的配置参数的含义,这些参数其实都是变量,后面跟着变量值,会在顶层Makefile或者其他Makefile中调用这些变量。
ALL-y里面有个u-boot.bin,这个就是我们最终需要的uboot二进制可执行文件,所作的所有工作就是为了它。在顶层Makefile中找到u-boot.bin目标对应的规则,如下所示:
示例代码 顶层Makefile代码段
  1. 881  ifeq ($(CONFIG_MULTI_DTB_FIT),y)
  2. 882  
  3. 883  fit-dtb.blob: dts/dt.dtb FORCE
  4. 884     $(call if_changed,mkimage)
  5. 885  
  6. 886  MKIMAGEFLAGS_fit-dtb.blob = -f auto -A $(ARCH) -T firmware -C none -O u-boot \
  7. 887     -a 0 -e 0 -E \
  8. 888     $(patsubst %,-b arch/$(ARCH)/dts/%.dtb,$(subst ",,$(CONFIG_OF_LIST))) -d /dev/null
  9. 889  
  10. 890  u-boot-fit-dtb.bin: u-boot-nodtb.bin fit-dtb.blob
  11. 891     $(call if_changed,cat)
  12. 892  
  13. 893  u-boot.bin: u-boot-fit-dtb.bin FORCE
  14. 894     $(call if_changed,copy)
  15. 895  else ifeq ($(CONFIG_OF_SEPARATE),y)
  16. 896  u-boot-dtb.bin: u-boot-nodtb.bin dts/dt.dtb FORCE
  17. 897     $(call if_changed,cat)
  18. 898  
  19. 899  u-boot.bin: u-boot-dtb.bin FORCE
  20. 900     $(call if_changed,copy)
  21. 901  else
  22. 902  u-boot.bin: u-boot-nodtb.bin FORCE
  23. 903     $(call if_changed,copy)
  24. 904  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代码段
  1. 226 ###
  2. 227 # if_changed      - execute command if any prerequisite is newer than
  3. 228 #                   target, or command line has changed
  4. 229 # if_changed_dep  - as if_changed, but uses fixdep to reveal dependencies
  5. 230 #                   including used config symbols
  6. 231 # if_changed_rule - as if_changed but execute rule instead
  7. 232 # See Documentation/kbuild/makefiles.txt for more info
  8. 233
  9. 234 ifneq ($(KBUILD_NOCMDDEP),1)
  10. 235 # Check if both arguments has same arguments. Result is empty string if equal.
  11. 236 # User may override this check using make KBUILD_NOCMDDEP=1
  12. 237 arg-check = $(strip $(filter-out $(cmd_$(1)), $(cmd_$@)) \
  13. 238                     $(filter-out $(cmd_$@),   $(cmd_$(1))) )
  14. 239 else
  15. 240 arg-check = $(if $(strip $(cmd_$@)),,1)
  16. 241 endif
  17. 242
  18. 243 # Replace >[        DISCUZ_CODE_1630        ]lt; with >[        DISCUZ_CODE_66        ]lt; to preserve $ when reloading the .cmd file
  19. 244 # (needed for make)
  20. 245 # Replace >#< with >\#< to avoid starting a comment in the .cmd file
  21. 246 # (needed for make)
  22. 247 # Replace >'< with >'\''< to be able to enclose the whole string in '...'
  23. 248 # (needed for the shell)
  24. 249 make-cmd = $(call escsq,$(subst \#,\\\#,$(subst $,$,$(cmd_$(1)))))
  25. 250
  26. 251 # Find any prerequisites that is newer than target or that does not exist.
  27. 252 # PHONY targets skipped in both cases.
  28. 253 any-prereq = $(filter-out $(PHONY),$?) $(filter-out $(PHONY) $(wildcard $^),$^)
  29. 254
  30. 255 # Execute command if command has changed or prerequisite(s) are updated.
  31. 256 #
  32. 257 if_changed = $(if $(strip $(any-prereq) $(arg-check)),                       \
  33. 258     @set -e;                                                             \
  34. 259     $(echo-cmd) $(cmd_$(1));                                             \
  35. 260     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代码段
  1. 942  u-boot-nodtb.bin: u-boot FORCE
  2. 943     $(call if_changed,objcopy)
  3. 944     $(call DO_STATIC_RELA,[        DISCUZ_CODE_1631        ]lt;,$@,$(CONFIG_SYS_TEXT_BASE))
  4. 945     $(BOARD_SIZE_CHECK)
复制代码

目标u-boot-nodtb.bin又依赖于u-boot,顶层Makefile中u-boot相关规则如下:
示例代码 顶层Makefile代码段
  1. 1260 u-boot:    $(u-boot-init) $(u-boot-main) u-boot.lds FORCE
  2. 1261    +$(call if_changed,u-boot__)
  3. 1262 ifeq ($(CONFIG_KALLSYMS),y)
  4. 1263    $(call cmd,smap)
  5. 1264    $(call cmd,u-boot__) common/system_map.o
  6. 1265 endif
复制代码

目标u-boot依赖于u-boot_init、u-boot-main和u-boot.lds。u-boot_init和u-boot-main是两个变量,在顶层Makefile中有定义,值如下:
示例代码 顶层Makefile代码段
  1. 712  u-boot-init := $(head-y)
  2. 713  u-boot-main := $(libs-y)
复制代码

$(head-y)跟CPU架构有关,我们使用的是ARM芯片,所以head-y在arch/arm/Makefile中被指定为:
  1. head-y := arch/arm/cpu/$(CPU)/start.o
复制代码

根据23.3.12小节的分析,我们知道CPU=armv7,因此head-y展开以后就是:
  1. head-y := arch/arm/cpu/armv7/start.o
复制代码

因此:
  1. u-boot-init= arch/arm/cpu/armv7/start.o
复制代码

$(libs-y)在顶层Makefile中被定义为uboot所有子目录下build-in.o的集合,代码如下:
示例代码 顶层Makefile代码段
  1. 650  libs-y += lib/
  2. 651  libs-$(HAVE_VENDOR_COMMON_LIB) += board/$(VENDOR)/common/
  3. 652  libs-$(CONFIG_OF_EMBED) += dts/
  4. 653  libs-y += fs/
  5. 654  libs-y += net/
  6. 655  libs-y += disk/
  7. 656  libs-y += drivers/
  8. 657  libs-y += drivers/dma/
  9. 658  libs-y += drivers/gpio/
  10. 659  libs-y += drivers/i2c/
  11. ......
  12. 692  libs-y += cmd/
  13. 693  libs-y += common/
  14. 694  libs-y += env/
  15. 695  libs-$(CONFIG_API) += api/
  16. 696  libs-$(CONFIG_HAS_POST) += post/
  17. 697  libs-y += test/
  18. 698  libs-y += test/dm/
  19. 699  libs-$(CONFIG_UT_ENV) += test/env/
  20. 700  libs-$(CONFIG_UT_OVERLAY) += test/overlay/
  21. 701  
  22. 702  libs-y += $(if $(BOARDDIR),board/$(BOARDDIR)/)
  23. 703  
  24. 704  libs-y := $(sort $(libs-y))
  25. 705  
  26. 706  u-boot-dirs    := $(patsubst %/,%,$(filter %/, $(libs-y))) tools examples
  27. 707  
  28. 708  u-boot-alldirs := $(sort $(u-boot-dirs) $(patsubst %/,%,$(filter %/, $(libs-))))
  29. 709  
  30. 710  libs-y     := $(patsubst %/, %/built-in.o, $(libs-y))
复制代码

从上面的代码可以看出,libs-y都是uboot各子目录的集合,最后:
  1. 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代码段
  1. 1392 u-boot.lds: $(LDSCRIPT) prepare FORCE
  2. 1393    $(call if_changed_dep,cpp_lds)
复制代码

接下来的重点就是各子目录下的built-in.o是怎么生成的,以drivers/gpio/built-in.o为例,在drivers/gpio/目录下会有个名为.built-in.o.cmd的文件,此文件内容如下:
示例代码 drivers/gpio/.built-in.o.cmd代码
  1. 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就可以看到链接的过程:
  1. make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zynq_altk_defconfig V=1
  2. make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- V=1
复制代码

编译的时候会有如下图所示内容输出:
第12章44976.png

图 23.3.18编译内容输出

将其整理一下,内容如下:
  1. arm-linux-gnueabihf-ld.bfd   -pie  --gc-sections -Bstatic  --no-dynamic-linker -Ttext 0x4000000
  2. -o u-boot -T u-boot.lds
  3. arch/arm/cpu/armv7/start.o
  4. --start-group  arch/arm/cpu/built-in.o  
  5. arch/arm/cpu/armv7/built-in.o  
  6. arch/arm/lib/built-in.o  
  7. arch/arm/mach-zynq/built-in.o  
  8. board/xilinx/zynq/built-in.o  
  9. cmd/built-in.o  
  10. common/built-in.o  
  11. disk/built-in.o  
  12. drivers/built-in.o  
  13. drivers/dma/built-in.o  
  14. drivers/gpio/built-in.o  
  15. ……
  16. drivers/usb/phy/built-in.o  
  17. drivers/usb/ulpi/built-in.o  
  18. dts/built-in.o  
  19. env/built-in.o  
  20. fs/built-in.o  
  21. lib/built-in.o  
  22. net/built-in.o  
  23. test/built-in.o  
  24. test/dm/built-in.o
  25. --end-group arch/arm/lib/eabi_compat.o  
  26. 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”命令的流程,如下图所示:
第12章46025.png

图 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的流程。至于更具体的实现,有兴趣的可以参考一下其他资料。





阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出0入0汤圆

发表于 2020-8-30 00:50:25 | 显示全部楼层
唉,想下来看看,可看到那几个G的毒盘就失去性趣了,而且还是压缩的。。。。。。要么全下,要么别下。。。。。。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-26 20:11

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表