5 分钟带你掌握 Makefile 分析

Wesley13
• 阅读 322

摘要:Makefile是一个名为GNU-Make软件所需要的脚本文件,该脚本文件可以指导Make软件控制arm-gcc等工具链去编译工程文件最终得到可执行文件,几乎所有的Linux发行版都内置了GNU-Make软件,VScode等多种IED也内置了Make程序。

什么是Makefile

Makefile是一个名为GNU-Make软件所需要的脚本文件,该脚本文件可以指导Make软件控制arm-gcc等工具链去编译工程文件最终得到可执行文件,几乎所有的Linux发行版都内置了GNU-Make软件,VScode等多种IED也内置了Make程序。

你见到的xxx.mk文件或者Makefile都统称为Makefile脚本文件。

Makefile脚本文件的语法学习可以参考:

https://www.gnu.org/software/make/manual/make.html (GNU make官方文档)

https://seisman.github.io/how-to-write-makefile/overview.html (跟我一起写Makefile 陈皓)

Makefile的规则

Makefile的规则如下,这里的[TAB]指键盘上的TAB按键,不是空格,如果在命令前输入了空格则会造成错误,并且在Makefile中TAB键不能随意使用:

目标 : 依赖

[TAB]命令

例如:

Hello :

@echo “Hello”

这时执行make命令就会输出一条语句”Hello”,Hello是目标,依赖为空,为了生成目标,需要执行echo “Hello”语句,从而导致输出Hello。

例如:假设我们有一个Hello.c C语言源文件,需要将其编译不链接为Hello.o文件,最后在进行连接,Makefile内容如下:

Hello.out : Hello.o

gcc -o Hello.out Hello.o

Hello.o : Hello.c

gcc -c -o Hello.o Hello.c

这时执行make命令,make解释器发现目标为“Hello.out”,但是生成Hello.out需要Hello.o,发现目录下找不到“Hello.o”,就向下查找是否有生成Hello.o的规则,找到了,发现”Hello.o”依赖于”Hello.c”,在目录下也找到了Hello.c,就执行语句“gcc -c -o Hello.o Hello.c”生成”Hello.o”,只要编译过程不出错,即可得到”Hello.o”,这时可以执行“gcc -o Hello.out Hello.o“生成”Hello.out”

从哪里开始分析?

这里可以用分析一个C语言或Java语言程序来类比,一般都是根据程序是执行流来进行分析,也就是先找到main函数,因为main函数是程序的执行入口,Makefile也有执行入口,在执行make命令时,make解释器默认搜索当前目录下名为“Makefile”的文件,找到后,执行生成第一个目标的命令及生成其依赖所需的命令。

这里选择在SDK/Targets目录中STM32L431_BearPi工程中的GCC目录下的Makefile开始分析。

第1行到第140行都是设置一些变量和导入一些makefile文件(其中也没有任何规则,都是进行一些变量的设置),第143行是第一条规则

5 分钟带你掌握 Makefile 分析

当我们执行make或make all时,就开始生成all目标,其依赖于BUILD_DIR(GCC/build)目录中的TARGET(Huawei_LiteOS).elf文件,BUILD_DIR和TARGET为两个变量,一开始就被赋值,如下图所示,实际使用时$(变量)会被替换为变量的值,例如$(TARGET).elf最终会被替换为Huawei_LiteOS.elf。

5 分钟带你掌握 Makefile 分析

5 分钟带你掌握 Makefile 分析

可是Huawei_LiteOS.elf还不存在,make只好继续向下查找是否有生成Huawei_LiteOS.elf的规则,好在第147行的目标为Huawei_LiteOS.elf,这就是生成Huawei_LiteOS.elf的规则,该规则依赖为$(OBJ_DIRS) $(C_OBJ) $(S_OBJ)分别对应三个目录,这三个目录都不存在,所以make只好继续向下查找,它发现第152行正好为目标是该目录的规则,就创建了该目录,解决了$(OBJ_DIRS)这个依赖,接着该处理$(C_OBJ)这个依赖

Make向下查找依赖发现位于第156行出现生成这个以来的规则,这里的$(C_OBJ):$(BUILD_DIR)/%.o:%.c对应makefile中的静态模式,我这里简单的说一下,大家如果想深入了解可以自行百度。

静态模型的格式如下:

目标列表: 与目标列表相匹配的模型: 与依赖相匹配的模型

[TAB]命令

来看一个例子,

5 分钟带你掌握 Makefile 分析

目标列表中有foo.o、bar.o和test.s三个值,首先将匹配%.o的模型找出来,test.s不匹配就被遗弃了,将匹配的foo.o和bar.o替换为foo.c和bar.c,最终这一条规则等于执行了下列这两条规则,为什么要这样做呢?你可以试想以下,假设目标列表中有几千个文件,这样做的话是不是就可以少写很多规则:

