Makefile相关知识(基于韦东山课程第一、三期)

1、传统的gcc -o 指令的缺点 9.5

假设现在有两个c文件,a.c和b.c,我在a.c的main函数中调用b.c中的函数,那么我生成可执行程序的编译指令是:

gcc -o test a.c b.c

这条指令会分别对a.c和b.c执行预处理、编译和汇编,最终生成a.o以及b.o,最后在链接成为test。这样做的问题在于,如果我修改了a.c,需要重新执行gcc指令,这条指令会再将a.c和b.c进行上述处理,可我b.c根本没有修改,不需要再处理,这就造成了重复。
我们一般将预处理、编译和汇编三者合起来称为编译,也就是说程序的形成过程分为两个步骤,编译和链接。解决重复的方法是将编译和链接分开处理,比如:

gcc -c -o a.o a.c
gcc -c -o b.o b.c
gcc -o test a.o b.o

如果我修改了a.c,我只需要重新将a.c编译成a.o,再将a.o和,没变的b.o链接即可。
那么问题来了,如果文件很多,怎么才能知道那个文件被修改了呢?处理方法是看文件时间,如果a.o的时间比a.c更早,那么说明a.c被修改了,需要重新编译。所谓的makefile就是在做这样的工作

2、makefile规则介绍 9.5

Makefile里以一个“规则”作为基本单位,规则的写法如下:

目标文件名:依赖文件名
tab键 命令

当依赖文件比目标文件“新”时或者依赖文件不存在时,执行下一行对应的命令。将上面的程序生成过程写成Makefile如下,一共三个规则:

test:a.o b.o
	gcc -o test a.o b.o
a.o:a.c
	gcc -c -o a.o a.c
b.o:b.c
	gcc -c -o b.o b.c

将上述语句写入一个文件中,放在与a.c和b.c相同目录下,并且文件命名为Makefile,不带后缀。

当我们第一次执行make命令的时候,机器认识到需要生成的是test文件,其依赖于a.o b.o文件,发现这两个文件没有,则先去生成a.o,往下看发现a.o依赖于a.c,且现在我有a.c但是没有a.o,则认为a.c比a.o更加“新”,则执行下面的命令:gcc -c -o a.o a.c 生成a.o,b.o的生成同理,最后a.o和b.o都有了,而test没有,则认为a.o和b.o都比test“新”,所以执行下面的命令:gcc -o test a.o b.o,最终生成test。

假设我修改了a.c,再次执行make。机器认识到需要生成的是test文件,其依赖于a.o b.o文件,首先去生成a.o,发现a.c比已经有的a.o更新,则用gcc -c -o a.o a.c重新生成a.o;再去生成b.o,发现已经有b.o而且b.c没有比b.o更新,故不执行命令。最后再进行链接。

3、makefile语法介绍 9.6

3.1、通配符以及一些常用符号

背景:按照上面第2点的写法,如果有一万个c文件,则需要写一万个类似下面的语句,太麻烦了。

a.o:a.c
	gcc -c -o a.o a.c

解决:使用通配符
将上面的Makefile改写

test:a.o b.o
	gcc -o test a.o b.o
a.o:a.c
	gcc -c -o a.o a.c
b.o:b.c
	gcc -c -o b.o b.c

改写后:

test:a.o b.o
	gcc -o test $^
%.o:%.c
	gcc -c -o $@ $<

注释:
%.o:%.c:%是通配符,机器在找寻生成a.o和b.o的规则时,通过%.o 找到对应规则。
$@:表示目标文件
$<:表示第一个依赖文件
$^:表示所有的依赖文件

3.2、假想目标

背景:假设我现在想在Makefile里面加一个功能,清除所有的.o文件以及test文件,写法如下:

test:a.o b.o
	gcc -o test $^
%.o:%.c
	gcc -c -o $@ $<
clean:
	rm *.o test

键入make clean即可完成清除操作,也就是说,make后面你可以带上目标名,则执行目标名对应的规则,如果不带目标名,即单纯的make就是默认从第一个目标开始,如果没目标,就从第一条语句开始。

问题:如果在当前目录下已经存在一个名为“clean”的文件,由于Makefile中clean对应规则后面没有依赖,也就是说永远都是依赖比目标clean更“旧”,这种情况下再执行make clean,会没有反应。

