目录
Makefile定义
Makefile 文件描述了整个工程的编译、连接等规则。其中包括:
①工程中的哪些源文件需要编译以及如何编译
②需要创建哪些库文件以及如何创建这些库文件
③如何最后产生我们想要的可执行文件。
为工程编写Makefile 的好处是能够使用一行命令来完成“自动化编译”。
终端输入make
命令启用Makefile文件。make是一个命令工具,它解释Makefile 中的指令。
Makefile 有自己的书写格式、关键字、函数。像C 语言有自己的格式、关键字和函数一样。以下是 Makefile 的基本规则和语法:
Makefile基本语法
基本语法:
target: prerequisites
command
①目标(Target):目标是 Makefile 中的名称,它指定了要构建的输出文件的名称。目标可以是可执行文件、中间文件、或者其他文件。
②依赖(Prerequisites):依赖是目标生成所需的文件或者其他目标。如果目标所依赖的文件发生了变化,Make 将重新生成该目标。
③命令(Command):命令是 Makefile 中的一条指令,它告诉 Make 如何生成目标文件。命令必须以 Tab 键开始,不能用空格。
Makefile变量
Makefile 支持变量,可以使 Makefile 更易于维护和更具可读性。
变量定义之后,使用$
使用变量。类似于#define
宏定义。
例如:
CC=gcc
CFLAGS=-Wall
hello: hello.o
$(CC) $(CFLAGS) -o hello hello.o
hello.o: hello.c
$(CC) $(CFLAGS) -c hello.c
在这个例子中,CC 是 C 编译器的名称,CFLAGS 是编译选项,使编译器显示所有警告。
Makefile通配符
在 Makefile 中,通配符用于匹配文件名或文件路径中的多个字符,以便在规则中批量处理文件。通配符常见的有以下几种:
*
:匹配零个或多个字符。
?
:匹配一个任意字符。
[...]
:匹配方括号内的任意一个字符。
[!...]
:匹配除了方括号内的字符之外的任意一个字符。
在 Makefile 中,通配符通常与模式规则结合使用,以便自动化地处理一类文件。
例:
program: *.c
gcc $^ -o $@
在这个 Makefile 中,*.c
通配符匹配当前目录下所有以 .c 结尾的文件。$^
表示所有匹配到的文件,$@
表示目标文件。因此,program: *.c
规则会将所有的 .c 文件编译链接成一个名为 program 的可执行文件。
Makefile模式规则|自动化变量
模式规则
模式规则(Pattern Rule)是 Makefile 中的一种特殊规则,它用于定义一种模式,告诉 Make 工具如何将一类文件转换成另一类文件。通常,模式规则用于编译源文件到目标文件的转换过程。
例子:
%.o: %.c
gcc -c $< -o $@
这个模式规则告诉 Make 工具,如果存在以 .c 结尾的源文件,则可以使用 gcc 编译器将其编译成对应的 .o 目标文件。$<
表示第一个依赖文件(即源文件),$@
表示目标文件。
自动化变量
自动化变量是 Makefile 中的一种特殊变量,它们在规则的命令中使用,代表了与规则相关联的文件名。这些变量使得 Makefile 编写更加简洁和灵活。
以下是常用的自动化变量:
$@
:表示规则中的目标文件名。
$<
:表示规则中的第一个依赖文件名。
$^
:表示规则中的所有依赖文件名,以空格分隔。
$?
:表示比目标文件更新的所有依赖文件名,以空格分隔。
$(@D)
:表示目标文件所在的目录名。
$(@F)
:表示目标文件的文件名(不包含路径)。
下面是一个示例 Makefile,演示了如何使用自动化变量:
all: program
program: main.o func1.o func2.o
gcc -o $@ $^
main.o: main.c
gcc -c $< -o $@
func1.o: func1.c
gcc -c $< -o $@
func2.o: func2.c
gcc -c $< -o $@
clean:
rm -f *.o program
在这个示例中,使用了 $@
表示目标文件,$^
表示所有依赖文件,$<
表示第一个依赖文件。通过这些自动化变量,可以使 Makefile 更加简洁和易于维护。
Makefile伪目标
伪目标是 Makefile 中的一种特殊目标,它不对应任何实际的文件。伪目标通常用于定义一些命令或者规则,用于执行一些特殊的操作,例如清理、安装、测试等。
在 Makefile 中,伪目标通常以 .PHONY
声明,以告诉 Make 工具这些目标不是文件名,而是一个命令序列。
.PHONY: clean
clean:
rm -f *.o
在这个示例中,clean 是一个伪目标,它的作用是删除所有 .o 文件。.PHONY
声明告诉 Make 工具,clean 并不对应任何实际的文件,只是一个执行命令的目标。
一个裸机开发使用Makefile案例
例:
目的是编写点亮一个LED的c语言程序。
这个项目包含了几个目录和文件:
加粗样式目录:
①imx6ul: 存放了一些头文件。
②bsp/clk: 存放了与时钟相关的源文件。
③bsp/led: 存放了与 LED 相关的源文件。
④bsp/delay: 存放了与延时相关的源文件。
⑤project: 存放了项目的主要源文件。
文件:
①start.s启动文件。
②main.c主函数、led.c、clk.c、delay.c
③imx6ul.h 手搓寄存器的结构体定义文件。
链接脚本文件(imx6ul.lds):定义了链接器如何将目标文件链接成可执行文件。
最终,这个 Makefile 的目标是生成以下几个文件:
①bsp.bin:二进制可执行文件,可以在 ARM 平台上运行。
②bsp.elf:ELF 格式的可执行文件,用于调试和分析。
③bsp.dis:反汇编文件,包含了可执行文件的汇编代码。
目录展示:
Makefile:
CROSS_COMPILE ?= arm-linux-gnueabihf-
TARGET ?= bsp
CC := $(CROSS_COMPILE)gcc
LD := $(CROSS_COMPILE)ld
OBJCOPY := $(CROSS_COMPILE)objcopy
OBJDUMP := $(CROSS_COMPILE)objdump
INCDIRS := imx6ul \
bsp/clk \
bsp/led \
bsp/delay
SRCDIRS := project \
bsp/clk \
bsp/led \
bsp/delay
INCLUDE := $(patsubst %, -I %, $(INCDIRS))
SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
SFILENDIR := $(notdir $(SFILES))
CFILENDIR := $(notdir $(CFILES))
SOBJS := $(patsubst %, obj/%, $(SFILENDIR:.S=.o))
COBJS := $(patsubst %, obj/%, $(CFILENDIR:.c=.o))
OBJS := $(SOBJS) $(COBJS)
VPATH := $(SRCDIRS)
.PHONY: clean
$(TARGET).bin : $(OBJS)
$(LD) -Timx6ul.lds -o $(TARGET).elf $^
$(OBJCOPY) -O binary -S $(TARGET).elf $@
$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
$(SOBJS) : obj/%.o : %.S
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
$(COBJS) : obj/%.o : %.c
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
clean:
rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)
解释:
CROSS_COMPILE
和 TARGET
是两个变量,分别用于设置交叉编译工具链的前缀和生成的目标文件名。它们使用了赋值运算符 ?=
,表示如果这些变量没有在命令行中指定,就使用后面的默认值。
CC、LD、OBJCOPY、OBJDUMP
分别是编译器、链接器、目标文件复制工具和目标文件反汇编工具的路径。
INCDIRS
和 SRCDIRS
分别指定了包含头文件和源文件的目录。
INCLUDE
使用了函数 patsubst
将每个目录名转换成了 -I
选项的形式,用于指定头文件搜索路径。
SFILES
和 CFILES
分别使用了 wildcard 函数匹配所有汇编语言文件和 C 语言文件。
SFILENDIR
和 CFILENDIR
使用了 notdir
函数获取文件名(不包含路径)。
SOBJS
和 COBJS
使用了 patsubst 函数将每个源文件名转换成了对应的目标文件名,并加了 obj/
前缀表示目标文件存放的目录。
VPATH
再次设置了 Makefile 中文件的搜索路径,以便 Make 可以在这些目录中找到源文件。
.PHONY: clean
声明了 clean 目标是一个伪目标,不对应实际文件。主要用来删除/清理编译生成的文件。
$(TARGET).bin
是构建目标,依赖于 $(OBJS)
中定义的所有目标文件。该规则使用了链接器 $(LD)
将所有目标文件链接成一个.elf
可执行文件,并使用 $(OBJCOPY)
将其转换成二进制文件。生成了一个 .dis
文件,包含了可执行文件的反汇编结果。
$(SOBJS)
和 $(COBJS)
分别是汇编文件和 C 文件的编译规则,将源文件编译成目标文件。
解析每一个变量定义的实际含义:
LD = arm-linux-gnueabihf-ld
OBJCOPY = arm-linux-gnueabihf-objcopy
OBJDUMP = arm-linux-gnueabihf-objdump
INC_DIRS = imx6ul bsp/clk bsp/led bsp/delay #头文件所在目录
SRC_DIRS = project bsp/clk bsp/led bsp/delay #源文件所在目录
INCLUDE = -I imx6ul -I bsp/clk -I bsp/led -I bsp/delay # Makefile语法要求指明头文件目录的时候前面需要加上-I
S_FILE = project/start.s # $(foreach dir, $(SRC_DIRS), $(wildcard $(dir)/*.s))
# 这个函数将寻找每个源文件目录下的.s汇编文件
C_FILE = project/main.c bsp/clk/bsp_clk.c \
bsp/led/bsp_led.c bsp/delaybsp_delay.c # 同上,得出所有源文件目录下的.c文件
S_FILE_NDIR = start.s # $(notdir $(S_FILE))这个函将把参数中的文件路径去掉,只剩下文件
C_FILE_NDIR = main.c bsp_clk.c bsp_led.c bsp_delay.c # 同上,只剩下文件
S_OBJS = obj/start.o # $(S_FILE_NDIR:.s=.o)将S_FILE_NDIR中所有.s文件替换为.o文件;
# $(patsubst %, obj/%, $(S_FILE_NDIR:.s=.o))把
# $(S_FILE_NDIR:.s=.o)得到的所有文件变为obj/$(S_FILE_NDIR:.s=.o),
# 也就是在前面加上obj/
C_OBJS = obj/main.o obj/bsp_clk.o obj/bsp_led.o obj/bsp_delay.o
OBJS = obj/start.o obj/main.o obj/bsp_clk.o obj/bsp_led.o obj/bsp_delay.o
VPATH = project bsp/clk bsp/led bsp/delay
解析编译命令:
$(TARGET).bin : $(OBJS)
【等价于】==> bsp.bin : obj/start.o obj/main.o obj/bsp_clk.o obj/bsp_led.o obj/bsp_delay.o
$(LD) -Timx6ul.lds -o $(TARGET).elf $^
【等价于】==> arm-linux-gnueabihf-ld -Timx6ul.lds -o bsp.elf obj/start.o obj/main.o \
obj/bsp_clk.o obj/bsp_led.o obj/bsp_delay.o #链接所有.o文件生成.elf文件
$(OBJCOPY) -O binary -S $(TARGET).elf $@
【等价于】==> arm-linux-gnueabihf-objcopy -O binary -S bsp.elf bsp.bin #生成bin文件
$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
【等价于】==> arm-linux-gnueabihf-objdump -D -m arm bsp.elf > bsp.dis #生成反汇编
$(S_OBJS) : obj/%.o : %.s
【等价于】==> obj/start.o : start.s
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
【等价于】==> arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ul -I bsp/clk -I bsp/led -I bsp/delay -o obj/start.o start.s
$(C_OBJS) : obj/%.o : %.c
【等价于】==> obj/main.o : main.c
obj/bsp_clk.o : bsp_clk.c
obj/bsp_led.o : bsp_led.c
obj/bsp_delay.o : bsp_delay.c
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
【等价于】==>
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ul -I bsp/clk -I bsp/led -I bsp/delay -o obj/main.o main.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ul -I bsp/clk -I bsp/led -I bsp/delay -o obj/bsp_clk.o bsp_clk.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ul -I bsp/clk -I bsp/led -I bsp/delay -o obj/bsp_led.o bsp_led.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ul -I bsp/clk -I bsp/led -I bsp/delay -o obj/bsp_delay.o bsp_delay.c
rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)
【等价于】==>rm -rf bsp.elf bsp.dis bsp.bin obj/main.o obj/bsp_clk.o obj/bsp_led.o obj/bsp_delay.o obj/start.o
终端运行: