当前位置: 首页 > article >正文

06_Uboot顶层Makefile分析_前期所做内容

目录

U-Boot顶层Makefile分析

版本号

MAKEFLAGS变量

命令输出

静默输出

设置编译结果输出目录

代码检查

模块编译

获取主机架构和系统

设置目标架构、交叉编译器和配置文件

调用scripts/Kbuild.include

交叉编译工具变量设置

导出其他变量


U-Boot顶层Makefile分析

在阅读uboot源码之前,肯定是要先看一下顶层Makefile,分析gcc版本代码的时候一定是先从顶层Makefile开始的,然后再是子Makefile,这样通过层层分析Makefile即可了解整个工程的组织结构。顶层Makefile也就是uboot根目录下的Makefile文件,由于顶层Makefile文件内容比较多,所以我们将其分开来看。

版本号

顶层Makefile一开始是版本号,内容如下(为了方便分析,顶层Makefile代码段前段行号采用 Makefile中的行号,因为uboot会更新,因此行号可能会与你所看的顶层Makefile有所不同):

 VERSION是主版本号, PATCHLEVEL是补丁版本号, SUBLEVEL是次版本号,这三个一,起构成了uboot的版本号,比如当前的uboot版本号就是"2016.03"。EXTRAVERSION是附加版本信息,NAME是和名字有关的,一般不使用这两个。

MAKEFLAGS变量

make是支持递归调用的,也就是在Makefile中使用"make”命令来执行其他的Makefile文件,一般都是子目录中的Makefile文件。假如在当前目录下存在一个"subdir”子目录,这个子目录中又有其对应的Makefile文件,那么这个工程在编译的时候其主目录中的Makefile就可以调用子目录中的Makefile,以此来完成所有子目录的编译。主目录的Makefile可以使用如下代码来编译这个子目录:

$(MAKE) -C subdir

$(MAKE)就是调用"make”命令, -C指定子目录。有时候我们需要向子make传递变量,这个时候使用"export”来导出要传递给子make的变量即可,如果不希望哪个变量传递给子make的话就使用“unexport”来声明不导出: 

export VARIABLE ......  //导出变量给子make。

unexport VARIABLE .......   //不导出变量给子make。

 

有两个特殊的变量:“SHELL”和"MAKEFLAGS”,这两个变量除非使用“unexport”声明,否则的话在整个make的执行过程中,它们的值始终自动的传递给子make。在uboot的主Makefile中有如下代码: 

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

 

命令输出

uboot默认编译是不会在终端中显示完整的命令,都是短命令,如图所示:

在终端中输出短命令虽然看起来很清爽,但是不利于分uboot的编译过程。可以通过设置变量"V=1"来实现完整的命令输出,这个在调试uboot的时候很有用,结果如图所示: 

 顶层Makefile中控制命令输出的代码如下:

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

$(origin <variable>) 

 variable是变量名, origin函数的返回值就是变量来源,因此$(origin V)就是变量V的来源。如果变量V是在命令行定义的那么它的来源就是"command line",这样"$(origin V)"和"commandline"就相等了。当这两个相等的时候变量 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的话:

 

V=0或者命令行不定义V的话: 

 

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_”的话,整个命令都不会输出。

 

 

静默输出

设置V=0或者在命令行中不定义V的话,编译uboot的时候终端中显示的短命令,但是还是会有命令输出,有时候我们在编译uboot的时候不需要输出命令,这个时候就可以使用uboot的静默输出功能。编译的时候使用"make-s"即可实现静默输出,顶层Makefile中相应的代码如下:

 

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

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

filter函数表示以pattern模式过滤text字符串中的单词,仅保留符合模式pattern的单词,可以有多个模式。函数返回值就是符合pattern的字符串。因此$(filter 4.%,$(MAKE_VERSION))的含义就是在字符串"MAKE-VERSION”中找出符合"4.%”的字符(%为通配符),MAKE_VERSION是make工具的版本号,ubuntu16.04里面默认自带的make工具版本号为 4.1,大家可以输入“make-v”查看。因此$(filter 4.%,$(MAKE_VERSION))不为空,条件成立,执行92~94行的语句。