解决:将clean定义为假想目标

test:a.o b.o
	gcc -o test $^
%.o:%.c
	gcc -c -o $@ $<
clean:
	rm *.o test
.PHONY: clean

3.3、变量

Makefile里面有两种变量,即时变量和延时变量。
即时变量:A:=xxx #A的值即刻确定,在定义时即确定
延时变量:B=xxx #B的值在使用到时才确定
注意:立即变量不能进行

obj-y += main.o
obj-y += display/
obj-y += encoding/

这样的连续追加的操作,因为立即变量必须定义时就确定,这种追加必须用延时变量。
变量的使用:$(变量名,如A或者B)

例子:

A:=$(C)
B=$(C)
all:
	@echo A=$(A)
	@echo B=$(B)
C=abc

运行make后
输出A等于空,B等于abc,因为在A定义的时候,C还没定义,而B是延时变量,注意C在B的后面仍然能生效的原因是因为make操作的时候是先将整个Makefile进行分析。

总结一下常用的变量定义:
:= #即时变量
= #延时变量
?= #延时变量,如果是第一次定义才有效,如果在前面该变量已定义则忽略这句话
+= #附加,跟C语言类似,它是即时还是延时,取决于前面的定义

4、makefile自带的一些函数 9.7

4.1、$(foreach var,list,text)

作用:对于list里面的每一个变量,执行text中的操作
例子,将A中的变量全部加上.o后缀

A= a b c
B= $(foreach f,$(A), $(f).o)

all:
        @echo B= $(B)
root@zwg-virtual-machine:/home/zwg/weidongshan# make
B= a.o b.o c.o

4.2、$(filter pattern…,text)和(filter-out pattern…,text)

作用:在text中取出符合pattern格式的值或在text中取出不符合pattern格式的值
例子,从变量C里筛选出含有/和不含/的。

C=a b c d/
D=$(filter %/,$(C))
E=$(filter-out %/,$(C))

all:
        @echo D= $(D)
        @echo E= $(E)
root@zwg-virtual-machine:/home/zwg/weidongshan# make
D= d/
E= a b c

4.3、$(wildcard pattern)

作用:在pattern中定义了文件名的格式,wildcard函数取出当前目录下与格式相符合的文件(如果有的话)
例子,假设当前目录下有a.c b.c c.c 三个文件

files=$(wildcard *.c)#将当前目录下的.c文件的名字赋值给files
  
files2=a.c b.c c.c d.c e.c
flies3=$(wildcard $(files2))#检查变量files2中哪些文件是当前目录下真实存在的
all:
        @echo files = $(files)
        @echo files3= $(files3)

4.4、$(patsubst pattern,replacement,text)

作用:将text中的符合pattern格式的值全部替换为replacement格式
例子,将files2里面的.c全部变成.d

files2 = a.c b.c c.c e.c d.c abc
dep_files=$(patsubst %.c,%.d,$(files2))
all:
        @echo dep_files = $(dep_files)
root@zwg-virtual-machine:/home/zwg/weidongshan# make
dep_files = a.d b.d c.d e.d d.d abc

5、关于Makefile中的头文件依赖 9.8

当前目录下的一些文件:

root@zwg-virtual-machine:/home/zwg/weidongshan# ls
a.c  b.c  c.c  c.h  Makefile

目前最新的Makefile如下:

test:a.o b.o c.o
	gcc -o test $^
%.o:%.c
	gcc -c -o $@ $<
clean:
	rm *.o test
.PHONY: clean

背景:假设我新加一个文件c.h,里面做一些操作,比如定义一个宏,再在c.c里使用这个宏。那么我的c.o就同时依赖于c.c和c.h两个文件,我需要将c.h加入到Makefile的依赖项中,做法如下:

test:a.o b.o c.o
	gcc -o test $^
c.o:c.c c.h         #这条规则没有命令,只是告诉机器c.o的依赖项
%.o:%.c
	gcc -c -o $@ $<
clean:
	rm *.o test
.PHONY: clean

这样做很麻烦,因为我这里是已知c.c里面要用到c.h,可实际上如何确定某个.c文件里包含了哪些.h文件是很麻烦的。

