Makefile学习小结

这段时间在学习正点原子的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)

print打印的结果:

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值