这段时间在学习正点原子的Linux教程,对于一个只学了STM32没有接触过Linux的人来说,在ubuntu编程中的Makefile文件着实涩!!!!于是去研究分析了一番,看了一些大佬的教程,做了一个小总结,代码程序来自正点原子教学代码,文章的内容是自己的一些理解,有一些内容是引用的。
Makefile教程 博客:http://blog.csdn.net/haoel/article/details/2886
O优化等级 博客:https://blog.csdn.net/qq_31108501/article/details/51842166
推荐阅读顺序1-3-2-3
1:小谈Makefile (定义,作用)
⑴:工程的编译过程
在谈Makefile之前,先说一下普通工程的编译过程。
一个普通工程的编译过程分为4步:
①、执行预处理器命令。
i.删除所有的#define,并且展开所有的宏定义 (#define和const的区别)
ii.处理所有的预编译指令,如在.h文件开头加入的#ifndef,#define,#endif这一类
iii.处理#include,将包含文件插入此处。
iiii.删除所有的注释 /**/、//
iiiii. 添加行号和文件名标识,以便于编译时产生的错误警告能显示行号
iiiiii. 保留#pragma编译器指令(我也不明白)
②、编译。把.c/.s这一类文件编译成.o文件(.o文件=目标文件 .elf=可执行文件,后面所说道的目标文件就是.o文件,可执行文件是.elf或.bin文件)
i.对文档进行词法分析、语法分析、语义分析、优化--->产生汇编代码文档。
ii.在这一步中,编译器将为文件里的变量分配必要的内存,并进行各种错误检查。
iii.为什么要分开编写,生成各自对应的那么多.o文件?
方便维护,这也是代码量大的维护关键方法,代码模块化之后,需要修改,那只编译修改过的那个就可以,不用全部模块
从新编译,这也是为什么会有Makefile。
③、链接。把.o文件链接成一个可执行文件
i. 主要是链接函数和全局变量(静态链接和动态链接)
ii. 每个模块的源代码文档经过编译器编译成目标文档,目标文档和库一起链接成可执行文档。
每个目标文档除了自己的数据和二进制代码外,还提供三个表,未解决符号表、导出符号表、地址重定向表。
④、调试。
⑵:Makefile文件的作用
在ubuntu下,以点灯实验为例。按照1中所述的编译步骤:编译文件需要先将所有的.c/.S文件编译成.o文件(用gcc命令),然后对.o文件进行链接生成执
行文件(ld命令),然后转化 成.bin文件(用objcopy命令),然后会反汇编方便分析(用objdump命令),每一步都对应输入一次命令,然后得到相应
的文件,最终生成.bin文件。但是如果有一个文件进行了更改,比如led.c中更改了寄存器的配置模式,则需要把上面所有的命令再重新来一次,生新的.bin
文件,非常麻烦。这就需要用到Makefile文件。
在 Makefile文件中,写了怎么去编译和链接程序,当有文件改动之后,只需要输入指令“make”,就可以进行编译或者重新编译那些需要去重新编译的文件。
So!Makefile文件 ①内容:整个工程的编译、链接规则,工程中的哪些源文件需要编译以及如何编译、需要创建哪些库文件以及如何创建这些库文件、
如何最后产生我们想要的可执行文件。
②作用:用来进行编译,好处是省事(又名提高效率,哈哈),无论是第一次编译,还是修改后再次编译。
编写时候的规则:
1.如果这个工程没有编译过,那么我们的所有.c/.S文件都要编译并被链接。
2.如果这个工程的某几个文件被修改,那么我们只编译被修改的文件,并链接目标程序。
3.如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。
这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现 错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错(-Wall命令),而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。
⑶:Makefile文件的工作过程
使用make命令
1.make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
2.如果找到,它会找文件中的第一个目标文件(tagert)在后面的例子中,他会找到“ledc.bin”这个文件,并把这个文件作为最终的目标文件。
3.如果ledc.bin”文件不存在,或是ledc.bin”所依赖的后面的 .o文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的
命令来生成ledc.bin”这个文件。
4.如果ledc.bin所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。
(这有点像一个堆栈的过程)
根据 .c/.S文件和.h文件,make会生成 .o 文件,然后再用 .o 文件声明make的终极任务,也就是执行文件ledc.bin了。
2:Makefile中的一些规则和操作
⑴:Makefile中命令规则
Target... : prerequisites ...
command...
...
/* command前面加TAB */
/* 目标文件:依赖文件
编译命令 ...
...
*/
/*target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比 target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则,也就是Makefile中最核心的内容。*/
⑵:Makefile中常见操作
①、变量定义 M := F
= 是最基本的赋值
:= 是覆盖之前的值
?= 是如果没有被赋值过就赋予等号后面的值
+= 是添加等号后面的值
PS: 用 ":=" B=1; A:=B; B=2; 这时A=1;
用 "= " B=1; A=B; B=2; 这时A=2,A=B的最终值;
例子:TARGET := LEDC /*TARGET可以理解为字符串变量LEDC*/
TARGET += BD /*TARGET可以理解为字符串变量LEDCBD*/
②、在Makefile中使用变量格式:$(变量),如①中使用$(M) 就是使用 F
函数的使用格式也是如此 $(函数名字,参数...)
③、模式规则
模式规则中,至少在规则的目标定定义中要包涵“%”,否则就是一般规则,目标中的“%”表示对文件名的匹配,“%”表示长度任意的非空字符串,
比如“%.c”就是所有的以.c 结尾的文件,类似与通配符,a.%.c 就表示以 a.开头,以.c 结束的所有文件。
模式规则使用方法:
%.o : %.c
commond...
④、自动化变量
3:正点原子例程分析
/*代码:点灯-LED使用的Makefile代码
*来源:正点原子阿尔法Linux教程!
*时间:2019-11-20
*分析人:吴彦祖,没错就是我,哈哈
*简介:整个Makefile内容上分两个部分,前半部分是获取各种字符串,后半部分是实际的编译操作。代码具有一定的兼容性,分析一下。
*/
/*--------------------------------------------part1:获取变量(字符串)---------------------------------------------------*/
CROS_COMPILE ?= arm-linux-gnueabihf- //变量的定义。“A?=B”的意思:如果变量A没有被赋值过,就赋值等号后面的B的值。
//arm-linuxgnueabihf- 交叉编译命令 用变量代替是为了效(sheng)率(shi)······
TARGET ?= ledc //ledc是要去生成的文件名,用变量来代替是为了方便修改。
CC := $(CROS_COMPILE)gcc //arm-linux-gnueabihf-gcc gcc编译命令,后面用来生成.o文件
LD := $(CROS_COMPILE)ld //arm-linux-gnueabihf-ld 链接命令,将各个.o(目标文件)链接生成可执行文件
OBJCOPY := $(CROS_COMPILE)objcopy //arm-linux-gnueabihf-objcopy 后面会用这个命令把生成的.elf文件转换成.bin文件
OBJDUMP := $(CROS_COMPILE)objdump //arm-linux-gnueabihf-objdump 反汇编命令,将.els反汇编成代码.dis
INCUDIRS := imx6u \ //'\'=换行符。是表写这行和下一行是同一行。
bsp/clk \ //INCUDIRS 这个变量是以空格为间隔的文件列表,应用于下面foreach
bsp/led \ //在这个程序中,是所有包含.h的文件的列表为了后面的编译操作做准备
bsp/delay
SRCDIRS := project \ //同理上面的INCUDIRS
bsp/clk \ //在这个程序中,是所有包含.c \.S的文件列表,为了后面的编译操作做准备
bsp/led \
bsp/delay
/*patsubst函数 ---模式字符串替换函数,在本程序的作用是将所有的文件路径名字,前面加上"I ",Makefile指定头文件路径规定要加"I "
*$(patsubst <pattern>,<replacement>,<text>)
*$()函数调用标准格式
*patsubst:函数
*<pattern>: 要寻找的字符,可以是通配符%,表示任意长度的字符串,本程序中就用的%,在所有的字符串(文件列表)前面都加I+空格
*注意中间用','隔开
*<replacement> :要进行替换的字符,本程序中是"I "
*注意中间用','隔开
*<text>:文件列表,以空格隔开的。
*/
/*所以INCLUDE变量最后被赋予的值是,在变量ICUDIRS中各个路径前面加上"I "以后的字符串
*比如 I led.c I clk.c
*/
INCLUDE := $(patsubst %, -I %, $(INCUDIRS))
/*foreach函数 ---循环式处理文件列表,在本程序中的作用是找到多有的.S和.c文件
*格式:$(foreach var text commond)
*foreach:函数名字,这也是Makefile中使用函数的格式$(函数名 参数··)
*var:局部变量
*text:文件列表,中间以空格隔开,每一次取一个值赋给var
*commond:对var变量进行操作,每次操作结果都会以空格隔开,最后返回空格隔开的列表
*/
/*$(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
*dir是变量,每次从SRCDIRS中取出一个变量,即第一次imx6u,第二次bsp/clk···
*执行的操作是$(wildcard $(dir)/*.S
*/
/*$(wildcard $(dir)/*.S
*Makefile规则中,通配符会被自动展开。但在变量的定义和函数引用时,通配符将失效,比如此时函数引用。
*通配符 '*'和'?' 作用:当查找文件夹时,可以使用它来代替一个或多个真正字符。区别'*'表示匹配的数量不受限制,'?'匹配字符数则受到限制
*最外层$()是调用函数的格式,函数是wildcard;$(dir)是前面foreach从SRCDIRS中取来赋给dir的值,也就是文件路径;*.S通配符,表示所有的.S结尾文件
*/
/*所以SFILES变量最后的赋值 便是 变量INCUDIRS中 所包含的文件路径中的 所有.S结尾的文件,以空格方式隔开表示
*CFILES也是同理,它最后被赋予的值是变量SRCUDIRS中 所包含的文件路径中的 所有.c结尾的文件,以空格方式隔开表示
*/
SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
/*notdir函数 ---去除所有的目录信息,比如SFILES中是dsp/led.c dsp/clc.c,函数作用以后就是led.c clk.c
*$(notdir text)
*text:文件列表,以空格隔开
*/
/*所以SFILENDIR和CFILENDIR的值,就是在目标文件路径下,所有的.S和.c文件的名字,组成的字符串*/
SFILENDIR := $(notdir $(SFILES))
CFILENDIR := $(notdir $(CFILES))
/*patsubst函数同上 $(patsubst <pattern>,<replacement>,<text>)
*在<replacement>中加入了文件路径格式的字符串 obj/ 作用是把生成的文件放入到obj文件下
*$(SFILENDIR:.S=.o) 把.S文件改成.o文件的后缀。同理$(CFILENDIR:.c=.o)
*实际上就是操作字符串,最后获得了以.o为结尾的与上面SFILENDIR、CFILENDIR中中的.S和.c变量相对应的.o文件的字符串。
*/
SOBJS := $(patsubst %, obj/%, $(SFILENDIR:.S=.o))
COBJS := $(patsubst %, obj/%, $(CFILENDIR:.c=.o))
/*OBJS是一个字符串,其内容是所有的.o文件。这里注意顺序,因为SOBJS中含有start.o,所以SOBJS放在首位*/
OBJS := $(SOBJS)$(COBJS)
/*VPATH是特殊变量 ---作用是添加,给make的去搜寻的文件目录
*程序把源文件分类,放在不同的目录中-->当make的时候,需要去寻找文件的依赖关系.
*make的寻找只会在当前目录下,在其他目录文件下的源文件,需要通过文件路径去寻找,像是在 SFILES和CFILES变量中,文件前面加上了路径
*VPATH的作用是,告诉make路径,让make自动去找。
*即,**如果make在当前文件下找不到,就会去VPATH中指定的目录文件去寻找**,这里的VPATH文件就是,前面定义的,所有的包含.c \ .S的文件的路径。
*/
VPATH := $(SRCDIRS)
/*-------------------------------------------------part2:进行编译------------------------------------------------------------*/
/*.PHONY 伪目标
*当文件中出现和clean同名的文件的时候,使用 make clean命令会无法执行,这是需要设置成伪目标。
*/
.PHONY:clean
/*开始进行Makefile中的编译操作*/
/*在此例程中ledc.bin : obj/start.o obj/main.o obj/clk.o obj/led.o obj/delay.o*/
$(TARGET).bin : $(OBJS)
/*链接。-Timx6u.lds,imx6u.lds是链接脚本文件;$^是所有依赖文件的集合,这里相当于$(OBJS)变量*/
$(LD) -Timx6u.lds -o $(TARGET).elf $^
/*objcopy生成二进制bin文件。-O,是选择以什么格式输出,-O binary选择以二进制格式输出;$@是规则中目标集合,这里就是ledc.bin */
$(OBJCOPY) -O binary -S $(TARGET).elf $@
/*objdump生成反汇编文件。-D:反汇编所有段;由led.elf > led.dis */
$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
/*↑这里,所有编译命令行都是以TAB键开始。*/
/*通过S生成.o文件*/
/*Makefile静态模式 ---可以更加容易的定义多目标的规则
*<targets...>: <target-pattern>: <prereq-patterns...>
*targets:定义了一系列的目标文件,可以有通配符,是目标的一个集合。 这里SOBJS变量的值是字符串obj/start.o,即目标文件start.o
*target-pattern:目标的格式。 这里目标的格式是.o,然后加上了路径obj/ obj/ .o
*prereq-patterns: 目标依赖的模式。 这里依赖模式就是所有的.S文件
*/
$(SOBJS) : obj/%.o : %.S
/*gcc编译:把.S编译成.o,-Wall:打印编译时错误、警告信息;$@:规则中目标集合,这里是strat.o;$<是依赖文件中的第一个文件,这里是start.S。
*但是在%号的作用下,$<表示一些列文件的集合
*/
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
/*同理.c编译成.o文件*/
$(COBJS) : obj/%.o : %.c
/*-nostdlib:不连接系统标准启动文件和标准库文件,只把指定的文件传递给连接器。在此处不需要启动文件、标准库文件。
*-O2 表示选择优化等级,有O0,O1,O2,O3几个等级。https://blog.csdn.net/qq_31108501/article/details/51842166 各级优化所做的操作
*/
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
/*清理,伪目标*/
clean:
rm -rf $(OBJS) $(TARGET).bin $(TARGET).elf $(TARGET).dis
/*打印*/
print:
/*@echo,加上@不会打印过程*/
@echo INCLUDE = $(INCLUDE)
@echo SFILES = $(SFILES)
@echo CFILES = $(CFILES)
@echo CFILENDIR = $(CFILENDIR)
@echo SFILENDIR = $(SFILENDIR)
@echo OBJS = $(OBJS)
@echo SOBJS = $(SOBJS)
@echo COBJS = $(COBJS)