解决:参考博客https://blog.csdn.net/qq1452008/article/details/50855810
指令1:使用gcc -M c.c 查看c.c的所有依赖项目(这里应该是系统自动能够查到某个c文件所对应的依赖文件,不需要人为处理,这也是为什么我们写c.c的头文件必须用同名的c.h的原因,方便系统查找):

root@zwg-virtual-machine:/home/zwg/weidongshan# gcc -M c.c
c.o: c.c /usr/include/stdc-predef.h /usr/include/stdio.h \
 /usr/include/x86_64-linux-gnu/bits/libc-header-start.h \
 /usr/include/features.h /usr/include/x86_64-linux-gnu/sys/cdefs.h \
 /usr/include/x86_64-linux-gnu/bits/wordsize.h \
 /usr/include/x86_64-linux-gnu/bits/long-double.h \
 /usr/include/x86_64-linux-gnu/gnu/stubs.h \
 /usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
 /usr/lib/gcc/x86_64-linux-gnu/7/include/stddef.h \
 /usr/include/x86_64-linux-gnu/bits/types.h \
 /usr/include/x86_64-linux-gnu/bits/typesizes.h \
 /usr/include/x86_64-linux-gnu/bits/types/__FILE.h \
 /usr/include/x86_64-linux-gnu/bits/types/FILE.h \
 /usr/include/x86_64-linux-gnu/bits/libio.h \
 /usr/include/x86_64-linux-gnu/bits/_G_config.h \
 /usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h \
 /usr/lib/gcc/x86_64-linux-gnu/7/include/stdarg.h \
 /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
 /usr/include/x86_64-linux-gnu/bits/sys_errlist.h c.h

指令2:使用 gcc -M -MF c.d c.c,将c.c文件所有的依赖项存放到文件c.d中。
注意: 生成的从c.d或者是下面生成的c.o.d(本质上都是产生c.o所需要的依赖文件,只是命名不同),这些依赖文件不仅仅包含了.h文件,也有.c文件,注意看依赖文件的格式,也就是说makefile中只需要包含依赖文件就可以了,不再需要写

%.o:%.c 
	gcc -c -o $@ $<
root@zwg-virtual-machine:/home/zwg/weidongshan# gcc -M -MF c.d c.c
root@zwg-virtual-machine:/home/zwg/weidongshan# ls
001  a.c  b.c  c.c  c.d  c.h  Makefile
root@zwg-virtual-machine:/home/zwg/weidongshan# cat c.d
c.o: c.c /usr/include/stdc-predef.h /usr/include/stdio.h \
 /usr/include/x86_64-linux-gnu/bits/libc-header-start.h \
 /usr/include/features.h /usr/include/x86_64-linux-gnu/sys/cdefs.h \
 /usr/include/x86_64-linux-gnu/bits/wordsize.h \
 /usr/include/x86_64-linux-gnu/bits/long-double.h \
 /usr/include/x86_64-linux-gnu/gnu/stubs.h \
 /usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
 /usr/lib/gcc/x86_64-linux-gnu/7/include/stddef.h \
 /usr/include/x86_64-linux-gnu/bits/types.h \
 /usr/include/x86_64-linux-gnu/bits/typesizes.h \
 /usr/include/x86_64-linux-gnu/bits/types/__FILE.h \
 /usr/include/x86_64-linux-gnu/bits/types/FILE.h \
 /usr/include/x86_64-linux-gnu/bits/libio.h \
 /usr/include/x86_64-linux-gnu/bits/_G_config.h \
 /usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h \
 /usr/lib/gcc/x86_64-linux-gnu/7/include/stdarg.h \
 /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
 /usr/include/x86_64-linux-gnu/bits/sys_errlist.h c.h

指令3:使用指令 gcc -c -o c.o c.c -MD -MF c.d,在编译的同时,生成c.d

root@zwg-virtual-machine:/home/zwg/weidongshan# gcc -c -o c.o c.c -MD -MF c.d
root@zwg-virtual-machine:/home/zwg/weidongshan# ls
001  a.c  b.c  c.c  c.d  c.h  c.o  Makefile

修改Makefile:

步骤1: 在第一次make时生成依赖文件

test:a.o b.o c.o
	gcc -o test $^
	
# c.o:c.c c.h  
# %.o:%.c
	# gcc -c -o $@ $<
%.o : %.c
        gcc -c -o $@ $< -MD -MF .$@.d	
clean:
	rm *.o test
.PHONY: clean