第92行也是一个判断语句,如果$(filter %s,$(firstword x$(MAKEFLAGS)))不为空的话条件,成立,变量quiet等于"silent”。这里也用到了函数filter,在$(firstword xS(MAKEFLAGS)))中过滤出符合"%s”的单词。到了函数firstword,函数firstword是获取首单词,函数格式如下:

 $(firstword <text>)

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

 图中的两行代码用于输出$(firstword x$(MAKEFLAGS))的结果,最后修改文件mx6ull alientek emmc.sh,在里面加入"-s”选项,结果如图所示:

 修改完成以后执行mx6ull_alientek_emmc.sh,结果如图所示:

 从图可以看出第一个单词是"xrRs",将$(filter %s ,$(firstword x$(MAKEFLAGS))展开就是$(filter %s, xrRs),而$(filter %s, xrRs)的返回值肯定不为空,条件成立, quiet-silent_第101行使用export导出变量quiet、Q和KBUILD_VERBOSE。

 

设置编译结果输出目录

uboot可以将编译出来的目标文件输出到单独的目录中,在make的时候使用“O”来指定输出目录,比如"make O-out”就是设置目标文件输出到out目录中。这么做是为了将源文件和编译产生的文件分开,当然也可以不指定0参数,不指定的话源文件和编译产生的文件都在同一个目录内,一般我们不指定0参数。顶层Makefile中相关的代码如下:

 第124行判断"O”是否来自于命令行,如果来自命令行的话条件成立, KBUILD_OUTPUT就为$(O),因此变量KBUILD_OUTPUT就是输出目录。

第135行判断KBUILD_OUTPUT是否为空。

第139行调用mkdir命令,创建KBUILD_OUTPUT目录,并且将创建成功以后的绝对路径赋值给KBUILD_OUTPUT。至此,通过O指定的输出目录就存在了。

 

代码检查

uboot支持代码检查,使用命令"make C=1”使能代码检查,检查那些需要重新编译的文件。“make C-2”用于检查所有的源码文件,顶层Makefile中的代码如下:

第176行判断C是否来源于命令行,如果C来源于命令行,那就将C赋值给变量KBUILD_CHECKSRC,如果命令行没有C的话KBUILD_CHECKSRC就为0。 

 

 

模块编译

在uboot中允许单独编译某个模块,使用命令"make M=dir”即可,旧语法"makeSUBDIRS=dir”也是支持的。顶层Makefile中的代码如下:

 第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是否为空,如果为空的话就设置变量sretree为当前目录,即srctree为“.”,一般不设置 KBUILD_SRC。

第214行设置变量objtree为当前目录。

第215和216行分别设置变量src和obj,都为当前目录。

第218行设置VPATH。

第220行导出变量scrtree、objtree和VPATH。

获取主机架构和系统

接下来顶层Makefile会获取主机架构和系统,也就是我们电脑的架构和系统,代码如下:

 第227行定义了一个变量HOSTARCH,用于保存主机架构,这里调用shell命令“uname-m获取架构名称,结果如图所示:

 从图可以看出当前电脑主机架构为“x86_64", shell中的"1”表示管道,意思是将左边的输出作为右边的输入, sed-e是替换命令,"sed-e s/i.86/x86/”表示将管道输入的字符串中的"i.86"替换为"x86",其他的"sed-es"命令同理。对于我的电脑而言, HOSTARCH-x86_64。

第237行定义了变量HOSTOS,此变量用于保存主机OS的值,先使用shell命令"uname-s”来获取主机OS,结果如图所示:

从图可以看出此时的主机OS为“Linux”,使用管道将“Linux”作为后面“tr'[:upper:]"[:lower:]"的输入, "tr'[:upper:]'"[:lower:]'"表示将所有的大写字母替换为小写字母,因此得到“linux”。最后同样使用管道,将“linux”作为"sed-e'sA(cygwinl).*/cygwin/”的输入,用于将cygwin.*替换为 cygwin*因此,HOSTOS=linux。

第240行导出HOSTARCH=x86_64, HOSTOS=linux。

设置目标架构、交叉编译器和配置文件

编译uboot 的时候需要设置目标板架构和交叉编译器,

“make ARCH=armCROSS_COMPILE=arm-linux-gnueabihf-”就是用于设置ARCH和 CROSS_COMPILE,在顶层Makefile中代码如下:

 第245行判断HOSTARCH和ARCH这两个变量是否相等,主机架构(变量HOSTARCH)是x86-64,而我们编译的是ARM版本uboot,肯定不相等,所以CROsS_COMPILE= arm-linuxgnueabihf-。从示例代码可以看出,每次编译 uboot 的时候都要在make 命令后面设置ARCH 和 CROSS_COMPILE,使用起来很麻烦,可以直接修改顶层Makefile,在里面加入 ARCH和CROSS COMPILE的定义,如图所示:

 按照图所示,直接在顶层Makefile里面定义ARCH和CROSS_COMPILE,这样就不用每次编译的时候都要在make命令后面定义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里面的才是实时有效的配置。

调用scripts/Kbuild.include

 示例代码中使用"include"包含了文件scripts/Kbuild.include,此文件里面定义了很多变量,如图所示:

 在uboot的编译过程中会用到scripts/Kbuild.include中的这些变量。

 

 

交叉编译工具变量设置

上面只是设置了CROSS-COMPILE的名字,但是交叉编译器其他的工具还没有设置,顶层Makefile中相关代码如下:

 

导出其他变量

接下来在顶层Makefile会导出很多变量,代码如下:

这些变量中大部分都已经在前面定义了,我们重点来看一下下面这几个变量:

ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR

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

 修改好顶层Makefile以后执行如下命令:

make ARCH=arm CROSS COMPILE=arm-linux-gnueabihf- mytest

从图可以看到这7个变量的值,这7个变量是从哪里来的呢?在uboot根目录下有个文件叫做 config.mk,这7个变量就是在config.mk里面定义的,打开config.mk内容如下: 

第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/S(ARCH)/config.mk的内容。sinclude读取的文件如果不存在的话不会报错。

第47行读取文件$(srctree)/$(CPUDIR)/config.mk的内容。

第50行读取文件$(srctree)/$(CPUDIR)/S(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.mk中读取的文件有:

 


http://www.kler.cn/a/17602.html

相关文章:

  • 【MYSQL】数据库日志 (了解即可)
  • 越南很火的slots游戏投放Google谷歌广告策略
  • 使用Element UI实现前端分页,及el-table表格跨页选择数据,切换分页保留分页数据,限制多选数量
  • 408笔记合集
  • 【linux】centos7 换阿里云源
  • Java NIO 深度解析:构建高效的 I/O 操作
  • C++之异常处理
  • 国民技术N32G430开发笔记(15)- IAP升级 树莓派串口发送数据
  • 如何搭建chatGPT4.0模型-国内如何用chatGPT4.0
  • C语言将汉字保存到文件中
  • 如何显示文件夹的后缀和隐藏文件
  • 一分钟学会Flask框架的安装与快速使用
  • 诺派克ROPEX热封控制器维修RES-407/RES-406
  • 设计模式-创建型模式-(工厂、简单工厂、抽象工厂)
  • 有必要给孩子买台灯吗?分享四款高品质的护眼台灯
  • 处理 json 和 HttpMessageConverter--文件下载-ResponseEntity --SpringMVC 文件上传
  • 组态软件对比,未来10年发展趋势!
  • 【VAR | 时间序列】应用VAR模型时的15个注意点
  • [实训] 实验1-SPI数据传输基础实验(下)
  • 操作系统2(多处理器编程)
  • 如何使用 ChatGPT 来快速编写产品需求文档(PRD)
  • 代码随想录算法训练营(总结)|动态规划总结篇
  • 基于空间矢量脉宽调制(SVPWM)的并网逆变器研究(Simulink)
  • Java 基础进阶篇(十一)—— Arrays 与 Collections 工具类
  • 在前端开发中,何时应该使用 jQuery,何时应该使用 Vue.js
  • 定积分比较大小的常用手段。