-62- 嵌入式Linux系统开发与应用实验教程
make -s
4.编写一个Makefile
(1)Makefile的结构
Makefile文件一般包含如下内容:
● 需要由 make 工具创建的项目,通常是目标文件和可执行文件; ● 要创建的项目依赖于哪些文件; ● 创建每个项目时需要运行的命令; ● 变量的定义; ● 注释。
(2)编写Makefile文件的规则 Makefile中规则的格式如下: targers:dependencies ┇ 或者
targers:dependencies;command ┇
targers指定目标名,通常是一个程序产生的目标文件名,也可能是执行一个动作的名字,名字之间用空格隔开。dependencies描述产生targers所需的文件,一个targers通常依赖于多个dependencies。Command用于指定该规则的shell命令,Shell命令可以有若干行。
注意:command必须以Tab键开头。否则,make就会显示出错信息。如果某一命令行太长可以分作两行,用反斜杠(\\)连接。
举例,用vi编辑器输入如下Makefile文件: vi target ↙ test:prog.o code.o
gcc –o test prog.o code.o gcc –c prog.c –o prog.o gcc –c code.c –o code.o rm –f *.o
prog.o:prog.c prog.h code.h code.o:code.c code.h clean:
command command
第3章 Linux下应用程序开发基础 -63-
:wq ↙ (存盘退出)
在上面的Makefile文件中共定义了4个目标:test、prog.o、code.o和clean。每个目标都是从最左边开始写,后面跟一个(:)冒号,如果这个目标的实现依赖于其他的目标或文件,则把他们列在冒号的后面,并以空格隔开。然后另起一行开始写实现这个目标的一组shell命令,命令必须以Tab键开头。
(3)make 调用Makefile中的规则 一般情况下,调用make命令可输入: # make target
target是Makefile文件中定义的目标之一,如果省略target,make就将生成Makefile文件中定义的第一个目标。对于上面Makefile的例子,make默认执行的是规则test,因为test是Makefile文件中定义的第一个目标,此时只需要输入命令:
# make
make将读入Makefile,然后执行第一条规则,例子中该规则是链接目标文件生成库,因此必须执行规则test依赖的规则prog.o和 code.o。在执行过程中将自动更新他们所依赖的文件。
有些规则不是被依赖的规则,需要make指定才能被运行,如上面例子中的clean规则可以这样执行:
make clean
这两种方式的结果一样,只是第一种方式没指明目标名,第二种方式指明了目标名。 在Makefile中,并不是所有的目标都对应于磁盘上的文件,有的目标存在只是为了形成一条规则,从而完成特定的工作,并不生成新的目标文件,这样的目标称为伪目标。Clean就是伪目标,Clean目标一般用于删除最终生成的可执行文件和在编译过程中产生的所有目标文件。
make在检查一个目标是否已经过时并需要更新时,采用的是按相关性递归的方法。Make在构建一个目标之前要生成该目标所依赖的所有文件,并递归地前进,从而确保这些文件都是新的。
(4)设置Makefile中文件的搜索路径
在Makefile中,可以通过给VPATH赋值来设置规则中目标文件和依赖文件的搜索目录。Make首先搜索当前目录,如果未找到依赖的文件,make将按照VPATH中给的目录依次搜索。VPATH对Makefile中所有文件都有效。例如:
ming.o: ming.c ming.h
ming.c 在目录//c/ming/ 中,ming.h 在目录//c/ming/head/中,则可以给VPATH变量赋值: VPATH:=//c/ming //c/ming/head 或
VPATH:=//c/ming: //c/ming/head
-64- 嵌入式Linux系统开发与应用实验教程
也可以使用指令vpath,与VPATH在使用上的区别是:vpath可以给不同类文件指定不同的搜索目录。% .o表示所有以 .o结尾的子串。
vpath %.c //c/ming vpath %.h //c/ming/head
vpath %.c表示清除所有vpath对%.c设置的搜索目录 vpath表示清除所有以前用vpath设置的搜索目录
这两种方式的效果是一样的,但是后一种要明确一些。这样make就会根据VPATH或者vpath来搜索相应的依赖文件。
(5)定义变量和引用变量
为了简化Makefile的编写,make引入了变量。变量实际上是为文本串在Makefile中定义一个便于记忆的名称。变量的定义和应用与Linux环境变量一样,变量名要大写,变量一旦定义之后,就可以通过将变量名用圆括号括起来,并在前面加上“$”符号来进行引用。
变量的主要作用如下:
● 保存文件名列表。在前面的例子里,作为依赖文件的一些目标文件名出现在可执行文件的规则中,而在这个规则的命令行里同样包含这些文件并传递给gcc做为命令参数。如果使用一个变量来保存所有的目标文件名,则可以方便地加入新的目标文件而且不易出错。
● 保存可执行命令名,如编译器。在不同的Linux系统中存在着很多相似的编译器 系统,这些系统在某些地方会有细微的差别,如果项目被用在一个非gcc的系统里,则必须将所有出现编译器名的地方改成用新的编译器名。但是如果使用一个变量来代替编译器名,那么只需要改变该变量的值。其他所有地方的命令名就都改变了。
● 保存编译器的参数。在很多源代码编译时,gcc需要很长的参数选项,在很多情况下,所有的编译命令使用一组相同的选项,如果把这组选项使用一个变量代表,那么可以把这个变量放在所有引用编译器的地方。当要改变选项的时候,只需改变一次这个变量的内容即可。
变量一般都在Makefile的头部定义。如果变量的值发生了改变,只需在一个地方进行修改就可以了,从而大大简化了Makefile的维护。按照惯例,所有的Makefile变量都应该是大写。现在利用变量把前面的Makefile重写一遍:
OBJS=prog.o code.o CC=gcc test:${ OBJS }
${ CC } –o test ${ OBJS } ${ CC } –c prog.c –o prog.o ${ CC } –c code.c –o code.o rm –f *.o
prog.o:prog.c prog.h code.h code.o:code..c code.h clean:
第3章 Linux下应用程序开发基础 -65-
除了用户自定义变量之外,在Makefile中还可以使用环境变量、自动变量和预定义变量。make 在启动时会自动读取系统当前已经定义了环境变量,并且会创建与之具有相同名称和数值的变量。需要注意的是,如果用户在Makefile中定义了相同名称的变量,那么用户自定义变量将会覆盖同名的环境变量。Make提供的预定义变量和自动变量具有特殊的含义,可在规则中使用。表 3-1 给出了一些主要的预定义变量,除这些变量外, make 还将所有的环境变量作为自己的预定义变量。
表 3-1 GNU make 的主要预定义变量
预定义变量 $* $+ $< $? $@ $^ $%
(6)Makefile的隐含规则
在上面的例子中,几个产生目标文件的命令都是从―.c‖的C语言源文件和相关文件通过编译产生―.o‖目标文件,这也是一般的步骤。实际上,make可以使工作更加自动化,也就是说,make知道一些默认的动作,它有一些称作隐含规则的内置的规则,这些规则告诉make当用户没有完整地给出某些命令的时候,应该怎样执行。
例如,把生成prog.o和code.o的命令从规则中删除,make将会查找隐含规则,然后会找到并执行一个适当的命令。由于这些命令会使用一些变量,因此可以通过改变这些变量来定制make。象在前面的例子中所定义的那样,make使用变量CC来定义编译器,并且传递变量CFLAGS(编译器参数)、CPPFLAGS(C语言预处理器参数)、TARGET_ARCH(目标机器的结构定义)给编译器,然后加上参数-c,后面跟变量$<(第一个依赖文件名),然后是参数-o加变量$@(目标文件名)。综上所述,一个C编译的具体命令将会是:
$ {CC} $ {CFLAGS} $ {CPPFLAGS} $ {TARGET_ARCH} –c $< -o $@
在上面的例子中,利用隐含规则,可以简化为: OBJS=prog.o code.o CC=gcc
含 义 不包含扩展名的目标文件名称。 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件。 第一个依赖文件的名称。 所有的依赖文件,以空格分开,这些依赖文件的修改日期比目标的创建日期晚。 目标的完整名称。 所有的依赖文件,以空格分开,不包含重复的依赖文件。 如果目标是归档成员,则该变量表示目标的归档成员名称。例如,如果目标名称为 mytarget.so(image.o),则 $@ 为 mytarget.so,而 $% 为 image.o。 -66- 嵌入式Linux系统开发与应用实验教程
test:${ OBJS }
${ CC } –o $@ $^ prog.o:prog.c prog.h code.h code.o:code.c code.h clean:
再举例说明在我们的实验开发板上如何编写hello应用程序的Makefile : CC=arm-uclibc-gcc EXEC = hello OBJS = hello.o CFLAGS +=
LDFLAGS+=-elf2flt –static
all: $(EXEC) $(EXEC): $(OBJS)
$(CC) $(LDFLAGS) -o $@ $(OBJS) clean:
-rm -f $(EXEC) *.elf *.gdb *.o
这个makefile 显示了几个主要的部分:
◆ CC 指明编译器的宏。
◆ EXEC 表示生成的执行文件名称的宏。 ◆ OBJS 目标文件列表宏。 ◆ CFLAGS 编译参数宏。 ◆ LDFLAGS 连接参数宏。 ◆ all: 编译主入口。 ◆ clean:清除编译结果节。
rm –f *.o
3.3.6 调试工具GDB
Linux包含了一个叫GDB的GNU调试程序,GDB是一个用来调试C/C++程序的强力调试器,它使你能在程序运行时观察程序的内部结构和内存的使用情况,它可完成如下一些调试任务:
● 设置断点; ● 监视程序变量的值;