修改后make一下,就会出现三个新的文件: .a.o.d .b.o.d .c.o.d,这三个文件里存放的是对应.c文件中的依赖项目
这样就不用在写类似于 c.o:c.c c.h 的语句。注意上面步骤1写的makefile只是生成了依赖文件,并没有将这些依赖文件加入到makefile的文本中,还需要步骤2.

步骤2: 条件编译,在第一次make时将产生的依赖文件加入makefile文本中
最终的成品如下,加入了是否包含依赖文件的一个条件判断,具体看9.8视频

objs=a.o b.o c.o
  
dep_files:=$(patsubst %,.%.d,$(objs))
dep_files:=$(wildcard $(dep_files))

test:$(objs)
        gcc -o test $^

ifneq ($(dep_files),)
include $(dep_files)
endif

%.o : %.c
        gcc -c -o $@ $< -MD -MF .$@.d

clean:
        rm *.o test

distclean:
        rm $(dep_files)

.PHONY:clean

总结在第一次make时,makefile主要执行 gcc -c -o $@ < − M D − M F . < -MD -MF . <MDMF.@.d ,makefile第一次执行想生成比如说a.o时,虽然我们只指明了a.o依赖a.c,没有说a.o依赖a.h,但是还是可以正确编译,系统会自动找到a.h,也就是说第一次make实际上不需要对依赖文件进行处理。但是这个a.h不能改变,自动寻找a.c对应依赖a.h猜测是系统的功能,而自动判断依赖文件是否比目标文件更“新”猜测是makefile本身的功能,这个功能的实现需要将具体依赖文件的文本名字写到makefile文本中,所以如果第一次make以后,改变了a.h,第二次make将识别不到a.h的改变。按照上面的makefile,第二次make时,就会将第一次make生成的依赖文件包含进makefile文本中,这样就没问题了。

6、Makefile中的CFLAGS 9.8

6.1 编译器系统目录

我们的程序一般包含两种头文件,一种是自己写的(这种一般和对应的.c文件在同一个目录,或者在一个统一的自己创建的include目录下),另一种是标准库的头文件,比如#include <stdio.h>,那么在make时,编译器如何知道该在那里找库的头文件呢?
如果时Linux上的gcc编译器,默认是在/usr/include
如果是用交叉编译工具arm-linux-gcc,则是在其存放的目录下面去找,那么怎样找到这个目录呢?命令行键入命令:

arm-linux-gcc ^C
echo $PATH

就会打印出文件路径,再退回打印的路径的上一个目录,就是了。
上面讲的是库头文件,库文件也是一样的。

可以人为改变编译器的系统目录(就是存放库头文件和库文件的目录)。方法是设置CFLAGS 的 -I 和 -F选项。

6.2 设置CFLAGS,进行编译优化,改变编译器系统目录

objs=a.o b.o c.o
  
dep_files:=$(patsubst %,.%.d,$(objs))
dep_files:=$(wildcard $(dep_files))

CFLAGS= -Werror -I  
#说明CFLAGS的内容,其中-Werror是在编译时将所有的warn都当做error来处理,推荐这样做,可以避免很多错误
#其中-I是指定编译器搜索的默认头文件目录,比如-I.表示默认头文件目录在当前目录
#一般我们习惯将所有的.h头文件放到一个include文件夹里,就可以用 -Iinclude 来指定这个文件夹

test:$(objs)
        gcc -o test $^

ifneq ($(dep_files),)
include $(dep_files)
endif

%.o : %.c
        gcc $(CFLAGS) -c -o $@ $< -MD -MF .$@.d #将CFLAGS加入到编译过程中

clean:
        rm *.o test

distclean:
        rm $(dep_files)

.PHONY:clean

7、修改Makefile以支持一个大型工程

之前讲解的都是对一堆.c文件的处理,而且这些.c文件都在都一个文件目录下,在一个有层次地工程项目中,各种源文件一般都是分布在不同层次的目录中,比如uboot和Linux,现在修改makefile以支持这种工程,主要参考韦东山第三期视频:4_数码相框_编写通用的Makefile:46分钟往后的内容,之前的不用看,思想主要是仿照着Linux的makefile写。

7.1 工程结构及总体认知