5 分钟带你掌握 Makefile 分析

回到LiteOS_Lab的Makefile上来,156行将C_OBJ变量中的符合build/xxx.o格式的文件作为xxx.c格式的依赖,C_OBJ变量的赋值如下图所示:

5 分钟带你掌握 Makefile 分析

$(patsubst PATTERN, REPLACEMENT, TEXT)函数的作用是模式替换,将TEXT中以空格隔开的每个单词(文件名),符合PATTERN格式的替换为REPLACEMENT格式,例如第124行,将所有的C_SOURCE变量中的文件名,凡是只要在SDK/ iot_link目录下的都替换为.o后缀,SDK/ iotlink目录中有一个符合该模型的文件,link_main.c,在执行该规则对应的命令时,目标文件为link_main.o,第125和126行同上。

5 分钟带你掌握 Makefile 分析

这里以link_main.c为例向大家讲解指令,经过模式替换后规则如下:

link_main.o: link_main.c

$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(@:%.o=%.lst) $< -o $@

这里的CC是指arm-none-eabi-gcc,CFLAGS是指各类编译参数,例如-MMD -MP -Wno-missing-braces,$(@:%.o=%.lst)函数的作用是将$@目标符合%.o模型的值替换为%. lst,这里就将link_main.o替换link_main. lst,$<是指第一个依赖,$@是指目标,组合的命令后如下:

arm-none-eabi-gcc -c -MMD -MP -Wno-missing-braces -Wa,-a,-ad,-alms=link_main. lst link_main.c -o link_main.o。一句话来说就是将所有的.c源文件编译不链接生成.o文件和.lst文件,等待后续进一步操作。

第160和161行的操作类似于上面的操作,将所有的汇编文件都编译不链接生成.o文件等待后续进一步操作。

第147到149行规则所需的依赖全部都生成好了,可以开始执行该规则的命令,将所有依赖通过LDFLAGS变量中的值链接生成Huawei_LiteOS.elf文件并列出程序文件中各段的大小。

LDFLAGS变量中通过MCU变量定义了内核相关参数,例如ARM架构的版本以及是否支持硬件浮点数运算等参数,如下图所示,如果你需要将工程移植到不是STM32L431系类的MCU上,就需要修改MCU变量的值。

5 分钟带你掌握 Makefile 分析

5 分钟带你掌握 Makefile 分析

LDFLAGS变量中通过LDSCRIPT变量读取os.ld链接脚本来控制程序该如何链接,每个段应该存放在程序中的何处,在os.ld链接脚本中还指明了MCU的RAM和FLASH大小及起始位置,我们在进行移植时也需要修改。

Huawei_LiteOS.elf文件是第143至145行规则的依赖,将该elf文件转换为Huawei_LiteOS.hex和Huawei_LiteOS.bin文件,即可烧录。

现在大家应该明白make控制下的整个程序编译过程了吧,以及Makefile文件起到的作用,我们再来看看前面的include导入的文件,如下图所示:

5 分钟带你掌握 Makefile 分析

首先导入了.config文件,这由Kconfig软件读取用户通过图形化配置的各项参数信息生成的,其中包含了对SDK中各组件参数的配置信息,如下图所示:

5 分钟带你掌握 Makefile 分析

5 分钟带你掌握 Makefile 分析

以AT组件为例,CONFIG_AT_ENABLE=y代表使能AT组件;CONFIG_AT_DEVNAME="atdev"将AT组件用到的串口以"atdev"注册到driver层中;CONFIG_AT_OOBTABLEN=6 OOB表的长度配置为6个,这时用于接收异步数据的结构体,意味着我们最多能配置6个特定字符串,当这时字符串出现时调用相应处理函数进行处理;CONFIG_AT_RECVMAXLEN=1024将AT框架中的接收缓存区大小配置为1024字节,如果你的MCU资源受限可以减少这里的大小;CONFIG_AT_TASKPRIOR=10将AT任务的优先级配置为10,注意:这里只有第35行这条语句会影响Make的编译,其余语句是为了记录用户做了哪些配置和生成iot_config.h所用。

.config文件中所有的组件配置都和上面分析的一致,如果组件没有被使能如下图所示:

5 分钟带你掌握 Makefile 分析

相信大家看到这里又有新的疑问了,这些配置是如何影响到程序的编译呢?回到前面的第70行include $(SDK_DIR)/iot_link/iot.mk,来看看这个SDK/iot_link目录下的iot.mk文件中有什么你就有答案了,如下图所示:

5 分钟带你掌握 Makefile 分析

该Makefile将每个组件所属文件夹下的Makefile也导入进来了,我们还是以AT框架为例,第31行,导入at目录下的at.mk文件,该Makefile内容如下图所示:

5 分钟带你掌握 Makefile 分析

