本文章适用于从事嵌入式uboot移植,和驱动开发的工程师阅读,涉及到的知识点比较基础,涉及内容不是那么深入。
首先Makefile是管理一个大型项目必须的文件,当一个项目中涉及到多个C语言程序,和.h头文件程序,这种情况下就必须使用到Makefile进行项目的编译和链接。Makefile文件的格式:
目标:依赖
TAB命令
命令前面是一个Tab键,当且仅当是Tab按键,不能是空格,必须是一个Tab按键。然后书写命令。
一、一个简单的Makefile文件的编写与执行
1、Makefile文件的编写
编译一个.c文件,生成hello的输出文件。
1 hello:hello.c
2 gcc hello.c -o hello
3
4 clean:
5 rm hello
在Linux的命令行中使用make hello命令,就可以直接执行gcc hello.c -o hello这句代码;当在Linux命令行中键入make clean命令,就会执行rm hello这句代码,将生成的hello可执行文件删除。其中使用make命令等同于make hello。默认make执行第一个目标所对应的命令。
二、裸机程序的Makefile分析
led.bin: start.o
arm-linux-ld -Ttext 0x0 -o led.elf $^ //生成可执行文件led.elf,这个文件不可用来烧录
arm-linux-objcopy -O binary led.elf led.bin ;用来生成可烧写的镜像文件的。
arm-linux-objdump -D led.elf > led_elf.dis ;用来反汇编的。就是把编译好的elf格式的可执行程序,反过来,得到汇编源代码
gcc mkv210_image.c -o mkx210 ;gcc是用来生成可执行程序,在Ubuntu中执行的,目的就是为了下面的代码
./mkx210 led.bin 210.bin ;制作SD卡启动的镜像,目的就是为了加上校验头
%.o : %.S ;%是makefile中的通配符,%.o代表所有.o的文件,自动推导规则
arm-linux-gcc -o $@ $< -c ;$@代表目标文件的名称,$<代表依赖文件的名称;-c是只编译不链接的意思
%.o : %.c
arm-linux-gcc -o $@ $< -c
.PHONY clean
clean:
rm *.o *.elf *.bin *.dis mkx210 -f
这是编译一个裸机程序所使用到的Makefile文件,其中涉及到几个知识点:
1、Makefile自动推导规则
例如我们make的时候,此时就会执行led.bin对应的命令,但是在执行该命令之前,程序会自动检测依赖文件start.o的来源,首先我们找不到这个start.o文件在哪里,这时候程序就会从下面的目标中寻找,首先判断该.o文件是不是由.S文件生成的,如果是就会执行对应目标下的命令,来生成该.o文件;如果不是,程序就会检测是不是对应的.c文件生成的,如果是,就会执行对应的命令生成.o的文件。这就是Makefile的自动推导规则。
2、Makefile的伪目标
伪目标就是没有依赖的目标,这段程序的目的就是为了执行伪目标对应的命令,不生成任何的文件,所以叫做伪目标。表示方式就是在.PHONY,例如上面clean目标就是一个伪目标。就是为了执行删除的目的。
3、Makefile中一些自动变量
也Makefile中预定义的具有特殊意义的符号。
$@:规则中目标文件名。
$<:规则中依赖文件名。
$^:依赖的文件集合。
4、Makefile中的通配符
(1) * : 若干个任意字符。
(2) ? : 1个任意字符。
(3) [ ] : 将[]中字符一次与外面的匹配结合。
(4) % : 表示任意字符,但是只用于规则描述中,又叫做规则通配符。
例如项目中有1.c、2.c、12.c、test.c几个文件,使用下面通配符进行输出的结果:
1 all:1.c 2.c 12.c test.c
2 echo *.c #会输出所有的文件
3 echo ?.c #输出1.c和2.c,长度为1的
4 echo [12].c #输出1.c和2.c
5
5、其他几点注意事项
(1)Makefile中注释和shell中相同的,都是用#进行注释
(2)在命令前面叫上@符号,表示静默执行,makefile在执行的时候就不会输出该命令。而是直接输出结果。
(3)变量的赋值运算符号:
"?=":表示如果前面定义过变量,就不给该变量赋值,如果前面没有该变量的定义,就给该变量赋值。
"+=":可以将Makefile中的变量,看成字符串,该赋值运算符,就相当于字符串的strcat函数的作用,将字符串连接起来。
"=" : 变量的赋值,需要前后考虑,不仅是前面变量的值,如果在后面变量的值发生改变,也是会对这个产生作用的。
":=" :变量的赋值,只考虑前面的代码。下面使用代码进行讲解。
1
2
3 A=abc
4 B=$(A)def
5 A=ght
6
7 all:
8 echo $B
#这段代码的输出为ghtdef
1
2
3 A=abc
4 B:=$(A)def
5 A=ght
6
7 target1:
8 echo $B
#这段代码的输出为abcdef
三、子Makefile和主Makefile如何关联
当一个项目中涉及到多个Makefile时,该如何去管理这些makefile了,这里面涉及到的多个Makefile分为两类:主Makefile和子Makefile。那么我们如何让这里Makefile能够很好的合作,而不出问题了,怎么通过主Makefile来编译所有的子Makefile对应的文件了。
例如这里的两个Makefile文件。
#子Makefile文件
objs := div64.o lib1funcs.o ctype.o muldi3.o printf.o string.o vsprintf.o
libc.a: $(objs)
${AR} -r -o $@ $^ #使用AR,这个命令是主Makefile文件传递过来的,用来生成库文件的
%.o:%.c
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
%.o:%.S
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
clean:
rm -f libc.a *.o
#主Makefile文件
CC = arm-linux-gcc
LD = arm-linux-ld
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdump
AR = arm-linux-ar
INCDIR := $(shell pwd)
# C预处理器的flag,flag就是编译器可选的选项
CPPFLAGS := -nostdlib -nostdinc -I$(INCDIR)/include
# C编译器的flag,-O2优化等级,-fno-builtin在编译链接的时候使用本地的文件和头文件
CFLAGS := -Wall -O2 -fno-builtin
#导出这些变量到全局,其实就是给子文件夹下面的Makefile使用
export CC LD OBJCOPY OBJDUMP AR CPPFLAGS CFLAGS
objs := start.o sdram_init.o led.o uart.o main.o
#objs += clock.o
objs += lib/libc.a
uart.bin: $(objs)
$(LD) -Tlink.lds -o uart.elf $^
$(OBJCOPY) -O binary uart.elf uart.bin
$(OBJDUMP) -D uart.elf > uart_elf.dis
gcc mkv210_image.c -o mkx210
./mkx210 uart.bin 210.bin
lib/libc.a:
cd lib; make; cd ..
%.o : %.S
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c
%.o : %.c
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c
clean:
rm *.o *.elf *.bin *.dis mkx210 -f
cd lib; make clean; cd ..
1、objs += lib/libc.a 这句代码,就是将主Makefile和子Makefile关联起来的代码,当执行目标的时候,主Makefile就会判断依赖文件的来源,这时候就会检测到libc.a文件的对应的目标,就会执行
lib/libc.a:
cd lib; make; cd ..
对应的命令,注意这几句代码,需要放在一行书写。首先就如子Makefile文件,然后执行子Makefile,然后推出。