这里虚拟一个工程项目,假设所有文件都在目录total下,total目录下有一个顶层Makefile文件,一个Makefile.build文件,一个main.c文件,dir_a,dir_b,dir_c三个子目录用于存放.c源文件,还有一个include子目录,里面存放所有.c文件对应的.h文件。三个子目录里分布有两个个.c文件和一个Makefile文件,.三个子目录下的c文件的名字为dir_x_y.c,其中x可以是a、b、c,y可以是1、2,具体如下:

顶层目录total
  Makefile
  Makefile.build
  mian.c
  include
    各种自己写的.h文件
  dir_a
    dir_a_1.c
    dir_a_2.c
    Makefile
    dir_a_a
      dir_a_a_1.c
      Makefile
  dir_b
    dir_b_1.c
    dir_b_2.c
    Makefile
  dir_c
    dir_c_1.c
    dir_c_2.c
    Makefile

现在说一下整体make过程的总体流程,我们在顶层total目录下键入make命令,系统自动执行total/Makefile文件,total/Makefile文件中会指明当前total目录下的目标程序有main.o,以及需要进入dir_abc三个子目录去执行子目录的Makefile。

#顶层total目录下Makefile部分内容
obj-y += main.o
obj-y += dir_a/
obj-y += dir_b/
obj-y += dir_c/

系统不会先处理total下的目标文件,而是先执行子目录的Makefile(因为顶层的main依赖子目录),比如会先去dir_a目录下执行dir_a的子Makefile,这里dir_a目录下还有子子目录dir_a_a,则继续先执行子子目录下的Makefile。每个层级下的每个Makefile执行完后都会生成一个对应的built-in.o文件,用以表示该目录下生成的目标文件,该built-in.o文件又会作为一部分去组成上一级目录的built-in.o,最终total目录下的Makefile将dir_a、dir_b、dir_c三个子目录下的built-in.o(其中dir_a的built-in.o包含了dir_a_a的built-in.o)和main.o一起再次组合成total目录下的built-in.o,最后这个total下的built-in.o再和其他需要链接的文件一起组成最终可执行目标文件,命名为final。

每个目录下built-in.o文件的建立依赖于各个目录中的Makefile去调用顶层的total/Makefile.build文件。

7.2 顶层Makefile

下面贴出Makefile的原文,逐行分析。

CROSS_COMPILE = arm-linux- 				#延时变量CROSS_COMPILE,用于指定交叉编译器
AS		= $(CROSS_COMPILE)as			#为一些常见的编译命令起别名,以下几行都是
LD		= $(CROSS_COMPILE)ld
CC		= $(CROSS_COMPILE)gcc
CPP		= $(CC) -E
AR		= $(CROSS_COMPILE)ar
NM		= $(CROSS_COMPILE)nm

STRIP		= $(CROSS_COMPILE)strip
OBJCOPY		= $(CROSS_COMPILE)objcopy
OBJDUMP		= $(CROSS_COMPILE)objdump

export AS LD CC CPP AR NM				#这些别名只是在当前顶层Makefile中定义了,需要export导出,使得子目录下的子Makefile也能用
export STRIP OBJCOPY OBJDUMP

CFLAGS := -Wall -Werror -O2 -g			#打印出所有警告信息,这一条不重要,可有可无
CFLAGS += -I $(shell pwd)/include		# $(shell pwd)表示执行pwd这个shell命令,该命令得到当前目录,比如当前目录为/total,-I表示改变
#编译器寻找头文件的默认目录,这句话总体表示将寻找头文件的默认目录改为/total/include,我们在开发的时候将写的.h文件都放到这个目录。疑问:
#那像stdio.h这种库头文件在哪里去找呢?

LDFLAGS := -lm -lfreetype -lts -lpthread -ljpeg		#链接参数,指明要链接的库,这里lm表示lib math,数学库,从左到右分别是:数学库、#freetype库(用于显示矢量字体)、触摸屏库(支持触摸屏操作)、pthread线程库、jpeg图片库(解析jpeg格式图片)

export CFLAGS LDFLAGS

TOPDIR := $(shell pwd)					#指出顶层目录就是当前目录
export TOPDIR

TARGET := final							#最终目标可执行文件的名字,随便取

#obj-y是最终想得到的所有目标文件的集合,main.o放在最前面
obj-y += main.o
#下面三条语句表示进入到dir三个子目录,分别执行其中的子Makefile,如果子目录下面还有子子目录,就在子目录中同样操作,去调用子子目录的#Makefile,而且需要注意的是,如果子目录发现确实有子子目录,是优先去执行子子目录的Makefile,执行完后再返回执行子目录的Makefile
obj-y += dir_a/
obj-y += dir_b/
obj-y += dir_c/

#all是和clean等一样的伪目标,make会将第一个出现的目标作为默认目标,就是只执行make不加目标名的时候,第一个目标名通常是all。
#也就是说执行make就等于在执行make all。
all : 
	#make -C表示递归调用子目录中的Makefile,-C选项后跟目录,表示到该目录下执行该目录的Makefile,然后从该目录递归往下调用
	#make -C ./ 表示从当前目录开始,也就是从total目录开始
	#-f指定子目录中的文件(文件名可以随意,不一定用Makefile作为文件名)作为Makefile,比如total/Makefile.build
	#也就是说,实际上每个目录,包括顶层目录,最终作为真正意义上的Makefile去执行的其实是total/Makefile.build,每个目录都会调用它
	#作为该目录的Makefile,total/Makefile.build中会先将obj-y等变量赋空值,然后会包含当前目录下的Makefile,这样就能识别当前目录
	#的obj-y是多少,以及需要进入哪些子目录。一旦执行make,会首先执行顶层的Makefile,也即是现在所处的这个文件,然后会首先想生成
	#all这个目标,进而去执行make -C ./ -f $(TOPDIR)/Makefile.build这条语句,转而去在那个目录下递归执行total/Makefile.build,
	#total/Makefile.build中会包含各个目录下原本的Makefile文档中内容。可以把所有built—in.o的生成比作递归函数的调用
	#total/Makefile.build就是具体执行的递归函数,函数返回值是built—in.o
	make -C ./ -f $(TOPDIR)/Makefile.build
	#在顶层目录中将最终生成的built-in.o文件和其他库文件一起链接成可执行文件final		
	$(CC) $(LDFLAGS) -o $(TARGET) built-in.o


clean:
	rm -f $(shell find -name "*.o")
	rm -f $(TARGET)

distclean:
	rm -f $(shell find -name "*.o")
	rm -f $(shell find -name "*.d")
	rm -f $(TARGET)
	

7.3 Makefile.build文件讲解

PHONY := __build
#(1)首先想生成第一个目标__build,下面有__build的依赖
__build:

#先定义obj-y和subdir-y 两个变量,赋值为空,obj-y存当前目录下的Makefile文件中定义的obj-y
#subdir-y存放当前目录下的Makefile文件中定义的需要进一步调用的下层目录
obj-y :=
subdir-y :=

#包含当目录下的Makefile文本,假设当前文本包含obj-y := a.o b.o c/ d/
include Makefile

#这句话将当前目录下Makefile定义的obj-y(假设为obj-y := a.o b.o c/ d/)中带斜杠的选出来去掉斜杠赋给subdir-y
#下面两句执行完后,最终subdir-y的值是 c和d
__subdir-y	:= $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y	+= $(__subdir-y)

# 将刚才得到的c和d,变成c/built-in.o d/built-in.o
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)

# 将obj-y := a.o b.o c/ d/带斜杆的提出,此时cur_objs就等于a.o和b.o
cur_objs := $(filter-out %/, $(obj-y))

# 依赖文件的处理,之前讲过
dep_files := $(foreach f,$(cur_objs),.$(f).d)
dep_files := $(wildcard $(dep_files))
ifneq ($(dep_files),)
  include $(dep_files)
endif


PHONY += $(subdir-y)

# (2)当前目录下的__build 依赖于子目录的调用结果以及本目录生成的built-in.o
__build : $(subdir-y) built-in.o

# (3)如果有子目录,递归往下调用
$(subdir-y):
	make -C $@ -f $(TOPDIR)/Makefile.build
#本目录生成的built-in.o依赖于当前目录的.o文件,和子目录生成的built-in.o文件
built-in.o : $(cur_objs) $(subdir_objs)
	$(LD) -r -o $@ $^

dep_file = .$@.d

%.o : %.c
	$(CC) $(CFLAGS) -Wp,-MD,$(dep_file) -c -o $@ $<
	
.PHONY : $(PHONY)

7.4 下层Makefile