看到了吧,第7行与前面的CONFIG_AT_ENABLE=y变量相对应,ifeq ($(CONFIG_AT_ENABLE),y)语句的意思是如果CONFIG_AT_ENABLE变量的值为y,则将ifeq到endif之间的语句全部执行。

第8至9行将at目录下所有.c文件添加到C_SOURCES变量中,注意这里用的是+=是追加上去。

第11至12行将at目录下所有.h文件所在路径(注意是路径,通过-I参数指定头文件所在的路径,这样编译器才能找到头文件,否则会因为找不到头文件导致编译失败)添加到C_INCLUDES变量中,注意这里用的是+=是追加上去。

第14至14行将-D CONFIG_AT_ENABLE=1文本追加到C_DEFS变量中。

这三个变量大家都很眼熟吧,这就是工程目录/GCC目录中Makefile中的那三个变量,如下图所示:

5 分钟带你掌握 Makefile 分析

5 分钟带你掌握 Makefile 分析

这样AT组件中的所有源文件和头文件就参与了编译。

回到第三个include,include $(MAKEFILE_DIR)/project.mk,这是用于包含(引入)工程目录/GCC目录下的project.mk,该Makefile部分内容如下图所示:

5 分钟带你掌握 Makefile 分析

主要用于包含Hal库中的文件以及用户自己添加进去的文件,这也是移植时需要进行修改的文件之一,大家可以仿照我前面分析的方法自己分析一下。最终所有被添加进入的.c源文件会被追加到C_SOURCES变量中,所有.h头文件所在的路径会被追加到C_INCLUDES变量中。

总结

以上就是LiteOS_Lab中Makefile运行的机制了,大家可以自己跟着文章全部分析分析一边以加深影响,SDK中所有的Makefile文件都不需要也不能进行修改,只需要修改工程中的三个Makefile,.config(这个不用手动修改,可以通过图形化配置进行修改),Makefile(根据目标MCU修改MCU相关的参数即可,也就是MCU这个变量的值),project.mk(根据目标MCU修改、添加或删除库文件以及用户文件以及最后的C_DEFS变量即可)。

点赞
收藏
评论区
推荐文章
Jacquelyn38 Jacquelyn38
1年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。 1、使用解构获取json数据let jsonData   id: 1, status: "OK", data: ['a', 'b'] ; let  id, status, data: number   jsonData; console.log(id, status, number )
blmius blmius
1年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录 问题 用navicat导入数据时,报错: 原因这是因为当前的MySQL不支持datetime为0的情况。 解决修改sql\mode: sql\mode:SQL Mode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。 全局s
Stella981 Stella981
1年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置 1、virsh edit centos7 找到“memory”和“vcpu”标签,将 <name>centos7</name> <uuid>2220a6d1-a36a-4fbb-8523-e078b3dfe795</uuid>
Wesley13 Wesley13
1年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表: **时辰** **时间** **24时制** 子时 深夜 11:00 - 凌晨 01:00 23:00 - 01 :00 丑时 上午 01:00 - 上午 03:00 01:00 - 03 :00 寅时 上午 03:00 - 上午 0
Wesley13 Wesley13
1年前
MySQL查询按照指定规则排序
1.按照指定(单个)字段排序 select * from table_name order id desc; 2.按照指定(多个)字段排序 select * from table_name order id desc,status desc; 3.按照指定字段和规则排序 selec
Stella981 Stella981
1年前
Angular material mat
Icon Icon Name mat-icon code _add\_comment_ add comment icon <mat-icon> add\_comment</mat-icon> _attach\_file_ attach file icon <mat-icon> attach\_file</mat-icon> _attach\
Wesley13 Wesley13
1年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
#### 背景描述 # Time: 2019-01-24T00:08:14.705724+08:00 # User@Host: **[**] @ [**] Id: ** # Schema: sentrymeta Last_errno: 0 Killed: 0 # Query_time: 0.315758 Lock_
3A网络 3A网络
2个月前
开发一个不需要重写成 Hive QL 的大数据 SQL 引擎
# 开发一个不需要重写成 Hive QL 的大数据 SQL 引擎 学习大数据技术的核心原理,掌握一些高效的思考和思维方式,构建自己的技术知识体系。明白了原理,有时甚至不需要学习,顺着原理就可以推导出各种实现细节。 各种知识表象看杂乱无章,若只是学习
3A网络 3A网络
2个月前
理解 virt、res、shr 之间的关系(linux 系统篇)
# 理解 virt、res、shr 之间的关系(linux 系统篇) **前言** 想必在 linux 上写过程序的同学都有分析进程占用多少内存的经历,或者被问到这样的问题 —— 你的程序在运行时占用了多少内存(物理内存)? 通常我们可以通过 t
helloworld_34035044 helloworld_34035044
4个月前
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。 uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid() 或 uuid(sep)参数说明:sep 布尔值,生成的uuid中是否包含分隔符'',缺省为