下面各层的Makefile都很简单,就是包含自身的.c文件对应的.o文件,如果有下下层的话,就包含下下层的Makefile

dir_a目录下的Makefile

obj-y += dir_a_1.o
obj-y += dir_a_2.o
obj-y += dir_a_a/

dir_b目录下的Makefile

obj-y += dir_b_1.o
obj-y += dir_b_2.o

dir_c目录下的Makefile

obj-y += dir_c_1.o
obj-y += dir_c_2.o

dir_a_a目录下的Makefile

obj-y += dir_a_a_1.o
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
韦东山老师为啥要录升级版嵌入式视频?200x年左右,嵌入式Linux在全世界、在中国刚刚兴起。我记得我2005年进入中兴时,全部门的人正在努力学习Linux。在2008年,我写了一本书《嵌入式Linux应用开发完全手册》。它的大概内容是:裸机、U-boot、Linux内核、Linux设备驱动。那时还没有这样讲解整个系统的书,芯片厂家Linux开发包也还不完善,从bootloader到内核,再到设备驱动都不完善。有全系统开发能力的人也很少。于是这书也就恰逢其时,变成了畅销书。我也根据这个思路录制了视频:裸机、U-boot、Linux内核、Linux设备驱动。收获些许名声,带领很多人进入Linux世界。11年过去了,嵌入式Linux世界发生了翻天覆地的变化① 基本系统能用芯片厂家都会提供完整的U-boot、Linux内核、芯片上硬件资源的驱动。方案厂家会做一些定制,比如加上某个WIFI模块,会添加这个WIFI模块的驱动。你可以使用厂家的原始方案,或是使用/借鉴方案商的方案,做出一个“能用”的产品。② 基础驱动弱化;高级驱动专业化基础的驱动,比如GPIO、UART、SPI、I2C、LCD、MMC等,有了太多的书籍、视频、示例代码,修修改改总是可以用的。很多所谓的驱动工程师,实际上就是“调参工程师”。我们群里有名的火哥,提出了一个概念:这些驱动就起一个“hardware enable”的作用。高级的驱动,比如USB、PCIE、HDMI、MIPI、GPU、WIFI、蓝牙、摄像头、声卡。体系非常复杂,很少有人能讲清楚,很多时候只是一笔带过。配置一下应用层工具就了事,能用就成。这些高级驱动,工作中需要专门的人来负责,非常专业。他们是某一块的专家,比如摄像头专家、音频专家。③ 项目为王你到一个公司,目的是把产品做出来,会涉及APP到内核到驱动全流程。中小公司玩不起华为中兴的配置,需要的是全面手。大公司里,只负责很小很小一块的镙丝钉,位置也不太稳固啊。所以,如果你不是立志成为某方面的专家,那就做一个全栈工程师吧。④ 调试很重要都说代码是3分写7分调,各种调试调优技术,可以为你的升职加薪加一把火。基于上述4点,我录制的全新视频将有这些特点:1. 快速入门,2. 实战项目,3. 驱动大全,4. 专题,5. 授人以渔,6. 要做任务另外,我们会使用多款芯片同时录制,先讲通用的原理,再单独讲各个板子的操作。这些芯片涵盖主流芯片公司的主流芯片,让你学习工作无缝对接。1.快速入门入门讲究的是快速,入门之后再慢慢深入,特别是对于急着找工作的学生,对于业余时间挑灯夜读的工作了的人,一定要快!再从裸机、U-boot、内核、驱动这样的路线学习就不适合了,时间就拉得太长了。搞不好学了后面忘了前面。并且实际工作中并不需要你去弄懂U-boot,会用就行:U-boot比驱动还复杂。讲哪些内容?怎么讲呢?混着讲比如先讲LED APP,知道APP怎么调用驱动,再讲LED硬件原理和裸机,最后讲驱动的编写。这样可以快速掌握嵌入式Linux的整套开发流程,不必像以前那样光学习裸机就花上1、2个月。而里面的裸机课程,也会让你在掌握硬件操作的同时,把单片机也学会了。讲基础技能中断、休眠-唤醒、异步通知、阻塞、内存映射等等机制,会配合驱动和APP来讲解。这些技能是嵌入式Linux开发的基础。而这些驱动,只会涉及LED、按制、LCD等几个驱动。掌握了这些输入、输出的驱动和对应的APP后,你已经具备基本的开发能力了。讲配置我们从厂家、从方案公司基本上都可以拿到一套完整的开发环境,怎么去配置它?需要懂shell和python等配置脚本。效果效率优先以前我都是现场写代码、现场写文档,字写得慢,降低了学习效率。这次,效果与效率统一考虑,不再追求所有东西都现场写。容易的地方可先写好代码文档,难的地方现场写。2.实战项目会讲解这样的涉及linux网关/服务器相关项目(不限于,请多提建议):                   定位为:快速掌握项目开发经验,丰满简历。涉及的每一部分都会讲,比如如果涉及蓝牙,在这里只会讲怎么使用,让你能写出程序;如果要深入,可以看后面的蓝牙专题。3. 驱动大全包括基础驱动、高级驱动。这些驱动都是独立成章,深入讲解。虽然基础驱动弱化了,但是作为Linux系统开发人员,这是必备技能,并且从驱动去理解内核是一个好方法。在讲解这些驱动时,会把驱动的运行环境,比如内核调度,进程线程等概念也讲出来,这样就可以搭建一个知识体系。没有这些知识体系的话,对驱动的理解就太肤浅了,等于在Linux框架下写裸机,一叶障目,不见泰山。定位为:工具、字典,用到再学习。4. 专题想深入学习的任何内容,都可独立为专题。比如U-boot专题、内核内存管理专题、systemtap调试专题。
本程序的Makefile分为3类: 1. 顶层目录的Makefile 2. 顶层目录的Makefile.build 3. 各级子目录的Makefile 一、各级子目录的Makefile: 它最简单,形式如下: EXTRA_CFLAGS := CFLAGS_file.o := obj-y += file.o obj-y += subdir/ "obj-y += file.o" 表示把当前目录下的file.c编进程序里, "obj-y += subdir/" 表示要进入subdir这个子目录下去寻找文件来编进程序里,是哪些文件由subdir目录下的Makefile决定。 "EXTRA_CFLAGS", 它给当前目录下的所有文件(不含其下的子目录)设置额外的编译选项, 可以不设置 "CFLAGS_xxx.o", 它给当前目录下的xxx.c设置它自己的编译选项, 可以不设置 注意: 1. "subdir/"中的斜杠"/"不可省略 2. 顶层Makefile中的CFLAGS在编译任意一个.c文件时都会使用 3. CFLAGS EXTRA_CFLAGS CFLAGS_xxx.o 三者组成xxx.c的编译选项 二、顶层目录的Makefile: 它除了定义obj-y来指定根目录下要编进程序去的文件、子目录外, 主要是定义工具链前缀CROSS_COMPILE, 定义编译参数CFLAGS, 定义链接参数LDFLAGS, 这些参数就是文件中用export导出的各变量。 三、顶层目录的Makefile.build: 这是最复杂的部分,它的功能就是把某个目录及它的所有子目录中、需要编进程序去的文件都编译出来,打包为built-in.o 详细的讲解请看视频。 四、怎么使用这套Makefile: 1.把顶层Makefile, Makefile.build放入程序的顶层目录 在各自子目录创建一个空白的Makefile 2.确定编译哪些源文件 修改顶层目录和各自子目录Makefile的obj-y : obj-y += xxx.o obj-y += yyy/ 这表示要编译当前目录下的xxx.c, 要编译当前目录下的yyy子目录 3. 确定编译选项、链接选项 修改顶层目录Makefile的CFLAGS,这是编译所有.c文件时都要用的编译选项; 修改顶层目录Makefile的LDFLAGS,这是链接最后的应用程序时的链接选项; 修改各自子目录下的Makefile: "EXTRA_CFLAGS", 它给当前目录下的所有文件(不含其下的子目录)设置额外的编译选项, 可以不设置 "CFLAGS_xxx.o", 它给当前目录下的xxx.c设置它自己的编译选项, 可以不设置 4. 使用哪个编译器? 修改顶层目录Makefile的CROSS_COMPILE, 用来指定工具链的前缀(比如arm-linux-) 5. 确定应用程序的名字: 修改顶层目录Makefile的TARGET, 这是用来指定编译出来的程序的名字 6. 执行"make"来编译,执行"make clean"来清除,执行"make distclean"来彻底清除

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值